Overview
Source: ./src/documentation/development/accessibility.mdx
Accessibility
OTTO Components are designed to meet Web Content Accessibility Guidelines (WCAG), ensuring that websites and applications built with these components are usable by everyone, including people with disabilities.
Skip to:
- Accessibility in the OTTO Components Library
- Your implementation responsibilities
- Screen reader support
- Prevent split focus on mixed content with
role="text" - Prefer
aria-disabledoverdisabled - Text resizing
- Accessibility testing
Accessibility in the OTTO Components Library
All components support a base set of accessibility features, such as:
- Aligned tab and DOM order: Components ensure the tab order matches the visual DOM order for predictable keyboard and screen reader navigation.
- Screen reader support: Components have a semantic structure and ARIA attributes that ensure screen readers can interpret and announce them correctly.
- Screen readers can access all components for clear, predictable interaction.
- Keyboard navigation and focus management: Users can operate all components entirely via keyboard, with correct focus handling for seamless navigation.
- Reduced motion: Components automatically detect and respect the user's system preference for reduced motion, minimizing animations to improve comfort.
Info
See the Accessibility UX documentation for detailed accessibility guidelines.
Your implementation responsibilities
While OTTO components come with built-in accessibility features, it is your responsibility to ensure that your implementation is fully accessible.
This includes:
- Add alternative text for images
- Provide labels for form fields
- Set ARIA attributes as needed
- Use short link texts for optimal accessibility
Screen reader support
Screen readers are software applications that convert text and other content into synthesized speech or braille output, allowing users with visual impairments to access digital content.
Use srSpeak for screen reader announcements
The srSpeak function is a utility that allows you to programmatically announce content to screen readers.
You can configure the urgency of the announcement, setting it to "assertive" or "polite", which corresponds with the aria-live attribute.
Use it to announce visual changes in content that are not automatically detected by screen readers, for example the item counter in a shopping cart after a product is added to the cart.
Use oc-aria-label for screen reader context
The oc-aria-label attribute lets you provide additional context information for screen readers and their users, especially when the visible label or content is not descriptive enough or when no visible label is present.
When to use
- If the visible label or content does not fully describe the purpose or action of the component.
- If your component has no visible label but still needs to be announced by assistive technology.
- If the component has a visible label, setting
oc-aria-labelcauses screen readers to use its value instead of the visible label.
How to use
- Set
oc-aria-labelto a short, meaningful description that gives extra context for the purpose or action of the component. - Avoid repeating visible text unless it is not accessible to screen readers.
Example:
<oc-media-object-card-v1 oc-aria-label="Open product details"></oc-media-object-card-v1>
Info
See the ARIA labels UX documentation for detailed guidelines on using
oc-aria-labeleffectively.
Prefer <b> over <strong> for fluent screen reading
When you need to emphasize text for screen readers, use the <b> tag instead of <strong>.
This ensures that the emphasized text is read fluently without unnecessary pauses, providing a better experience for screen reader users.
Use <strong> only when you want to indicate strong importance or urgency, as it may cause screen readers to pause or change tone, which can disrupt the flow of information.
Prevent split focus on mixed content with role="text"
iOS VoiceOver splits a container into separate focus stops whenever it contains a mix of inline elements (e.g. <b>, <span>) and plain text nodes — even if macOS VoiceOver reads the whole thing as one unit.
Add role="text" to the parent element to tell iOS VoiceOver to treat its entire subtree as a single, unsplittable text string.
When to use
- A container mixes inline elements (
<b>,<abbr>,<span>, …) with surrounding text. - Users must swipe through the inline element as an extra, unwanted VoiceOver stop on iOS.
- macOS VoiceOver already reads the content correctly — only iOS is affected.
How to use
Add role="text" to the wrapping element. For <label>, which carries its own implicit role, wrap the content in a <span role="text"> instead.
<!-- ✅ div: role="text" directly on the container -->
<div role="text">
<b>Bold</b> Text
</div>
<!-- ✅ label: role="text" on an inner span, because <label> has its own implicit role -->
<label for="id1">
<span role="text"><b>Bold</b> Text</span>
</label>
Note
role="text"is not part of the ARIA specification. It is a iOS VoiceOver extension and is safely ignored by macOS VoiceOver, NVDA, and JAWS — making it safe to use as a targeted fix.
Prefer aria-disabled over disabled
The native disabled attribute is problematic for accessibility and should be avoided for interactive elements (buttons, form controls) in most cases:
- Removes the element from the tab order. Keyboard-only users tabbing through the page can't discover that the control exists at all.
- Removes the element from focus entirely. You can't explain why a control is unavailable via
aria-describedbyor a tooltip, since a non-focusable element never receives focus or hover-equivalent interaction from assistive technology. - Inconsistent screen reader behavior. For example, VoiceOver's swipe/virtual-cursor navigation can still land on and announce natively-disabled elements — and, due to past WebKit bugs, sometimes even interact with them — while a sighted keyboard-only user tabbing through skips them entirely. This inconsistency between input modes and browsers makes native
disabledunreliable.
Recommended pattern — aria-disabled="true"
- Keep the element focusable and in the tab order.
- Style the disabled appearance via CSS (e.g. reduced opacity,
cursor: not-allowed) instead of relying on the:disabledpseudo-class, which no longer applies. - Block the actual action yourself in the event handler —
aria-disableddoes not prevent clicks or activation natively, so the component must guard against it. - Optionally pair it with
aria-describedbyor a live region to explain why the control is currently unavailable.
<!-- ❌ Anti-pattern: native disabled -->
<!-- Not keyboard-focusable, so it cannot expose a focus-triggered explanation -->
<button disabled>Submit</button>
<!-- ✅ Recommended: aria-disabled -->
<!-- Focusable, announced as disabled, can reference an explanation -->
<button aria-disabled="true" aria-describedby="submit-hint" class="is-disabled">Submit</button>
<span id="submit-hint">Fill out all required fields to enable submission.</span>
button.addEventListener("click", (event) => {
if (button.ariaDisabled === "true") {
event.preventDefault();
return;
}
// ... perform the action
});
When native disabled is still fine
Native disabled remains appropriate when the element is genuinely irrelevant to the current step or task and doesn't need to be discoverable — for example, a "Next" button in step 1 of a wizard for a step that doesn't exist yet.
Text resizing
OTTO Components fully support text resizing and browser zoom, meeting WCAG 2.1 Success Criterion 1.4.4 (Level AA). This ensures users with low vision can enlarge text up to 200% without loss of content or functionality.
How it works
- Browser zoom: All components scale uniformly when users zoom the page using browser controls (Ctrl/Cmd + + or Ctrl/Cmd + mouse wheel).
- Text-only zoom: Components adapt correctly when users increase text size in browser settings, ensuring clarity and readability without breaking layout.
- Relative units: Components use relative sizing units (rem, em, %) rather than fixed pixels, allowing text to scale proportionally with user preferences.
Your implementation responsibilities
While OTTO Components are built to support text resizing, you should:
- Avoid fixed-width containers: Use flexible layouts that can accommodate larger text without causing overflow or clipping.
- Test at 200% zoom: Verify your implementation at 200% browser zoom to ensure all content remains accessible and functional.
- Don't disable zoom: Never use
user-scalable=noormaximum-scale=1.0in viewport meta tags, as this prevents users from zooming. - Use relative units: Follow component patterns by using relative units in your custom styles rather than fixed pixel values.
Testing text resize
To test text resizing:
- Open your page in a modern browser (Chrome, Firefox, Safari, or Edge).
- Use browser zoom controls to increase the zoom level to 200% (Ctrl/Cmd + + or Ctrl/Cmd + mouse wheel).
- Verify that all text is readable, no content is cut off, and all functionality remains available.
- Test text-only zoom if your browser supports it (Firefox: Settings > General > Zoom > Zoom text only).
Accessibility testing
To test the accessibility of a website or application, you can use Lighthouse, a tool within the Chrome DevTools that provides a comprehensive report on the accessibility of your site or app. To make a screen reader test, you can use the following screen readers:
- For macOS users, you can use the VoiceOver screen reader.
- For Linux users, you can use the Orca screen reader.
- For Windows users, you can use the NVDA screen reader or the JAWS screen reader.
- For iOS users, you can use the VoiceOver screen reader.
- For Android users, you can use the TalkBack screen reader.
