Accessibility
What the components already handle, and the four things a caller still has to get right.
Colour is never the only signal
Every place the system uses colour to mean something, it pairs it with a second signal — text, an icon, or a shape.
One focus ring, everywhere
Identical across every variant on purpose: a keyboard user moving between two Trellis apps should not have to relearn where they are. Tab through these.
light
dark
Labels are bound, not adjacent
Field renders a real <label htmlFor> and wires the hint or error with aria-describedby, so a screen reader announces the problem when focus lands rather than on submit.
No such branch on origin.
Announcements are proportionate
Interrupting a screen reader is a cost, so only failures pay it.
role="alert"Interrupts. A failure the reader must know about now.role="status"Waits for a pause.aria-live="polite"The user just clicked; they are already here. Assertive would talk over them.aria-hiddenThe loading state comes from the region, not from decorative grey boxes.alt=""The name is already in the row. Announcing it twice is noise.A way past the navigation
Every app in the suite renders a rail a dozen links deep before its content, and until recently none of them had a way past it — a dozen tab presses to reach the thing you came for, on every page. AppShell now renders a SkipLink first in the tab order and gives <main> the matching id, so an app that adopts the shell gets this without knowing it exists.
Click here, then press Tab. The link is off screen until it has focus — not display: none, which would take it out of the tab order and is the usual way a skip link ends up existing and not working.
…and this is where it lands.
A value that is drawn must also be announced
Twenty progress bars existed across the suite before Progress did, and aria-valuenow appeared in zero files platform-wide. Every one of them was invisible to a screen reader, and several were the only indication on their page of how much budget was left. The visual half was never the missing part.
valueText is what a screen reader says instead of “91%” — because the percentage is rarely the fact.
What the system cannot do for you
Four things that are still the caller's job, and are the four that actually get missed.
- Label an icon-only button.
<Button size="icon">with noaria-labelis unlabelled to a screen reader — it reads as “button”. - Use a link for navigation. A Button that navigates breaks middle-click, open-in-new-tab and the back button.
- Write the error as an instruction. “No such branch on origin” beats “Invalid input” — the component styles it, only you know what would fix it.
- Keep the tab order the reading order. Reordering visually with CSS while leaving the DOM alone leaves a keyboard user jumping around the page.
Checking contrast honestly
Every example on this site renders in both themes at once, because a component can look right in the theme you happen to be in and be unreadable in the other — and toggling back and forth from memory will not catch it.
The brand colours are an identity palette, not a UI palette: none of --brand-green, --brand-teal or --brand-blue is guaranteed to carry text at an accessible contrast. Use them for a mark or an accent, and never put body copy on one without checking it.