Azalea

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.

NEEDS APPROVALDEADThe label is always shown, so the amber and the red do not have to be told apart.
The icon distinguishes a warning from a failure.

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.

Alert, tone=dangerrole="alert"Interrupts. A failure the reader must know about now.
Alert, other tonesrole="status"Waits for a pause.
FormMessagearia-live="polite"The user just clicked; they are already here. Assertive would talk over them.
Skeletonaria-hiddenThe loading state comes from the region, not from decorative grey boxes.
Avatar imagealt=""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.

Skip to content

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.

  1. Label an icon-only button. <Button size="icon"> with no aria-label is unlabelled to a screen reader — it reads as “button”.
  2. Use a link for navigation. A Button that navigates breaks middle-click, open-in-new-tab and the back button.
  3. 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.
  4. 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.