GitHub (opens in a new tab)
Mode
Themes
Color-vision friendly
High contrast

The listening room

A page a scanner passes. Below sits the band’s press kit, the document a venue or a journalist gets sent, and planted in it are eleven accessibility barriers you hear before you see. The instrument changes here: the hunt is a screen reader, running A·03’s fifteen-minute plan first and DevTools second, to confirm in the tree what your ears already caught. Under the WCAG rule set a scanner reports nothing on the press kit at all. This site’s own suite measured that.

The contract: every barrier lives inside the framed page below, nothing outside the frame is broken, none of its links or buttons do anything real, and the answers wait at the end. The broken page is a page of its own, deliberately outside this site’s styles and safety nets, so what you catch behaves the way it would in the wild. What sits below is only a half-scale preview; the hunt happens at full scale in its own tab. Two of the eleven live in pronunciation alone: the page looks right, the tree’s structure is untouched, and only the voice gives them away. Your tools will tell you the page is fine, which is the lesson: zero WCAG violations, two best-practice hits that both trace back to a single barrier. If you would rather read than hunt, skip past the broken page, straight to the answers.

Broken on purpose · barriers 1–11 · preview at 1:2

Open it at 1:1, start your screen reader, then run the plan. The tree pane is for afterwards.

End of the broken page. Everything from here on is compliant again.

Answers

Each barrier below opens to a hint first; the answer hides one level deeper. Every reveal carries what a screen reader announces at that spot and what the fix makes it say instead, so the room is learnable without a screen reader on hand. The announcements are read from the accessibility tree this page builds, in VoiceOver’s phrasing; your own engine will differ in wording, and where engines differ in substance the answer says so rather than averaging them.

  1. Barrier 1: pull the heading list and read the first row
    Reveal the answer

    The h1 is an inline SVG wordmark with no title element and no role, so the level-one heading has no accessible name: the outline opens with a blank row. The heading after the sidebar’s h2 is an h4, because h4 happened to be the right size in the design, so the outline also jumps from level 2 to level 4 at its second step.

    Heard: "heading level 1", then either nothing or a bare "image", which the VoiceOver pass will settle. The rotor’s heading list opens with an empty row; under it come "Facts, heading level 2" and then "Biography, heading level 4".

    Should say: "Fitis, heading level 1", then "Facts, heading level 2", then "Biography, heading level 2".

    WCAG 1.3.1 Info and Relationships · Caught by: the heading list, and the tree; this is the one barrier a scanner half-sees, as two best-practice hits

    Put the band name in the h1 as text and let CSS draw the wordmark, or give the SVG a title element and role="img". Then size the headings with CSS and let the levels run 1, 2, 2. Pulling the heading list is step two of the fifteen-minute plan.

  2. Barrier 2: listen to the gap between two sections
    Reveal the answer

    The ornament between sections is an img with alt="divider ornament". It is pure decoration and it is announced eight times, once after the intro and once between every pair of body sections.

    Heard: "divider ornament, image", eight times over: it lands after the intro, before "Facts, heading level 2", and again ahead of each section in the main column after the biography, including the one whose heading you never hear.

    Should say: Nothing at all. The biography ends and "Press quotes, heading level 2" follows it directly.

    WCAG 1.1.1 Non-text Content · Caught by: continuous reading, and the tree; a scanner sees an alt attribute and passes

    alt="" removes a decorative image from the tree entirely. Writing alt text is not always the accessible choice: deciding there is nothing to say is, as craft’s hiding matrix sets out.

  3. Barrier 3: read the page top to bottom without looking at it
    Reveal the answer

    The facts sidebar sits first in the DOM and is moved to the right column with CSS order. The eye reads the biography first and the facts second; continuous reading gets line-up, sound, release date and press contact before the page has said who the band is.

    Heard: "Facts, heading level 2. Line-up, Richard Boden, vocals and guitar. Thomas Sweet, keys and vocals. Phillip Daduna, drums. Sebastian Hartwig, guitar. Sound, postpunk, alternative rock, German lyrics…", and only after all of it, "Biography, heading level 4".

    Should say: "Biography, heading level 2. Fitis sind vier Musiker aus Berlin…", with the facts panel read after the band has been introduced, in the order the eye takes them.

    WCAG 1.3.2 Meaningful Sequence · Caught by: continuous reading, and the source-order viewer in the accessibility pane; a scanner cannot read intent, so it stays silent

    Put the DOM in the order the content means and let grid or flex place it visually. Reordering is a layout tool, not a sequencing one.

  4. Barrier 4: something in the biography sounds wrong
    Reveal the answer

    The four German biography sentences carry no lang attribute, so they inherit lang="en" from the document and the synthesizer applies English phonemes to German. Nothing is invalid, so every language rule passes. The EP title in the intro is tagged lang="de" correctly, on purpose, as the control.

    Heard: The intro says "Alles auf Anfang" in a German voice, and then the biography arrives as "Fitis sind veer Moo-zicker ows Berlin", English vowels on every German word, all the way to "roh und direkt".

    Should say: The same four sentences in the German voice, the way the EP title already sounds.

    WCAG 3.1.2 Language of Parts · Caught by: the ear, and only the ear. Nothing here is visible and nothing here is invalid

    lang="de" on the element that holds the German. One attribute, and the voice switches.

  5. Barrier 5: the press quotes have an accent that is not theirs
    Reveal the answer

    The quotes wrapper carries lang="fr", copied in from a template. The code is perfectly valid and the text is English, which is exactly why no rule fires: valid-lang checks the code, not whether it is true.

    Heard: "Fore peupl, ouanne roum, no sayftee net", the French voice working its way through "Four people, one room, no safety net", and the same for the other two quotes.

    Should say: The quotes in the page voice, English, with no language switch at all.

    WCAG 3.1.2 Language of Parts · Caught by: the ear; the tree shows the inherited language if you go looking, and a scanner passes a valid code

    Delete the attribute. A wrong language declaration is worse than none, because it overrides a document language that was right.

  6. Barrier 6: the press photo announces itself twice
    Reveal the answer

    The hero photo’s alt begins "Image of the band Fitis…". The role already says image, so the words are doubled: alt text describes what is shown, it never says what the element is.

    Heard: "Image of the band Fitis, four members, one reaching toward the camera, image". The word image at both ends of one announcement.

    Should say: "The band Fitis, four members, one reaching toward the camera, image".

    WCAG 1.1.1 Non-text Content · Caught by: the ear; axe’s rule for this ships as best-practice and needs the alt to equal its surrounding text, which it does not here, so nothing fires

    Drop the prefix. The six thumbnails under it are the control: their alt text is distinct, specific and says nothing about being pictures, the swaps the agent skill makes first.

  7. Barrier 7: try to navigate the technical rider by row and column
    Reveal the answer

    The rider is a real table with th scope="col" and th scope="row" underneath, and role="presentation" on top, added once to lose the default borders. The role wins: the semantics are stripped and the cells arrive as loose text.

    Heard: One flat run: "Position Requirement Provided by Vocals Two dynamic vocal microphones with boom stands Venue Guitar Two combo amplifiers…". No "table, seven rows, three columns", and table navigation with Control Option and the arrow keys does nothing.

    Should say: "table, seven rows, three columns", then, on entering a cell, "Vocals, row 2 of 7. Provided by, column 3 of 3: Venue."

    WCAG 1.3.1 Info and Relationships · Caught by: table navigation going dead, and the tree; presentation-role-conflict only fires on a focusable or ARIA-carrying element, so a scanner passes this

    Remove the role and style the borders away in CSS. A table that looks plain is still a table.

  8. Barrier 8: ask the rotor to jump you to the setlist
    Reveal the answer

    The six songs are one paragraph of br elements and typed dashes. It looks like a list and carries none of the semantics, so there is no item count, no per-item position and nothing for the rotor’s list menu to land on.

    Heard: "01 - Im Radio 02 - Lauf 03 - Schatten der Vergangenheit 04 - Fallen…", one long paragraph, and the rotor’s list menu never offers the setlist at all.

    Should say: "list, 6 items", then "1, Im Radio, 1 of 6", with the arrow keys walking the songs.

    WCAG 1.3.1 Info and Relationships · Caught by: the ear, and the tree; a scanner is silent because nothing here claims to be a list

    An ol with six li elements. The count and the position come free, and the numbering is the list’s job rather than typed text.

  9. Barrier 9: four dates are on the screen; count how many you hear
    Reveal the answer

    The tour dates section was left aria-hidden="true" after a demo where it "cluttered VoiceOver". Sighted visitors see a heading and four dates; screen reader users get silence. Nothing inside is focusable, so the rule that catches aria-hidden over a focusable element has nothing to fire on.

    Heard: Nothing. Continuous reading leaves "The last two can be dropped for a short slot", passes "divider ornament, image" twice back to back, and arrives at "Press review, heading level 2". The heading and all four dates are simply not there, and that doubled ornament is the one audible clue.

    Should say: "Tour dates, heading level 2. list, 4 items. 12.09.2026, Berlin, Kellerklub Anfang, sold out."

    WCAG 1.3.1 Info and Relationships · Caught by: the ear, as silence where the eye sees content, and the tree, where the whole subtree is missing; a scanner sees nothing wrong

    Delete the attribute. aria-hidden hides from assistive technology only and leaves the pixels in place, which is the one hiding method that always desynchronises the two, craft’s hiding matrix has the other three.

  10. Barrier 10: the press review scored the EP; how well?
    Reveal the answer

    The star rating is drawn entirely in CSS, as ::after { content: "★★★★☆" } on an empty span. The element’s text content is the empty string. The browser exposes the glyphs in the accessibility tree, but they are symbols, not a number, and whether a screen reader reads them, spells them or skips them is its own setting.

    Heard: "Kellerpost, August 2025: black star black star black star black star white star". Chromium, Firefox and WebKit all fold the generated glyphs into the paragraph in the tree, so the words are there for the reader to find; what you hear then depends on the screen reader’s symbol handling, and one that skips symbols leaves the sentence ending on the colon.

    Should say: "Kellerpost, August 2025: 4 out of 5 stars." Say the rating, not the glyphs.

    WCAG 1.1.1 Non-text Content · Caught by: the ear, and inconsistently, which is the lesson; the tree shows the generated text in Chromium, and no scanner rule covers it

    Put the rating in the markup as text and let CSS draw the stars beside it. Content that carries meaning does not belong in a stylesheet.

  11. Barrier 11: pull the links list and listen for a stutter
    Reveal the answer

    Each social link holds an icon SVG carrying its own aria-label plus the visible word next to it. The name computation concatenates both, so the accessible name is the word twice over. A name exists, so link-name passes.

    Heard: "Instagram Instagram, link", then "Bandcamp Bandcamp, link", then "Spotify Spotify, link", three stutters in a row in the links list.

    Should say: "Instagram, link".

    WCAG 4.1.2 Name, Role, Value · Caught by: the links list, and the name-computation chain in the tree; a scanner checks that a name exists, not that it reads well

    The icon is decoration next to its own label: aria-hidden="true" on the SVG and let the visible word name the link, as the tree section showed.

About this page

The band is real; the press kit is not. Fitis exists, and the actual site, which is not broken on purpose, lives at fitis-band.de. The line-up, the sound, the EP and its tracks are accurate. The press quotes, the review and the outlets they are credited to, the photo captions, the venues and the tour dates are invented for the exercise, as are the barriers.

The plan this room is built around is sheet A·03, fifteen minutes with a screen reader, and the matrix this page sits in the far corner of is the proof chapter’s what automation can and can’t see. A whole page that passes the scanner column is what that section is describing.