Screen Reader Support
How Byteion components announce to screen reader users.
Overview
Screen readers convert the accessibility tree into speech or braille. Byteion components should expose meaningful names, roles, states, and relationships. Visual-only UI is not enough — state changes must be announced when needed.
Why this exists
Many people rely on screen readers, braille displays, magnification, or other assistive technologies to use the web. A modal that opens silently, an unlabeled icon button, or an unannounced form error can make a product workflow impossible to complete. Byteion components aim to announce enough information for a screen-reader user to understand and operate the interface.
Core rule: native HTML first
- Use
<button>for actions,<a>for navigation,<label>for form fields,<fieldset>and<legend>for grouped controls - Add ARIA only when native semantics do not fully describe the UI
- Do not use ARIA to fake behaviour that the component does not actually support
Core principles
- Semantic structure — Headings, landmarks, lists, buttons, links, and form labels create a navigable page
- Accessible names — Every control needs a clear name from visible text,
<label>,aria-label, oraria-labelledby - State announcements — Expanded, selected, checked, disabled, invalid, and busy states should be exposed
- Relationships — Connect triggers with panels via
aria-controls, dialogs witharia-labelledby, fields witharia-describedby - Live regions — Announce dynamic updates only when users need to hear them
- Focus management — Move focus into newly opened UI and restore focus when it closes
Byteion component patterns
Buttons and icon buttons
Text buttons usually get their accessible name from visible text. Icon-only buttons need aria-label or sr-only text:
<!-- aria-label approach -->
<button type="button" aria-label="Open user menu"
class="rounded-button bg-background px-2 py-2 text-foreground
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20">
<i class="ti ti-dots-vertical" aria-hidden="true"></i>
</button>
<!-- sr-only approach -->
<button type="button"
class="rounded-button bg-background px-2 py-2 text-foreground
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20">
<span class="sr-only">Open user menu</span>
<i class="ti ti-dots-vertical" aria-hidden="true"></i>
</button>
Forms and validation
Use <label> for every input. Use aria-invalid="true" only when the field has an error. Use aria-describedby to connect help text or error messages:
<label for="email" class="text-sm font-medium text-foreground">Email</label>
<input type="email" id="email" aria-invalid="true"
aria-describedby="email-error"
class="w-full rounded-lg border border-danger bg-background px-3 py-2 text-sm
focus:outline-none focus:ring-2 focus:ring-danger/20">
<p id="email-error" class="mt-1 text-sm text-danger">
Enter a valid email address.</p>
Modal dialogs
Use role="dialog" or native <dialog> where supported. Add aria-modal="true" and aria-labelledby pointing to the modal title. Move focus into the modal when opened, trap focus while open, and return focus to the trigger when closed:
<div data-bi-modal="settings" role="dialog" aria-modal="true"
aria-labelledby="settings-title"
class="rounded-xl bg-card p-6 shadow-panel border border-border">
<h2 id="settings-title" class="text-lg font-semibold text-foreground">Settings</h2>
<p class="text-sm text-muted-foreground">Configure your preferences.</p>
</div>
Dropdowns
The trigger should expose expanded state. Use aria-expanded and aria-controls when the menu panel has an ID. Menu items must be keyboard reachable:
<button type="button" aria-expanded="false" aria-controls="user-menu"
class="rounded-button bg-background px-4 py-2 text-sm text-foreground
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20">
User menu
</button>
<div id="user-menu" hidden
class="rounded-lg border border-border bg-card p-2 shadow-dropdown">
<a href="#" class="block px-3 py-2 text-sm text-foreground">Profile</a>
<a href="#" class="block px-3 py-2 text-sm text-foreground">Log out</a>
</div>
Tabs
Use role="tablist", role="tab", and role="tabpanel" for custom tab implementations. Use aria-selected for the active tab and support arrow-key navigation:
<div role="tablist" aria-label="Billing sections"
class="flex gap-2 border-b border-border">
<button role="tab" aria-selected="true" aria-controls="invoices-panel"
class="rounded-button px-4 py-2 text-sm font-medium text-foreground
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20">
Invoices</button>
<button role="tab" aria-selected="false" aria-controls="payments-panel"
class="rounded-button px-4 py-2 text-sm font-medium text-muted-foreground
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20">
Payments</button>
</div>
<div role="tabpanel" id="invoices-panel" class="mt-4">
<p class="text-sm text-muted-foreground">Invoice history.</p>
</div>
<div role="tabpanel" id="payments-panel" hidden class="mt-4">
<p class="text-sm text-muted-foreground">Payment methods.</p>
</div>
Toasts and alerts
Use aria-live="polite" for normal updates and aria-live="assertive" only for urgent errors. Avoid repeating the same announcement:
<div id="toast-announcements" class="sr-only" aria-live="polite" aria-atomic="true"></div>
Tooltips
Tooltips should supplement, not replace, labels. Use aria-describedby to connect the trigger with tooltip content. Do not put critical information only inside a hover-only tooltip.
Practical examples
<!-- Toast announcement -->
<div aria-live="polite" class="sr-only" id="toast-announcements">
<!-- Toast text inserted here dynamically -->
</div>
<!-- Visually hidden but screen-reader visible -->
<button type="button" aria-label="Open user menu"
class="rounded-button bg-background px-2 py-2">
<i class="ti ti-dots-vertical" aria-hidden="true"></i>
</button>
Best practices
- Prefer visible text labels when possible
- Use
aria-labelonly when visible text is not available - Use
aria-live="polite"for non-urgent updates - Use
aria-live="assertive"only for critical errors - Hide decorative icons with
aria-hidden="true" - Keep headings in logical order
- Test dynamic components with keyboard and at least one screen reader
Common mistakes
Don't
- Use
display: noneon text that should be available to screen readers - Add
aria-liveto content that updates every second - Use
aria-labelthat conflicts with visible text - Hide focusable elements with
aria-hidden="true" - Put critical instructions only in placeholder text
- Use
<div onclick>instead of<button>
Do
- Use
.sr-onlyfor supplementary screen-reader text - Connect errors with
aria-describedby - Update
aria-expandedwhen dropdowns open or close - Use
aria-hidden="true"for decorative icons - Restore focus after closing modals
Testing checklist
- Can every control be reached with keyboard?
- Does each control have a useful accessible name?
- Does the screen reader announce role and state?
- Are expanded, selected, checked, disabled, invalid, and busy states announced?
- When a modal opens, does focus move inside it?
- When a modal closes, does focus return to the trigger?
- Are form errors connected to fields?
- Are live announcements useful and not repeated?
- Are decorative icons hidden from assistive technology?
- Does the page still make sense without visual layout?
Developer tips
Cmd+F5 to enable. Navigate with VO+Arrow keys.
• NVDA on Windows is a free screen reader for practical testing.
• TalkBack on Android for mobile screen-reader testing.
• Chrome DevTools Accessibility pane shows the computed accessibility tree and accessible names.
• Byteion toasts announce through aria-live="polite" by default.
• Test with a real screen reader — automated tools cannot fully validate whether announcements make sense.Related
- Accessibility Overview — Overall accessibility contract
- Keyboard Navigation — Keyboard interaction patterns
- Focus Management — Focus trap and restoration
- ARIA Patterns — ARIA roles, states, and properties
- Modal / Dialog — Dialog semantics and focus
- Dropdown / Menu — Dropdown ARIA patterns
- Toast / Alert — Live region behaviour
- Tooltip — Tooltip description patterns
Unlock Premium Patterns
Get access to copy-paste ready Pro blocks, SaaS templates, and advanced component patterns.
Explore Pro Components