Accessibility

The five startup/CI accessibility contract checks Chirp runs, and the semantic-HTML and ARIA patterns it expects

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

Overview

Accessibility in Chirp is a contract, not a convention.app.check() scans your templates at startup and in CI, and fails loud on five common regressions: htmx handlers on non-interactive elements, unlabeled form fields, images missing alt, skipped heading levels, and layouts with no <main>landmark.

This page shows what each check catches, how to promote it to a hard build failure, and the semantic-HTML and ARIA patterns Chirp expects you to ship. If you arrived here from a contract warning, the accessibility checks below name your fix.

Accessibility contract checks

Five checks run wherever [[docs/quality/contracts-debugging/_index|app.check() validates contracts]] — at startup in debug mode, and in CI via chirp check myapp:app. The contract message names the offending template and the concrete fix. All five emit at WARNINGseverity, so they surface regressions without blocking an app mid-migration.

Category Catches Fix
a11y_interactive htmx URL attributes (hx-get/hx-post/hx-put/hx-patch/hx-delete) on non-interactive elements. Use<button> or <a>, or add role="button" tabindex="0".
a11y_label <input>/<select>/<textarea> with no associated label — no matching <label for="…">, no wrapping <label>, no aria-label/aria-labelledby. Hidden, submit, button, and reset inputs are exempt. Add<label for="id">, wrap the field in a <label>, or set aria-label.
a11y_alt <img> tags with no altattribute. Usealt="…" for meaningful images, alt=""for decorative ones.
a11y_heading Heading levels that skip — for example<h1> straight to <h3> with no <h2>— which breaks the document outline for screen readers. Use heading levels in order with no gaps.
a11y_landmark Layout templates with no<main> (or role="main") landmark. Only layouts are checked; pages inherit landmark structure from the layout. Add<main> or role="main"to the layout.

Semantic HTML

The checks reward semantic markup: elements that convey meaning give screen readers a structure to navigate, and they keepa11y_heading and a11y_landmarkquiet for free.

  • header, main, nav, footerfor page structure
  • article, sectionfor content grouping
  • h1h6for headings, in order, no skips
  • button for actions, afor navigation
  • labelfor form controls
<header>
  <nav aria-label="Main navigation">...</nav>
</header>
<main>
  <article aria-label="Question and answer">
    <h2>Question</h2>
    <p>...</p>
  </article>
</main>

ARIA for dynamic content

When content updates via htmx or SSE, ARIA announces the change to assistive technology:

  • aria-live="polite"— announces updates without interrupting
  • aria-atomic="true"— reads the entire region when it changes
  • aria-label— describes regions and controls

The RAG demo uses this pattern for the streaming answer region:

      <div sse-swap="answer" hx-target="this" aria-live="polite" aria-atomic="true">
        <span class="chirpui-text-muted">Searching docs and generating answer…</span>
        {{ typing_indicator() }}
      </div>

Source: examples/chirpui/rag_demo/templates/ask.html.

Forms

  • Associatelabel with inputs via for/id, or wrap the input
  • Usearia-describedbyfor validation messages
  • Usearia-invalidwhen a field has errors
  • Providearia-labelfor icon-only buttons
<label for="question-input">Your question</label>
<textarea id="question-input" name="question" aria-describedby="validation"></textarea>
<div id="validation" role="alert" aria-live="polite"></div>

Images and keyboard

  • Always providealt for images — empty string alt=""for decorative ones
  • Usearia-label or titlefor icon-only buttons
  • Keep every interactive element focusable and operable by keyboard
  • Use visible focus styles; never setoutline: nonewithout a replacement
  • For custom controls (such as switches), userole="switch" with aria-checked