Guides
Accessibility
Accessibility isn't a feature in RenDS — it's one of the three pillars. Every component ships keyboard-navigable, screen-reader-friendly, color-contrast-safe, and motion-sensitive. This page explains what's automatic, what's your job, and how to tell the difference.
Standards
The baseline
RenDS targets WCAG 2.1 Level AA as the minimum and Level AAA where the design allows it. In practice that means:
- All non-text UI hits 3:1 contrast against the adjacent color.
- All body text hits 4.5:1 contrast. Large text (18pt+) hits 3:1.
- Every interactive element is reachable and operable by keyboard alone.
- Every interactive element has an accessible name (visible label or
aria-label). - Touch targets are at least 44×44px (Apple HIG).
- No information is conveyed by color alone.
You inherit the baseline for free as long as you use RenDS components with the recommended markup. The rules below tell you where to pay attention so you don't lose it.
Input
Keyboard navigation
Every interactive component ships with keyboard support that follows WAI-ARIA Authoring Practices. The common keys:
Space only on button, not on links.Select, Menu, Listbox, Tabs (vertical), Radio Group.Tabs (horizontal), Toggle Group, Slider values.Select and Menu: jump to next item starting with that letter.ren-toast ships it by default.Featured pattern — F6 to reach the toast viewport. Keyboard-only users frequently miss action toasts ("Undo") because they appear without focus and disappear without warning. ren-toast wires a global F6 handler that moves focus to the viewport so the user can Tab to the action button. It's a small detail that lifts RenDS above WCAG 2.1 AA — most DSs don't bother. See ren-toast accessibility.
Test yourself
Before shipping a screen: put your mouse down and try to complete every user-facing task with only the keyboard. If you get stuck or lose your focus indicator, something is broken.
Signals
Focus management
RenDS uses :focus-visible — not :focus — so keyboard users see a ring, but mouse-clickers don't get a distracting one after every click. The ring color is --color-focus-ring, which defaults to the accent color.
Focus trapping
Modal components (ren-dialog, ren-alert-dialog, ren-sheet) trap focus when open. Tab cycles inside the modal; Esc closes it and returns focus to the trigger that opened it. You don't configure this — it just works.
Initial focus
When a dialog opens, focus lands on the first focusable element inside. Override with autofocus if the first element isn't the right target:
<dialog class="ren-dialog">
<h2>Delete account?</h2>
<p>This action cannot be undone.</p>
<button class="ren-btn ren-btn-secondary">Cancel</button>
<button class="ren-btn ren-btn-danger" autofocus>Delete</button>
</dialog>Never hide the focus ring
If a focus ring looks wrong on your design, adjust the color, not the visibility.
Do
/* Change the ring, keep it visible */
:root {
--color-focus-ring: hotpink;
}
Don't
/* Hides focus for keyboard users */
button:focus,
button:focus-visible {
outline: none;
}
Markup
ARIA and semantics
The first rule of ARIA is: don't use ARIA. Use the right HTML element first — <button>, <nav>, <dialog>, <details>. RenDS is built around native elements, so most components don't need explicit roles.
When you do need ARIA
- Icon-only buttons need
aria-label. No exceptions. - Form fields need an associated
<label>— either wrapping the input or usingfor=/id=. - Live regions (toasts, notifications) need
aria-live="polite"or"assertive".ren-toasthandles this. - Nav landmarks need an
aria-labelwhen there's more than one<nav>on the page. - Current page in navigation uses
aria-current="page".
Do
<button class="ren-btn" aria-label="Close dialog"> <svg aria-hidden="true">...</svg> </button>
Don't
<button class="ren-btn"> <svg>...</svg> </button> <!-- no accessible name -->
Size
Touch targets
Interactive elements are at least 44×44px (Apple HIG standard). RenDS exposes this as a token: --touch-min. Every button, link, and tappable element hits this minimum by default.
If you build your own interactive element, use the token:
.my-custom-action {
min-height: var(--touch-min);
min-width: var(--touch-min);
display: flex;
align-items: center;
justify-content: center;
}On dense data (like a spreadsheet), smaller targets are acceptable if users are primarily on a keyboard/mouse. Don't shrink targets below 32px even then.
Color
Color contrast
Every semantic color pairing in RenDS has been checked against WCAG. The key combinations:
text on surface
The quick brown fox jumps over the lazy dog.
text-muted on surface
Supporting caption or helper text.
on-accent on accent
Primary button label.
on-danger on danger
Destructive action label.
Never rely on color alone
About 1 in 12 men and 1 in 200 women have some form of color-vision deficiency. If the only difference between two states is hue, your UI is broken for them.
Do
<span class="ren-badge ren-badge-danger"> <svg aria-hidden="true">...</svg> 3 errors </span>
Don't
<!-- Only color conveys "error" --> <span class="ren-badge ren-badge-danger"> 3 </span>
Motion
Reduced motion
Some users experience vestibular disorders triggered by parallax, scaling, or heavy animation. Every animation in RenDS uses --duration-* tokens which collapse to 0ms when prefers-reduced-motion: reduce is set.
Use the tokens. Don't hardcode durations:
Do
.my-thing {
transition:
transform var(--duration-fast)
var(--easing-standard);
}
Don't
.my-thing {
/* Ignores user preference */
transition: transform 200ms ease;
}
For essential feedback that shouldn't disappear in reduced-motion (like a loading spinner), base/motion-alternatives.css swaps rotation for a subtle pulse so the feedback is preserved without spinning.
You get these for free
What RenDS handles automatically
Automatic behavior
- Focus rings on
:focus-visible(keyboard), not:focus(mouse). - Focus trapping inside
ren-dialog,ren-alert-dialog,ren-sheet. - Focus return to the trigger when a modal closes.
- Esc closes every overlay component.
- Arrow-key navigation inside
ren-select,ren-menu,ren-tabs,ren-radio-group,ren-slider,ren-calendar. - Dark mode via
color-scheme: light darkandlight-dark(). - High-contrast mode respected through semantic tokens.
prefers-reduced-motionrespected via duration tokens.- Minimum 44px touch targets on interactive elements.
aria-liveonren-toast.- Proper roles on
ren-tabs,ren-menu,ren-breadcrumb.
You have to…
Your checklist
Before shipping a screen
- Every icon-only button has an
aria-label. - Every form input has a
<label>(visible orren-sr-only). - Every
<img>hasalt=""(empty for decoration) or a meaningful alt text. - Every
<nav>landmark hasaria-labelif there are multiple on the page. - The current page in nav uses
aria-current="page". - You can tab through the whole screen without the focus ring disappearing.
- You can complete every task with keyboard only.
- Form errors are announced, not just color-coded.
- Headings go in order (h1 → h2 → h3, no jumps).
- Reading order with CSS turned off still makes sense.
Pitfalls
Common mistakes
Using <div onclick=> instead of <button>
A div with a click handler is invisible to keyboards and screen readers. Use a button. If you need it to look like a link, style it as one — the semantics and the visual are independent.
Removing the focus outline "because it's ugly"
You're hiding critical feedback from everyone who navigates with a keyboard. Change the color or style the ring, don't remove it.
Setting tabindex="-1" on something users should reach
tabindex="-1" means "don't reach this with Tab". Use it for focus-trap anchors or scripted focus, not to "tidy up" the tab order.
Using placeholder as a label
Placeholders disappear when the user types, which drops context and fails for screen readers and low vision users. Always use a real <label>. The placeholder is for examples of input format.
Color-only error indication
A red border alone doesn't say "error". Add an icon, an inline message, or a status word. Screen readers should hear the error; low-contrast-vision users should see it.
Forgetting aria-hidden="true" on decorative icons
If the icon is decoration and the button already has a text label, mark the icon aria-hidden="true". Otherwise screen readers read the icon name twice.
Resources
Further reading
- WCAG 2.1 Quick Reference — the official standard.
- WAI-ARIA Authoring Practices — keyboard patterns for every common widget.
- Inclusive Components by Heydon Pickering — the patterns behind many RenDS components.
- WebAIM Contrast Guide — why 4.5:1 and what it means.