P-04 · Primitives · Primitives
All elements · Surface on the Primitives page
Last checked 2026-10-22 · Awaiting approval · Accessibility tests
Approve Surface
Approving as
A bounded visual container: background, border, radius, elevation and the shared hover/selected pattern that cards, list rows and kanban cards all use.
| Axis | Values |
|---|---|
data-tone |
(paper) · raised · sunken · primary · info · success · warning · error · inverse |
data-radius |
none · sm · md · lg · xl |
data-elevation |
xs · sm · md · lg · xl · 2xl |
data-bordered · data-interactive |
1px token border / hover + selected + focus states |
aria-selected · data-state |
true / hover · selected |
Guidance
When to use
- Give a pane, panel or region its own background, border and radius.
-
Raise a floating panel with
data-elevation, or recess a well withdata-tone="sunken". - Tint a region with a status tone, such as a success or error summary block.
-
Share the hover and selected look across cards, list rows and kanban cards with
data-interactive.
When to consider something else
- When you only need spacing and alignment, use Box; Surface is for visible containers. See Box.
- When the container summarizes one record with a header and actions, use Card. See Card.
- When the tinted block is a message the user must notice, use Banner, which has an icon and role. See Banner.
- When the whole container is one action, build it on Pressable or Button rather than a clickable Surface. See Pressable.
Usability
- Use elevation sparingly: panes are flat, floating layers are raised.
- Use
data-borderedwhen a surface sits on the same tone as its parent. -
Reflect selection with
aria-selectedoraria-pressedso the look and the state come from one attribute. - Keep status tones for content that is actually about that status.
- Don't add
data-interactiveto a surface that does nothing on click. - Don't nest raised surfaces inside raised surfaces to build hierarchy.
-
Don't use
data-state="hover"or"selected"in the product; they exist to show states in the catalog.
Accessibility
Keyboard
| Keys | Action |
|---|---|
| Tab |
Moves focus to an interactive surface when it is focusable (a button,
a or tabindex="0").
|
| Enter | The binding must activate an interactive surface that is not a native button or link. |
| Space | The binding must activate an interactive surface that acts as a button or selectable option. |
Roles and names
-
A plain surface adds no role. A named region needs
role="region"or a landmark element witharia-label. -
An interactive surface needs a role that matches what it does: a native
buttonora, orrole="option"orrowinside a listbox or grid. -
Selection is
aria-selected="true"(option, row, tab) oraria-pressed="true"(toggle button).
Focus
-
data-interactivesurfaces show--qf-focus-ringon:focus-visible(2.4.7). - A focused surface must not be hidden behind sticky headers or other layers (2.4.11).
Other requirements
- Selected and hover states must not rely on background color alone where that change is the only cue; the selected border helps meet 3:1 (1.4.11).
- Text on status tones and on
inverseneeds 4.5:1 (1.4.3) in both themes. - Borders and edges that identify a control need 3:1 against adjacent colors (1.4.11).
Test your implementation too. This guidance covers the element as specified here. How a screen composes it (labels, order, announcements) still needs its own keyboard and screen reader check.
Accessibility tests
Passed the gate (run 358b316 on 2026-10-22): no finding in any theme. Awaiting a person’s approval of the checklist below.
| Area | Automated | A person checks |
|---|---|---|
| General | Passed | Color is never the only cue, text alternatives say the same thing as the graphic, and the element reads the same in the light and the dark theme. |
| Zoom | Passed | At 200% zoom, at 320 CSS pixels wide and with the WCAG text-spacing overrides, nothing is lost, clipped or overlapping. |
| Keyboard | Passed | Every row of the Keyboard table works, focus is always visible and never trapped, and the tab order follows the reading order. (3 rows) |
| Screen reader | Passed | With NVDA or JAWS on Windows and VoiceOver on macOS, names, roles and states are announced as Roles and names says, and changes are announced once. (3 items) |
Approving. When the checklist passes on the checked run, record it with
bun run a11y:approve -- surface --by "Your name" and commit
a11y/approvals.json with the rebuilt pages.
All test status
Package
Consumers link one stylesheet, css/classic/index.css, which includes every element. The
files below are where this element is defined.
Latest updates
No recorded changes yet.