Screen Reader

스크린 리더

Assistive technology that navigates the web by voice — it walks the accessibility tree in DOM order, announcing each element's role and name.

···
html
<div class="stage">
  <div class="pane page">
    <div class="tag">page</div>
    <div class="el h1" id="e0">Dashboard</div>
    <div class="el img" id="e1"></div>
    <div class="el p" id="e2"></div>
    <div class="el btn" id="e3">Export</div>
    <div class="cursor" id="cursor"></div>
  </div>
  <div class="pane sr">
    <div class="tag">SR</div>
    <div class="log" id="log"></div>
  </div>
</div>
css
.stage{width:94%;height:88%;display:flex;gap:4%}
.pane{position:relative;flex:1;border-radius:14px;border:1px solid var(--line);background:var(--surface);overflow:hidden}
.tag{position:absolute;top:8px;left:10px;font:700 10px/1 ui-monospace,monospace;color:var(--muted);z-index:2}
.page{padding:20% 9% 9%;display:flex;flex-direction:column;gap:7%}
.h1{font:800 clamp(11px,3vmin,15px)/1 ui-monospace,monospace;color:var(--fg)}
.img{height:24%;border-radius:6px;background:linear-gradient(135deg,var(--accent),var(--accent-3));opacity:.55}
.p{height:16%;border-radius:6px;background:var(--muted);opacity:.25}
.btn{align-self:flex-start;padding:6px 14px;border-radius:6px;background:var(--accent);color:#fff;font:700 10px/1 ui-monospace,monospace}
.cursor{position:absolute;border:2px dashed var(--accent-2);border-radius:6px;transition:all .4s ease;pointer-events:none;opacity:0}
.log{position:absolute;inset:26px 10px 10px;display:flex;flex-direction:column-reverse;gap:6px;font:10px/1.3 ui-monospace,monospace;color:var(--accent-3);overflow:hidden}
.log div{opacity:0;transform:translateY(4px);transition:opacity .3s ease,transform .3s ease}
.log div.show{opacity:1;transform:translateY(0)}
js
const page = document.querySelector('.page');
const cursor = document.getElementById('cursor');
const log = document.getElementById('log');
const items = [
  { el: document.getElementById('e0'), text: 'heading level 1, Dashboard' },
  { el: document.getElementById('e1'), text: 'image, revenue chart' },
  { el: document.getElementById('e2'), text: 'text, monthly summary paragraph' },
  { el: document.getElementById('e3'), text: 'button, Export' },
];
function addLine(text) {
  const line = document.createElement('div');
  line.textContent = text;
  log.prepend(line);
  requestAnimationFrame(function () { line.classList.add('show'); });
  while (log.children.length > 4) log.removeChild(log.lastChild);
}
function moveTo(el) {
  const pageBox = page.getBoundingClientRect();
  const box = el.getBoundingClientRect();
  cursor.style.opacity = '1';
  cursor.style.left = (box.left - pageBox.left - 4) + 'px';
  cursor.style.top = (box.top - pageBox.top - 4) + 'px';
  cursor.style.width = (box.width + 8) + 'px';
  cursor.style.height = (box.height + 8) + 'px';
}
let i = 0;
function tick() {
  const cur = items[i % items.length];
  moveTo(cur.el);
  addLine(cur.text);
  i++;
}
tick();
setInterval(tick, 1400);

Screen readers like VoiceOver, NVDA, JAWS, and TalkBack don't read pixels — they read the accessibility tree the browser builds from the DOM. A virtual cursor moves one element at a time, announcing its role (heading, button, image...), accessible name, and state (selected, expanded...) in sequence.

That order follows **DOM order**, not visual position. Reordering things visually with CSS (`order`, `position: absolute`) doesn't change what a screen reader reads next — when the two diverge a lot, the experience gets confusing fast.

The demo moves a virtual cursor across a mini page — heading, image, paragraph, button — while the transcript on the right fills in with each announcement as it happens.

When to use

When laying out a page, avoid making visual order diverge sharply from DOM order. The most reliable check is turning on a real screen reader (VoiceOver on Mac: Cmd+F5) and reading the page with nothing but Tab.