Floating Action Button
A circular FAB in the corner opens the disclosure panel. The button ships with the default layout, so every page on the site exposes the same controls without per-page wiring.
Custom Trigger
Wire a footer link, header menu item, or in-page CTA to open the same panel by calling NDS.Accessibility.open() from your own click handler. The data-accessibility-toggle attribute is only honored on the first match in the DOM, so use the JS API for every additional trigger.
<a href="#" class="nds-link" id="open-a11y">Accessibility settings</a>
document.getElementById('open-a11y').addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation(); // Panel's outside-click-close listener also lives on document.
NDS.Accessibility.open();
});
FAB Position
Anchor the FAB and the panel to whichever edge suits the layout, so neither collides with chat widgets, cookie banners, or content already pinned to a corner. The two attributes are set separately and should name the same side.
| Attribute | Set on | Behavior |
|---|---|---|
data-fab-pos="auto" | FAB | What the panel ships. Follows the panel it names in aria-controls, so changing the side below moves both. Pin an edge instead with end or start (logical), or physical left, right, bottom. |
data-panel-side="end" | Panel | Which edge the panel slides from. Same logical and physical vocabulary as the FAB, plus top. Set it to match the FAB so the pair reads as one control. |
Accessibility Modes
Bundle switches in the first accordion section. Each one is a recipe of primitives plus an optional opening typography stance, so a single tap covers the most common access needs without forcing visitors to compose the controls themselves.
| Bundle | WCAG | What it activates |
|---|---|---|
epilepsy-safe | 2.3.1 | Pauses motion and drops saturation to half so flashing media and oversaturated palettes cannot trigger seizures. |
visually-impaired | 1.4.6 | Forces a high-contrast palette swap and bumps font sizing to 1.30×, the AAA contrast plus large-text combination. |
cognitive-disability | 2.2, 2.3 | Highlights every heading and pauses motion so the page reads as a clear, static outline. |
motor-impaired | 2.5.5 | Promotes every button, link, and form control to a 48×48px hit-target. |
colorblind | 1.4.1 | Applies a deuteranopia simulation filter for designer empathy QA. Color-deficient users themselves should rely on OS-level daltonization. |
dyslexia-friendly | 1.4.8, 1.4.12 | Swaps the font stack to OpenDyslexic plus Maqroo, underlines every link, and applies the WCAG 1.4.8 spacing recipe (1.6 line-height, 0.12em letter-spacing in non-cursive scripts, 0.16em word-spacing). |
adhd-friendly | 2.2, 2.3 | Pauses motion, highlights titles, and turns on the reading mask so the visitor controls focus one paragraph at a time. |
Readable Experience
Tile grid in the second accordion section. Each tile is an independent control: cycling tiles step through 4 levels (default plus 3) and toggle tiles flip on or off. Every change writes a token onto <html data-a11y> so author CSS can react.
| Tile | Mechanism | Effect |
|---|---|---|
| Font Sizing | Cycle: 0, 1, 2, 3 | Scales every --typo-* token by 1.15×, 1.30×, or 1.50× via --user-font-scale. Stamps font-step-N. |
| Dyslexia Friendly | Toggle | Swaps --nds-font-family to the OpenDyslexic plus Maqroo stack and bumps body-copy weight. |
| Highlight Titles | Toggle | Outlines every h1 through h6 with the warning palette so the document outline becomes scannable. |
| Highlight Links | Toggle | Adds a dashed warning outline plus underline to every text link, leaving button-styled anchors untouched. |
| Reading Mask | Toggle | Activates the draggable focus band described in the next section. |
| Pause Motion | Toggle | Mutes animations and transitions site-wide and pauses every autoplay media element on activation. |
| Text Alignment | Cycle: default, end, start, justify | Forces alignment on body content via logical start and end so RTL and LTR both read naturally. |
| Line Height | Cycle: normal, 1.6, 1.8, 2.0 | Sets --user-line-height on body copy. Headings keep their typographic ratios. |
| Letter Spacing | Cycle: 0, 0.04em, 0.08em, 0.12em | Sets --user-letter-spacing. Tile is hidden on Arabic pages because letter-spacing breaks cursive ligatures. |
| Word Spacing | Cycle: 0, 0.16em, 0.32em, 0.48em | Sets --user-word-spacing on body copy. |
Visual Filters
Tile grid in the third accordion section. Filters are mutex: turning one on automatically mutes the others, since composing CSS filters multiplicatively never produces what visitors expect. Every filter is wrapped in @media not (forced-colors: active) so Windows High Contrast users are not double-treated.
| Filter | CSS | Use case |
|---|---|---|
| Boost Contrast | contrast(1.15) | Sharpens edges across the whole page without swapping the palette. |
| Monochrome | grayscale(1) | Removes color cues so the visitor can verify the page still works without them. |
| High Contrast | Token-level palette swap | Replaces every semantic background, text, link, and border token with a black-on-white (or inverted) palette. Independent of the filter pipeline so chrome contrast can be tuned per theme. |
| High Saturation | saturate(2) | Doubles vividness for visitors who perceive low-chroma palettes as washed out. |
| Low Saturation | saturate(0.5) | Halves vividness for visitors with light sensitivity or migraine triggers. |
| Deuteranopia | SVG feColorMatrix | Machado-Oliveira-Fernandes 2009 simulation matrix at severity 1.0. Matches Chrome DevTools' "Emulate vision deficiencies" panel so QA results align with the on-page filter. |
Reading Mask
A telescope-style focus band that dims everything except a configurable horizontal stripe. The stripe stays where the visitor leaves it: it only moves on grab-handle drag or arrow-key nudge, never on scroll, so cognitively-loaded users never lose their place.
Toolbar
Activated from the Readable Experience tile. A floating toolbar attaches to the band with four icon buttons: decrease band height, drag handle, increase band height, and close. The toolbar flips above the band automatically when there is no room below it.
Keyboard
| Key | Action |
|---|---|
Arrow Up / Arrow Down | Nudge the band by 20px. |
Page Up / Page Down | Move the band by 100px. |
Home / End | Jump to the top or bottom of the viewport. |
Escape | Close the mask. Routed through toggleMode so a bundle-supplied mask lands in state.excluded. |
Settings
| Setting | Range | Description |
|---|---|---|
mask-band | 20px to 160px, 20px steps | Half-height of the unmasked band. Persists per visitor. |
mask-y | 0 to viewport height | Center Y of the band. Saved on drag-end and on every keyboard nudge so reload restores the same position. |
Built-in Features
Activates as soon as the panel markup is on the page. Drop the FAB plus aside snippet into your base template once and every page picks it up without further wiring.
One-tap presets for epilepsy, visual impairment, cognitive load, motor impairment, colorblindness, dyslexia, and ADHD. Activating a bundle composes its primitives without clobbering settings the visitor already tuned by hand.
Cycling and toggle tiles cover font sizing, dyslexia font, link and title highlights, reading mask, motion, alignment, line height, letter spacing, and word spacing.
Six color treatments behave as a single-pick group: boost contrast, monochrome, high contrast, high saturation, low saturation, and deuteranopia simulation.
A draggable focus band with size controls, pointer-capture dragging, and arrow-key nudging. The position survives reload so visitors can resume reading where they left off.
Every choice writes to localStorage['nds-a11y'] and a FOUC guard re-applies it before paint, so navigating between pages never reverts the visitor's setup.
Honors prefers-reduced-motion and prefers-contrast automatically, and re-applies whenever the visitor flips them in their OS mid-session.
A polite live region announces every toggle, tile cycle, and reset in the visitor's language. Strings load lazily from assets/i18n/accessibility/{lang}.json.
Usage Guidelines
Best Practices
- Treat the FAB as part of the site chrome. Keep it on every page so visitors who tuned the panel on one page do not lose their entry point on the next
- Add a redundant entry point in the footer or main menu (a link that calls
NDS.Accessibility.open()) so keyboard users can reach the panel from the natural tab order without hunting for a corner - Place custom triggers near content visitors are likely to return to. The panel returns focus to the opener on close (W3C APG Disclosure requirement), and the browser scrolls that element into view if it is offscreen, so a trigger high on the page will yank the visitor back to it after they close the panel
- Move the pair with
data-fab-poson the FAB anddata-panel-sideon the panel when chat widgets, cookie banners, or sticky CTAs already occupy a corner. Logicalstartandendflip with text direction; physicalleftandrightstay put - Do not duplicate the panel markup. The component initializes the first
[data-accessibility-panel]it finds and ignores any siblings, so a second include silently falls back to a dead element - Do not put bundle-equivalent toggles in your own settings page. They will fight the panel's persistence and confuse visitors who tuned modes elsewhere. Link to the panel instead
- If your design overrides body typography, scope
--user-line-height,--user-letter-spacing, and--user-word-spacingconsumers behind the matching[data-a11y~="has-{prop}"]selectors so untouched pages stay at your defaults - When you ship custom
--typo-*tokens, wrap their values incalc(value * var(--user-font-scale, 1))so the Font Sizing tile scales them too - Test in dark mode: every visual filter and the high-contrast palette have dark-aware overrides, and your custom tokens should follow the same pattern
- Place dynamic content (modals, drawers, toasts) outside the masked band's host or accept that the reading mask will dim them. The mask sits at
z-index: 850, below the FAB and panel but above page content - Do not rely on the deuteranopia filter as an actual accommodation. It is a designer empathy QA tool. Color-deficient visitors get correct daltonization from their OS settings, not from a CSS filter applied to an already-decoded page
Modifier Classes
| Class | Description |
|---|---|
nds-accessibility-toggle | Panel-specific chrome for the trigger. Pair with nds-fab so the Fab dock owns its edge, inset and size, plus nds-btn nds-primary nds-circle for the default visual. |
nds-accessibility-panel | Panel-specific chrome. Pair with nds-panel so the Panel component owns position, width, the header offset and the slide. |
nds-accessibility-quick | Inline-flex row of quick-toggle buttons (theme toggle, language switch) at the top of the scrollable body. |
nds-accessibility-modes | Sized accordion modifier used by the Modes section. Adjusts switch-row padding to match the tile grids. |
nds-a11y-count | Tag pill next to each accordion title showing how many controls in that section are active. Hides itself when empty. |
Data Attributes
| Attribute | Description |
|---|---|
data-accessibility-toggle | Marks the FAB so the panel can localize it and arm itself on the first click. Opening is data-panel-toggle's job; additional triggers call NDS.Accessibility.open(). |
data-panel-toggle | Set to the panel's id so the Panel component opens it, tracks aria-expanded, and returns focus on close. |
data-accessibility-panel | Mark the panel root. Required for auto-init. |
data-accessibility-action="reset" | Mark the reset button. Two-click confirmation with a 5-second arming window is wired automatically. |
data-fab-pos | Set on the FAB to pick its dock edge. Values: end, start, left, right, bottom. |
data-panel-side | Set on the panel to pick the edge it slides from. Set it to the same side as the FAB. |
data-a11y-mode | Mark a switch or button as a bundle or primitive toggle. Value matches a key from MODE_BUNDLES or a primitive token name. |
data-a11y-visual | Mark a button as a visual-filter selector. Joining the mutex group: clicking it mutes every other filter automatically. |
data-a11y-setting | Mark a button as a cycling setting. Pair with data-a11y-cycle to declare the value sequence. |
data-a11y-cycle | Comma-separated list of values the setting tile cycles through. The first value is "off" and renders no active tile-bars. |
data-a11y-exclude-token | Drop a tile from the rendered panel when the loaded i18n file lists the token in exclude_controls. Used to hide letter-spacing on Arabic. |
data-a11y-status | Place on a role="status" live region inside the panel body. Used by NDS.announce() to deliver polite WCAG 4.1.3 announcements for every toggle, cycle, and reset. |
data-state on the panel | Managed by the Panel component. Values: open (slides the panel into view), opening and closing (the transition windows; cleared and hidden re-applied when the slide ends). |
Tokens Stamped on <html>
Author CSS can react to active modes by reading [data-a11y~="..."] on the document root. Tokens compose, so multiple values appear space-separated.
| Token | Source |
|---|---|
epilepsy-safe, visually-impaired, cognitive-disability, motor-impaired, colorblind, dyslexia-friendly, adhd-friendly | Active bundle marker. Stamped while the bundle's switch is on. |
reduce-motion, high-contrast, low-saturation, high-saturation, monochrome, boost-contrast, cvd-deutan, dyslexia, highlight-titles, highlight-links, reading-mask | Primitive markers. Set by direct toggle, by bundle composition, or by an OS preference for reduce-motion and high-contrast. |
font-step-1, font-step-2, font-step-3 | Discrete font scaling tier. Maps to 1.15×, 1.30×, and 1.50× via --user-font-scale. |
text-align-start, text-align-end, text-align-justify | Forces alignment on body content. |
has-line-height, has-letter-spacing, has-word-spacing | Presence flags for the matching --user-* CSS variable. Stamped only when the value is non-default so author rules are never clobbered when an unrelated mod is on. |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--user-font-scale | 1 | Set on :root by font-step. Wrap your --typo-* tokens in calc(value * var(--user-font-scale, 1)) to opt in. |
--user-line-height | normal | Visitor-tuned line-height applied to body copy when has-line-height is on. |
--user-letter-spacing | 0 | Visitor-tuned letter-spacing applied when has-letter-spacing is on. |
--user-word-spacing | 0 | Visitor-tuned word-spacing applied when has-word-spacing is on. |
--fab-dock-offset | calc(var(--nds-viewport-padding) / 2) | Distance from the viewport edge, set on the Fab dock. Applies to every FAB sharing that edge, not just this one. |
--panel-width | min(420px, 100vw) | Panel width, set on the panel. Falls back to full viewport on narrow screens. |
--panel-top | --nds-nav-height | Top inset of the panel. Leave it unset and the Panel component tracks the live bottom edge of the topbar plus mainnav, so a sticky header is never overlapped. |
JavaScript API
The NDS.Accessibility API exposes the panel's lifecycle, the four state mutators, and a read-only state snapshot. Auto-init runs on first paint via the loader, so most pages never need to call NDS.Accessibility.init() directly.