Tabwalk Public demo

A real browser. A reproducible result.

The page looks fine.
Try leaving the form.

Tabwalk presses Tab and Shift+Tab, tries Escape, and shows where keyboard navigation breaks. Start with this small example, then explore the report below.

The keyboard trap and its fix

Recording transcript: Tab enters the email field, then Subscribe. Tab cycles back to email. Shift+Tab and Escape cannot leave the form. After removing the custom focus loop, Tab reaches the privacy link and Shift+Tab leaves the form.

Same markup. Different keyboard behaviour.

Static checks alone miss this trap.

WCAG violations detected in the two versions
CheckBrokenFixed
axe-core violations00
Keyboard violations1 trap0

Counts above come from scanning both versions with the same Tabwalk checkers. Zero findings does not establish accessibility.

See the fix

Remove the custom Tab handler from this ordinary form. Native keyboard navigation already moves between its controls and the rest of the page.

// Remove the handler that redirects focus:
if (!event.shiftKey && active === last) {
  event.preventDefault();
  first.focus();
}
Both versions and their source

Explore the scanner output

Acme Store

A deliberately broken demo shop, checked with the production scanner.

Download JSON
17unique problems
7critical
4need a human
6pages checked

Generated 2026-10-08 · develop snapshot · 81 affected elements · 2 page(s) with partial keyboard coverage. Open a finding for evidence and a page for its Tab order.

28 findings shown

criticalImages must have alternative text

axe-core · image-alt · 6 page(s) · 7 element(s)

wcag2a · wcag111 · EN-9.1.1.1 · RGAA-1.1.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<img src="/img/logo.svg" width="120" height="40">

img[src$="logo.svg"]

Fix any of the following: Element does not have an alt attribute aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element's default semantics were not overridden with role="none" or role="presentation"

Found on: /about.html, /contact.html, /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

criticalImages must have alternative text

axe-core · image-alt · 6 page(s) · 14 element(s)

wcag2a · wcag111 · EN-9.1.1.1 · RGAA-1.1.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<img src="/img/cart.svg">

iframe img[src$="cart.svg"]

Fix any of the following: Element does not have an alt attribute aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element's default semantics were not overridden with role="none" or role="presentation"

Found on: /about.html, /contact.html, /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

criticalButtons must have discernible text

axe-core · button-name · 4 page(s) · 6 element(s)

wcag2a · wcag412 · EN-9.4.1.2 · RGAA-11.9.1 · section508

An affected element, outlined on the demo page

Pictured on: /. The HTML and selector below describe this element.

<button class="add"><img src="/img/cart.svg"></button>

.card:nth-child(1) > button

Fix any of the following: Element does not have inner text that is visible to screen readers aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element does not have an implicit (wrapped) <label> Element does not have an explicit <label> Element's default semantics were not overridden with role="none" or role="presentation"

Found on: /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

criticalForm elements must have labels

axe-core · label · 3 page(s) · 3 element(s)

wcag2a · wcag412 · EN-9.4.1.2 · RGAA-11.1.1 · section508

An affected element, outlined on the demo page

Pictured on: /products/apron.html. The HTML and selector below describe this element.

<input type="number" value="1" min="1">

input

Fix any of the following: Element does not have an implicit (wrapped) <label> Element does not have an explicit <label> aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element has no placeholder attribute Element's default semantics were not overridden with role="none" or role="presentation"

Found on: /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

criticalButtons must have discernible text

axe-core · button-name · 2 page(s) · 2 element(s)

wcag2a · wcag412 · EN-9.4.1.2 · RGAA-11.9.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<button><img src="/img/cart.svg"></button>

iframe #content > form > button

Fix any of the following: Element does not have inner text that is visible to screen readers aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element does not have an implicit (wrapped) <label> Element does not have an explicit <label> Element's default semantics were not overridden with role="none" or role="presentation"

Found on: /about.html, /contact.html

Read the rule guidance

criticalImages must have alternative text

axe-core · image-alt · 1 page(s) · 1 element(s)

wcag2a · wcag111 · EN-9.1.1.1 · RGAA-1.1.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<img src="/img/kettle.svg" width="240">

img[src$="kettle.svg"]

Fix any of the following: Element does not have an alt attribute aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute Element's default semantics were not overridden with role="none" or role="presentation"

Found on: /about.html

Read the rule guidance

criticalKeyboard focus must not get stuck in one part of the page

keyboard · keyboard-trap · 1 page(s) · 1 element(s)

wcag2a · wcag212 · EN-9.2.1.2 · RGAA-12.9.1 · section508

An affected element, outlined on the demo page

Pictured on: /contact.html. The HTML and selector below describe this element.

<form>
        <input type="email" placeholder="Email" aria-label="Email for the newsletter">
        <button type="submit">Sign up</button>
      </form>

#newsletter > form

Focus cycles between 2 elements and never leaves: #newsletter > form > input, #newsletter > form > button. Tab, Shift+Tab and Escape do not get out.

Found on: /contact.html

Read the rule guidance

seriousElements must meet minimum color contrast ratio thresholds

axe-core · color-contrast · 6 page(s) · 7 element(s)

wcag2aa · wcag143 · EN-9.1.4.3 · RGAA-3.2.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<p>Questions about an order? <a href="/contact.html">Click here</a>.</p>

iframe p:nth-child(1)

Fix any of the following: Element has insufficient color contrast of 2.84 (foreground color: #999999, background color: #ffffff, font size: 9.8pt (13px), font weight: normal). Expected contrast ratio of 4.5:1

Found on: /about.html, /contact.html, /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

seriousElements must meet minimum color contrast ratio thresholds

axe-core · color-contrast · 6 page(s) · 7 element(s)

wcag2aa · wcag143 · EN-9.1.4.3 · RGAA-3.2.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<p>© Acme Store</p>

iframe p:nth-child(2)

Fix any of the following: Element has insufficient color contrast of 2.84 (foreground color: #999999, background color: #ffffff, font size: 9.8pt (13px), font weight: normal). Expected contrast ratio of 4.5:1

Found on: /about.html, /contact.html, /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

seriousKeyboard focus must be visible

keyboard · focus-visible · 6 page(s) · 6 element(s)

wcag2aa · wcag247 · EN-9.2.4.7 · RGAA-10.7.1 · section508

<a class="skip" href="#content">Skip to content</a>

a.skip

Keyboard focus moves to this element, but it is not visible on the screen.

Found on: /about.html, /contact.html, /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

seriousLinks must have discernible text

axe-core · link-name · 6 page(s) · 7 element(s)

wcag2a · wcag244 · wcag412 · EN-9.2.4.4 · EN-9.4.1.2 · RGAA-6.2.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<a href="/"><img src="/img/logo.svg" width="120" height="40"></a>

header > a[href="/"]

Fix all of the following: Element is in tab order and does not have accessible text Fix any of the following: Element does not have text that is visible to screen readers aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element has no title attribute

Found on: /about.html, /contact.html, /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

seriousElements must meet minimum color contrast ratio thresholds

axe-core · color-contrast · 4 page(s) · 6 element(s)

wcag2aa · wcag143 · EN-9.1.4.3 · RGAA-3.2.1 · section508

An affected element, outlined on the demo page

Pictured on: /. The HTML and selector below describe this element.

<p class="price">£39.00</p>

.card:nth-child(1) > .price

Fix any of the following: Element has insufficient color contrast of 2.09 (foreground color: #b3b3b3, background color: #ffffff, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1

Found on: /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

seriousKeyboard focus must be visible

keyboard · focus-visible · 4 page(s) · 6 element(s)

wcag2aa · wcag247 · EN-9.2.4.7 · RGAA-10.7.1 · section508

An affected element, outlined on the demo page

Pictured on: /. The HTML and selector below describe this element.

<button class="add"><img src="/img/cart.svg"></button>

div.card:nth-of-type(1) > button.add

Nothing on the screen changes when this element receives keyboard focus.

Found on: /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

seriousLinks must be distinguishable without relying on color

axe-core · link-in-text-block · 4 page(s) · 5 element(s)

wcag2a · wcag141 · EN-9.1.4.1 · RGAA-10.6.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<a href="/">Home</a>

.crumbs > a[href="/"]

Fix any of the following: The link has insufficient color contrast of 2.2:1 with the surrounding text. (Minimum contrast is 3:1, link text: #333333, surrounding text: #666666) The link has no styling (such as underline) to distinguish it from the surrounding text

Found on: /about.html, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

seriousFocused elements must not be hidden under other content

keyboard · focus-obscured · 1 page(s) · 1 element(s)

wcag22aa · wcag2411

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<header>
    <a href="/"><img src="/img/logo.svg" width="120" height="40"></a>
    <nav>
      <a href="/">Home</a>
      <a href="/products/kettle.html">Kettles</a>
      <a href="/about.html">About</a>
      <a href="/contact.html">Contact</a>
    </nav>
  </header>

header

Covers p.crumbs:nth-of-type(1) > a when it receives keyboard focus after the skip link.

Found on: /about.html

Read the rule guidance

seriousFrames must have an accessible name

axe-core · frame-title · 1 page(s) · 1 element(s)

wcag2a · wcag412 · EN-9.4.1.2 · RGAA-2.1.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<iframe src="/contact.html" width="480" height="120"></iframe>

iframe

Fix any of the following: Element has no title attribute aria-label attribute does not exist or is empty aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty Element's default semantics were not overridden with role="none" or role="presentation"

Found on: /about.html

Read the rule guidance

seriousAll touch targets must be 24px large, or leave sufficient space

axe-core · target-size · 1 page(s) · 1 element(s)

wcag22aa · wcag258

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<input type="email" placeholder="Email">

iframe #content > form > input[type="email"][placeholder="Email"]

Fix any of the following: Target has insufficient size (177px by 21px, should be at least 24px by 24px) Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of 21px instead of at least 24px.

Found on: /about.html

Read the rule guidance

Needs a humanElements must meet minimum color contrast ratio thresholds

axe-core · color-contrast · 1 page(s) · 1 element(s)

wcag2aa · wcag143 · EN-9.1.4.3 · RGAA-3.2.1 · section508

An affected element, outlined on the demo page

Pictured on: /. The HTML and selector below describe this element.

<h1>Kitchen things that last</h1>

h1

Fix any of the following: Element's background color could not be determined due to a background image

Found on: /

Read the rule guidance

Needs a humanElements must meet minimum color contrast ratio thresholds

axe-core · color-contrast · 1 page(s) · 1 element(s)

wcag2aa · wcag143 · EN-9.1.4.3 · RGAA-3.2.1 · section508

<p>Free delivery on orders over £40 — this week only.</p>

section > p

Fix any of the following: Element's background color could not be determined due to a background image

Found on: /

Read the rule guidance

Needs a humanKeyboard focus must not get stuck in one part of the page

keyboard · keyboard-trap · 1 page(s) · 1 element(s)

wcag2a · wcag212 · EN-9.2.1.2 · RGAA-12.9.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<iframe src="/contact.html" width="480" height="120"></iframe>

iframe

Focus stayed inside this frame for 100 presses of Tab. Check that keyboard users can get out of it.

Found on: /about.html

Read the rule guidance

Needs a human<video> elements must have captions

axe-core · video-caption · 1 page(s) · 1 element(s)

wcag2a · wcag122 · EN-9.1.2.2 · RGAA-4.3.1 · section508

An affected element, outlined on the demo page

Pictured on: /about.html. The HTML and selector below describe this element.

<video src="/img/workshop.mp4" controls="" width="480"></video>

video

Fix all of the following: Check that captions are available for the element

Found on: /about.html

Read the rule guidance

RecommendationHeading levels should only increase by one

axe-core · heading-order · 4 page(s) · 4 element(s)

<h4>Best sellers</h4>

#content > h4

Fix any of the following: Heading order invalid

Found on: /, /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

RecommendationAll page content should be contained by landmarks

axe-core · region · 3 page(s) · 3 element(s)

RGAA-9.2.1

<a class="skip" href="#content">Skip to content</a>

.skip

Fix any of the following: Some page content is not contained by landmarks

Found on: /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

RecommendationThe skip-link target should exist and be focusable

axe-core · skip-link · 3 page(s) · 3 element(s)

RGAA-12.7.1

<a class="skip" href="#content">Skip to content</a>

.skip

Fix any of the following: No skip link target

Found on: /products/apron.html, /products/kettle.html, /products/pan.html

Read the rule guidance

RecommendationLandmarks should have a unique role or role/label/title (i.e. accessible name) combination

axe-core · landmark-unique · 1 page(s) · 1 element(s)

<header>
    <a href="/"><img src="/img/logo.svg" width="120" height="40"></a>
    <nav>
      <a href="/">Home</a>
      <a href="/products/kettle.html">Kettles</a>
      <a href="/about.html">About</a>
      <a href="/contact.html">Contact</a>
    </nav>
  </header>

header

Fix any of the following: The landmark must have a unique aria-label, aria-labelledby, or title to make landmarks distinguishable

Found on: /about.html

Read the rule guidance

RecommendationLandmarks should have a unique role or role/label/title (i.e. accessible name) combination

axe-core · landmark-unique · 1 page(s) · 1 element(s)

<nav>
      <a href="/">Home</a>
      <a href="/products/kettle.html">Kettles</a>
      <a href="/about.html">About</a>
      <a href="/contact.html">Contact</a>
    </nav>

nav

Fix any of the following: The landmark must have a unique aria-label, aria-labelledby, or title to make landmarks distinguishable

Found on: /about.html

Read the rule guidance

RecommendationLandmarks should have a unique role or role/label/title (i.e. accessible name) combination

axe-core · landmark-unique · 1 page(s) · 1 element(s)

<main id="content">

#content

Fix any of the following: The landmark must have a unique aria-label, aria-labelledby, or title to make landmarks distinguishable

Found on: /about.html

Read the rule guidance

RecommendationLandmarks should have a unique role or role/label/title (i.e. accessible name) combination

axe-core · landmark-unique · 1 page(s) · 1 element(s)

<footer>
    <p>Questions about an order? <a href="/contact.html">Click here</a>.</p>
    <p>© Acme Store</p>
  </footer>

iframe footer

Fix any of the following: The landmark must have a unique aria-label, aria-labelledby, or title to make landmarks distinguishable

Found on: /about.html

Read the rule guidance

Pages and keyboard coverage

A completed walk describes the initial page state. Other states and manual checks still need review.

/about.html — About — Acme Store

Partial walk · 10 stops visited · 6 focus style samples tested

Limits or omissions: Focus styles inside embedded frames were not checked. The keyboard walk could not finish traversing a frame.

Numbered keyboard stops on /about.html
  1. Skip to content a.skip
  2. No text header > a
  3. Home nav > a:nth-of-type(1)
  4. Kettles a:nth-of-type(2)
  5. About a:nth-of-type(3)
  6. Contact a:nth-of-type(4)
  7. Home p.crumbs:nth-of-type(1) > a
  8. our story p:nth-of-type(2) > a
  9. No text video
  10. No text iframe
/contact.html — Contact — Acme Store

Partial walk · 13 stops visited · 10 focus style samples tested

Limits or omissions: A focus cycle prevented the forward walk from continuing. Some potentially focusable controls were not reached.

Numbered keyboard stops on /contact.html
  1. Skip to content a.skip
  2. No text header > a
  3. Home nav > a:nth-of-type(1)
  4. Kettles a:nth-of-type(2)
  5. About a:nth-of-type(3)
  6. Contact a:nth-of-type(4)
  7. Your name #content > form > input:nth-of-type(1)
  8. Email input:nth-of-type(2)
  9. Order question Returns select
  10. No text textarea
  11. No text #content > form > button
  12. Email for the newsletter #newsletter > form > input
  13. Sign up #newsletter > form > button
/ — Acme Store

Walk completed · 13 stops visited · 6 focus style samples tested

Numbered keyboard stops on /
  1. Skip to content a.skip
  2. No text header > a
  3. Home nav > a:nth-of-type(1)
  4. Kettles a:nth-of-type(2)
  5. About a:nth-of-type(3)
  6. Contact a:nth-of-type(4)
  7. Kettle 1.7 L div.card:nth-of-type(1) > h4 > a
  8. No text div.card:nth-of-type(1) > button.add
  9. Cast iron pan div.card:nth-of-type(2) > h4 > a
  10. No text div.card:nth-of-type(2) > button.add
  11. Linen apron div.card:nth-of-type(3) > h4 > a
  12. No text div.card:nth-of-type(3) > button.add
  13. Click here p:nth-of-type(1) > a
/products/apron.html — Linen apron — Acme Store

Walk completed · 10 stops visited · 7 focus style samples tested

Numbered keyboard stops on /products/apron.html
  1. Skip to content a.skip
  2. No text header > a
  3. Home nav > a:nth-of-type(1)
  4. Kettles a:nth-of-type(2)
  5. About a:nth-of-type(3)
  6. Contact a:nth-of-type(4)
  7. 1 input
  8. No text button.add
  9. delivery terms p:nth-of-type(2) > a
  10. Click here p:nth-of-type(1) > a
/products/kettle.html — Kettle 1.7 L — Acme Store

Walk completed · 10 stops visited · 7 focus style samples tested

Numbered keyboard stops on /products/kettle.html
  1. Skip to content a.skip
  2. No text header > a
  3. Home nav > a:nth-of-type(1)
  4. Kettles a:nth-of-type(2)
  5. About a:nth-of-type(3)
  6. Contact a:nth-of-type(4)
  7. 1 input
  8. No text button.add
  9. delivery terms p:nth-of-type(2) > a
  10. Click here p:nth-of-type(1) > a
/products/pan.html — Cast iron pan — Acme Store

Walk completed · 10 stops visited · 7 focus style samples tested

Numbered keyboard stops on /products/pan.html
  1. Skip to content a.skip
  2. No text header > a
  3. Home nav > a:nth-of-type(1)
  4. Kettles a:nth-of-type(2)
  5. About a:nth-of-type(3)
  6. Contact a:nth-of-type(4)
  7. 1 input
  8. No text button.add
  9. delivery terms p:nth-of-type(2) > a
  10. Click here p:nth-of-type(1) > a

Run the same example yourself.

This is a read-only snapshot, generated from the examples in the repository. Filters and print work here; scheduling, notifications and new scans run in your own installation.

git clone https://github.com/Artemy-And/tabwalk.git
cd tabwalk
docker compose -f docker-compose.yml \
  -f docker-compose.demo.yml up -d

Open http://localhost:8080, create your local admin account, add http://acmestore.example/ and run a scan.