A junior ships a 20-line gallery: grid grid-cols-4 gap-2, thumbnails in a flat grid, onClick on each <div> to open a viewer. Passes Figma. Works in the reviewer's mouse. Ships. Then the bug queue lights up from four directions at once.

A code reviewer with a mouse and Chrome clicks thumb #1. Viewer opens. Clicks X. Closes. Approves the PR. Ships on Friday. Four different probes — keyboard, screen reader, layout paint, bandwidth — are each about to expose a separate wall the 20-line grid has no defenses against.

FIG. 1 — THE 20-LINE VERSION
1
// Naive gallery — the 20-line version juniors ship.
2
// Works with a mouse. Fails for keyboard, AT, bandwidth, and LCP.
3
function BrokenGallery({ images }) {
4
  const [openIdx, setOpenIdx] = useState(null);
5
6
  return (
7
    <>
8
      <div className="grid grid-cols-4 gap-2">
9
        {images.map((img, i) => (
10
          <div key={img.id} onClick={() => setOpenIdx(i)}>
11
            <img src={img.src} alt="" />
12
          </div>
13
        ))}
14
      </div>
15
      {openIdx !== null && (
16
        <div className="fixed inset-0 bg-black/90 flex items-center justify-center"
17
             onClick={() => setOpenIdx(null)}>
18
          <img src={images[openIdx].src} alt="" />
19
        </div>
20
      )}
21
    </>
22
  );
23
}
24
25
// Silent failures:
26
//   1. <div onClick> — not keyboard focusable, not a button, AT misses it
27
//   2. No Arrow navigation — open #1, want #2? Close, then click #2
28
//   3. All <img> paint at mount — 200-image gallery eats data budget
29
//   4. No width/height — browser paints each image as 0x0 until loaded,
30
//      then reflows the grid on each decoder callback. CLS disaster.
31
//   5. No focus trap in the viewer — Tab falls through to the page
32
//   6. alt="" — all images invisible to screen readers. alt is REQUIRED.
33
//   7. No aria-modal / role="dialog" on the viewer — AT walks past it
— Passes code review. Works for a mouse in Chrome. Fails silently on every other probe. —
Live gallery · naive grid, no defensestap a probe
Harbor
Dusk
Ridge
Gold
Tide
Granite
Noon
Mist
Shore
Ember
Fog
Dawn

The simulator runs the naive version. Four probe buttons on the right correspond to the four walls. Tap one; the grid below reacts. The naive <div onClick> thumbs look fine to your mouse — tap one and the viewer opens. But each probe pulls a different thread, and the whole construction unravels from a different direction every time.

Step 1 / 3 · Probe: the Tab key

Probe 1 — keyboard. A user presses Tab. Focus skips every thumbnail; the <div onClick> is not focusable. Press Tab again: focus lands on the footer link. The gallery is invisible to the keyboard.

In the simulator above, the “Tab probe” button simulates pressing Tab from the page's navigation bar down through the gallery. Try it: click the TAB button on the right. A glowing focus ring will try to land on the first thumbnail. Then click it a second time. What happens across all twelve thumbs?

The naive thumb is <div onClick={open}><img alt="" /></div>. With no tabIndex, no role, no <button> — what does Tab do when the keyboard focus reaches the gallery row?

wall
reveal
capstone
residue