# Accessibility

How Tempo components meet WCAG 2.2 AA, and what your product still has to provide.

## Overview

Tempo products target WCAG 2.2 level AA on Web and follow the platform accessibility guidelines on iOS and Android. Accessibility is part of the definition of done for every screen, not a later pass.

Components render native elements and build coordinated behavior on Base UI primitives, so keyboard support and ARIA wiring come with them. Names, structure, content, and testing stay with you.

## Shared Responsibilities

| Components handle                                                                | You provide                                                |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Native elements and roles, such as `<button>`, `<input>`, and `<table>`          | Page structure: one `h1`, headings in order, and landmarks |
| Keyboard behavior for menus, listboxes, tabs, radio groups, sliders, and dialogs | A DOM order that matches the visual order                  |
| A visible focus outline on every control                                         | Styles that keep it visible                                |
| Label, description, and error wiring inside `Field`                              | The label, description, and error text                     |
| Live announcements in `Callout`, `Toast`, and `CopyButton`                       | Messages that state the outcome, such as Payment sent      |
| Reduced motion and forced colors support                                         | The same support in your own styles                        |
| Buttons, checkboxes, radios, switches, and sliders at 24px or larger             | Spacing around any smaller target                          |

## Keyboard and Focus

Every action must work from a keyboard alone. Press Tab through the example to see each control's focus outline.

```tsx
import { Button, Checkbox, Field, Input, style } from '@tempoxyz/ds'

export function Demo() {
  return (
    <div {...styles.stack()}>
      <Field.Root>
        <Field.Label>Recipient</Field.Label>
        <Input.Group>
          <Input.Control placeholder="0x…" />
        </Input.Group>
      </Field.Root>
      <Field.Root>
        <Field.Item>
          <Checkbox />
          <Field.Label>Save as contact</Field.Label>
        </Field.Item>
      </Field.Root>
      <div {...styles.row()}>
        <Button variant="secondary">Cancel</Button>
        <Button>Continue</Button>
      </div>
    </div>
  )
}

namespace styles {
  export const row = style({ display: 'flex', gap: '3', justifyContent: 'flex-end' })

  export const stack = style({
    display: 'grid',
    gap: '4',
    maxWidth: '20rem !custom',
    width: '100% !custom',
  })
}
```

[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Foundations/FocusVisible.tsx)

* Tab moves through controls in DOM order. Keep that order equal to the visual order, and never set a positive `tabIndex`.
* Controls draw a 2px `blue.700` outline, offset 2px, on `:focus-visible`. Invalid fields switch it to `red.800`.
* `Menu`, `Select`, and `Combobox` move with arrow keys, select with Enter, and close with Escape. `Menu` and `Select` also jump to an item as you type.
* `Tabs` move focus with arrow keys and wrap at the ends. `RadioGroup` is one Tab stop, and arrow keys move focus and selection together.
* `Slider` steps with arrow keys, jumps by `largeStep` with Page Up and Page Down, and reaches `min` and `max` with Home and End.
* `Dialog`, `AlertDialog`, and `Drawer` trap focus, lock page scroll, close on Escape, and return focus to the trigger. `Popover` and `Menu` return focus too.
* `Toast.Viewport` is a landmark that F6 focuses. Focus or hover inside it pauses the dismiss timers.
* Add a skip link to `AppShell.Main` when the sidebar holds many links.

Single-character shortcuts need a way to turn them off or remap them. Show shortcuts with `Kbd`, which reads modifiers by name, such as Command K.

## Target Sizes

WCAG 2.2 sets a 24 by 24px minimum for pointer targets on Web. These controls meet or exceed it.

| Control                     | Target                                                    |
| --------------------------- | --------------------------------------------------------- |
| `Button`                    | 32px `small`, 40px `medium`, 56px `large`                 |
| `Checkbox` and `RadioGroup` | A hit area of at least 36px around a 16 to 24px indicator |
| `Switch`                    | 24px tall at `small`                                      |
| `Slider`                    | A 24px-tall interactive track                             |
| `Hex` copy button           | 24px around a 16px icon                                   |

When you build a smaller target, such as an icon inside a table row, either grow its hit area to 24px or keep a 24px circle centered on it clear of every other target. On mobile, the minimum is 44 by 44 points.

## Labels and Announcements

Every control needs an accessible name that matches its visible label. Screen reader and voice control users find controls by those words.

**Do:** Give every field a visible `Field.Label`, with hints in `Field.Description`.

**Don't:** Use a placeholder as the only label. It disappears as you type and sits below 4.5:1
contrast.

* **Fields.** `Field.Label` links to its control. `Field.Description` and a shown `Field.Error` join the control's `aria-describedby`, so errors read with the field.
* **Icon-only buttons.** Set `aria-label` on the `Button`, naming the action, such as Copy address. Icons are hidden from assistive technology unless labelled.
* **Groups.** Name `Tabs.List`, `RadioGroup`, and `AppShell.Sidebar` with `aria-label`, or use a `Fieldset` legend that asks one question.
* **Status.** `Callout` renders `role="alert"` for `negative` and `warning` and `role="status"` for `info` and `positive`. `Banner` has no role, so add one only when it appears after the page loads.
* **Toasts.** The viewport is a live region. Reserve `priority: 'high'` for urgent toasts, which announce assertively.
* **Loading.** `Button` with `loading` sets `aria-busy`. Give a standalone `Spinner` an `aria-label`, such as Loading transactions.
* **Values.** `Hex` gives screen readers the full value behind a truncated one. `Amount` with `strikethrough` isn't announced, so say in text that an amount is superseded.
* **Tooltips.** `Tooltip` wires no ARIA to its trigger. Name the trigger itself, and keep essential content out of tooltips.

## Color and Contrast

* Keep text at 4.5:1 against its background, or 3:1 at 24px and larger. Use `foreground` or `gray.900` for any text you need to read.
* Keep focus outlines, state borders, and meaningful icons at 3:1. `blue.700` and `red.800` meet it on both backgrounds.
* Never use color alone. Pair each status color with text or an icon.

See [Color](/docs/foundations/color) for measured contrast pairs.

## User Preferences

* **Reduced motion.** Every component that moves or scales checks `prefers-reduced-motion: reduce`. Movement and scaling stop, and color changes and short fades remain. See [Motion](/docs/foundations/motion).
* **Forced colors.** In Windows contrast themes, `Button`, `Input`, `Textarea`, `NumberField`, `DatePicker`, `Combobox`, and `Autocomplete` drop their resting outline and draw a solid outline in a system color on focus. Icons fill with `currentColor`, so they follow the system text color.
* **Color scheme.** Tokens follow `color-scheme`, so set `light dark` on the root to respect the system setting.
* **Zoom and reflow.** Content must reflow at 320 CSS pixels wide, equal to 400% zoom on a 1280px window, without horizontal scrolling. `Grid` with `fit`, `Inline` with `wrap`, and `Container` gutters reflow by default.
* **Text size.** Spacing uses rem, so it grows with a larger default font. Avoid fixed heights on anything that holds text, and test at 200% zoom.

## Checklist

Run through this list before you ship a screen.

**Structure**

* The page has one `h1`, and headings follow in order.
* Regions use landmarks, such as `main` and `nav`, through `render` on layout primitives.
* The DOM order matches the visual order.

**Keyboard**

* Every action works with Tab, Enter, Space, Escape, and arrow keys.
* Focus is always visible and never trapped outside a dialog.
* Closing a popup returns focus to the control that opened it.

**Names and Content**

* Every field has a visible label, and every icon-only button has an `aria-label`.
* Errors say what went wrong and how to fix it, such as Enter a valid 0x address.
* Links and buttons make sense out of context, such as View transaction rather than Click here.
* Status messages announce without moving focus.

**Visual**

* Text meets 4.5:1, and focus outlines and state borders meet 3:1, in light and dark.
* No information depends on color alone.
* The screen works at 320 CSS pixels wide and at 200% zoom.
* Targets are 24px or larger on Web and 44 points or larger on mobile.

**Motion and Time**

* Motion respects reduced-motion settings, and nothing flashes more than three times a second.
* Toasts that hold an action stay until dismissed, or the action is available elsewhere.
* Sessions warn before they expire, such as before a signing request times out.

**Testing**

* Test with a screen reader: VoiceOver with Safari on macOS and iOS, and NVDA with Firefox or Chrome on Windows, plus TalkBack on Android.
* Run an automated checker, then confirm each result by hand, since automated tools find only part of the issues.

## Related

* [Color](/docs/foundations/color) for contrast and color use
* [Motion](/docs/foundations/motion) for reduced motion
* [Forms](/docs/patterns/forms) for labels and grouping
* [Errors and Validation](/docs/patterns/errors-and-validation) for writing and announcing errors
* [Button](/docs/components/button) for icon buttons and loading states
