R RenDS v0.13.0

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:

Tab
Move focus to the next interactive element.
Shift+Tab
Move focus backward.
Enter / Space
Activate buttons and toggles. Space only on button, not on links.
Esc
Close dialogs, popovers, menus, tooltips. Always a reliable escape hatch.
/
Move between options in Select, Menu, Listbox, Tabs (vertical), Radio Group.
/
Move between Tabs (horizontal), Toggle Group, Slider values.
Home / End
Jump to first / last option in lists, calendars, sliders.
Type a letter
In Select and Menu: jump to next item starting with that letter.
F6
Moves focus to the toast viewport — useful when an action toast is showing and the user wants to act on it before it auto-dismisses. A platform convention from macOS, rarely implemented elsewhere; 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 using for=/id=.
  • Live regions (toasts, notifications) need aria-live="polite" or "assertive". ren-toast handles this.
  • Nav landmarks need an aria-label when 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 dark and light-dark().
  • High-contrast mode respected through semantic tokens.
  • prefers-reduced-motion respected via duration tokens.
  • Minimum 44px touch targets on interactive elements.
  • aria-live on ren-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 or ren-sr-only).
  • Every <img> has alt="" (empty for decoration) or a meaningful alt text.
  • Every <nav> landmark has aria-label if 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