Astro Build Standards docs

Accessibility#

[a11y]Reference

Accessibility is a build requirement, not a phase.

Every component ships with the accessibility behavior it needs — semantics, keyboard, focus and reduced motion — from the start. Prefer native HTML; add ARIA only where native semantics are insufficient. Redundant or incorrect ARIA is worse than none.

Semantic HTML first#

[a11y.semantics]Required

Use <nav>, <button>, <dialog>, <header>, <main>, <details> before <div role="…">. Add an explicit role only when no element fits.

Labels & state#

[a11y.labels]Required

  • Icon-only controls get an accessible name (aria-label or visually-hidden text). Decorative icons get aria-hidden="true".
  • Toggles set aria-expanded + aria-controls; checkable items aria-checked; tabs aria-selected.
  • Every landmark of a repeated type gets a distinguishing aria-label.
  • Meaningful images get real alt; decorative get alt="".

Keyboard support#

[a11y.keyboard]Required

Required keys are what the pattern actually requires; optional keys are worth adding but not gated.

ComponentRequiredOptional / pattern-dependent
ButtonEnter / Space (native)
Modal / DialogEsc closes; focus trapped while open; focus returns to the trigger
TabsTab reaches the tablist; Arrow keys move between tabs; roving tabindexHome/End; manual activation (Enter/Space) instead of automatic — pick one and be consistent
AccordionEnter/Space toggles each header (native <button>)Up/Down between headers, Home/End — optional in the ARIA pattern. Esc is not part of it
DisclosureEnter/Space toggles
Menu / Menu buttonArrowDown opens and focuses the first item; Esc closes and returns focusType-ahead; Home/End
ComboboxArrow keys move through options; Enter selects; Esc closes

Automatic tab activation (selection follows focus) is appropriate when showing a panel is instantaneous; manual activation is required when it isn't. Choose per project and apply consistently.

Focus management#

[a11y.focus]Required

  • Never drop focus to <body>. On close/remove, move focus to the next logical element.
  • Dialogs store document.activeElement on open and restore on close.
  • Roving tabindex: only the active item is tabindex="0".
  • Focus must not be obscured by sticky headers, banners or footers (WCAG 2.2 SC 2.4.11). Use scroll-margin-top matched to the sticky header height.

Reduced motion#

[a11y.reduced-motion]Required

Wrap every animation in @media (prefers-reduced-motion: reduce), and gate JS/GSAP animations on matchMedia("(prefers-reduced-motion: reduce)"), updating on its change event.

Measurable thresholds#

[a11y.thresholds]Required

Reviewers check numbers, not adjectives:

CheckThreshold
Text contrast4.5:1 (3:1 for ≥24px, or ≥19px bold)
Non-text contrast — control boundaries, focus indicators, meaningful graphics3:1
Target size24×24 CSS px minimum (SC 2.5.8); 44×44 preferred for primary touch targets
Reflowusable at 320px equivalent — 400% zoom at 1280px, no horizontal scroll (SC 1.4.10)
Text spacingno clipping with increased line-height/letter/word spacing (SC 1.4.12)
Forced colorsusable in forced-colors: active; don't rely on background images for meaning

Every threshold is checked in each theme the project ships.

Page-level#

[a11y.page]Default

  • Skip link to #main in Layout.astro.
  • Logical heading order, no skipped levels.
  • One h1 per page — an agency convention for clarity and SEO, not a WCAG requirement. Deviate only with a reason.
  • Visible focus everywhere.