v1.6.x-dev NDS IQ v6

Accessibility - National Design System

A site-wide floating panel that lets visitors apply accessibility presets, tune typography, and switch visual filters, with every choice persisted across pages.

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.

Open the global panel
The same button is also pinned to the corner of every page.
<button class="nds-btn nds-primary nds-circle nds-fab nds-accessibility-toggle" type="button" aria-label="Accessibility settings" data-i18n-attr="aria-label:panel_label" aria-controls="ndsAccessibilityPanel" aria-expanded="false" data-fab-pos="auto" data-panel-toggle="ndsAccessibilityPanel" data-accessibility-toggle hidden> <i class="nds-icon nds-hgi-accessibility" aria-hidden="true"></i> </button> <aside id="ndsAccessibilityPanel" class="nds-panel nds-accessibility-panel" data-panel-side="end" aria-label="Accessibility settings" data-i18n-attr="aria-label:panel_label" data-accessibility-panel hidden> <div class="nds-panel-header"> <span class="nds-featured-icon nds-circle"> <i class="nds-icon nds-hgi-accessibility" aria-hidden="true"></i> </span> <div class="nds-panel-text"> <h2 class="nds-panel-title" data-i18n="panel_title">Accessibility Tools</h2> </div> <button class="nds-btn nds-subtle nds-icon-only" data-panel-close type="button" aria-label="Close accessibility panel" data-i18n-attr="aria-label:close_panel"> <i class="nds-icon nds-hgi-cancel-01" aria-hidden="true"></i> </button> </div> <div class="nds-panel-body"> <div class="nds-scroll-more nds-divided"> <div class="nds-scroll-more-content"> <div class="nds-sr-only" data-a11y-status role="status" aria-live="polite" aria-atomic="true"></div> <!-- Display & Language — quick toggles --> <div class="nds-accessibility-quick"> <button class="nds-btn nds-subtle nds-icon-only nds-theme-toggle-wrap" data-theme-toggle type="button" aria-label="Toggle theme" data-i18n-attr="aria-label:toggle_theme"> <i class="nds-icon nds-hgi-moon-02" aria-hidden="true"></i> </button> <a class="nds-btn nds-subtle nds-icon-only" href="/ar/" aria-label="التبديل إلى العربية" data-i18n-attr="aria-label:switch_lang"> <i class="nds-icon nds-hgi-translation" aria-hidden="true"></i> </a> </div> <!-- Accessibility settings — one accordion, three items: Modes (switches), Readable Experience (tile grid), Visually Pleasing (tile grid). First item open by default; the other two collapsed to keep the panel compact on first open. --> <div class="nds-accordion nds-lg nds-accessibility-modes" id="a11yAccordion"> <!-- Item 1: Accessibility Modes (bundle switches) --> <div class="nds-accordion-item"> <h3 class="nds-accordion-header"> <button class="nds-btn nds-subtle nds-menu-btn nds-accordion-btn" type="button" aria-expanded="true" data-state="open" aria-controls="a11yModesCollapse"> <span class="nds-accordion-title"><span data-i18n="section_modes">Accessibility Modes</span> <span class="nds-a11y-count nds-tag nds-green nds-rounded nds-sm" data-a11y-count="modes" aria-hidden="true"></span><span class="nds-sr-only" data-a11y-count-sr="modes"></span></span> </button> </h3> <div class="nds-accordion-collapse" id="a11yModesCollapse" data-state="open"> <div class="nds-accordion-content"> <div class="nds-accordion-body"> <fieldset class="nds-form-group nds-switch-group"> <div class="nds-form-container nds-switch-container" data-mode-id="epilepsy-safe"> <div class="nds-form-header"> <label for="a11y-mode-epilepsy-safe"> <span class="nds-label" data-i18n-name></span> <span class="nds-info" data-i18n-desc></span> </label> </div> <div class="nds-form-control"> <div class="nds-switch"> <input type="checkbox" id="a11y-mode-epilepsy-safe" class="nds-switch-input" data-a11y-mode="epilepsy-safe"> <div class="nds-switch-track"><div class="nds-switch-thumb"></div></div> </div> </div> </div> <div class="nds-form-container nds-switch-container" data-mode-id="visually-impaired"> <div class="nds-form-header"> <label for="a11y-mode-visually-impaired"> <span class="nds-label" data-i18n-name></span> <span class="nds-info" data-i18n-desc></span> </label> </div> <div class="nds-form-control"> <div class="nds-switch"> <input type="checkbox" id="a11y-mode-visually-impaired" class="nds-switch-input" data-a11y-mode="visually-impaired"> <div class="nds-switch-track"><div class="nds-switch-thumb"></div></div> </div> </div> </div> <div class="nds-form-container nds-switch-container" data-mode-id="cognitive-disability"> <div class="nds-form-header"> <label for="a11y-mode-cognitive-disability"> <span class="nds-label" data-i18n-name></span> <span class="nds-info" data-i18n-desc></span> </label> </div> <div class="nds-form-control"> <div class="nds-switch"> <input type="checkbox" id="a11y-mode-cognitive-disability" class="nds-switch-input" data-a11y-mode="cognitive-disability"> <div class="nds-switch-track"><div class="nds-switch-thumb"></div></div> </div> </div> </div> <div class="nds-form-container nds-switch-container" data-mode-id="motor-impaired"> <div class="nds-form-header"> <label for="a11y-mode-motor-impaired"> <span class="nds-label" data-i18n-name></span> <span class="nds-info" data-i18n-desc></span> </label> </div> <div class="nds-form-control"> <div class="nds-switch"> <input type="checkbox" id="a11y-mode-motor-impaired" class="nds-switch-input" data-a11y-mode="motor-impaired"> <div class="nds-switch-track"><div class="nds-switch-thumb"></div></div> </div> </div> </div> <div class="nds-form-container nds-switch-container" data-mode-id="colorblind"> <div class="nds-form-header"> <label for="a11y-mode-colorblind"> <span class="nds-label" data-i18n-name></span> <span class="nds-info" data-i18n-desc></span> </label> </div> <div class="nds-form-control"> <div class="nds-switch"> <input type="checkbox" id="a11y-mode-colorblind" class="nds-switch-input" data-a11y-mode="colorblind"> <div class="nds-switch-track"><div class="nds-switch-thumb"></div></div> </div> </div> </div> <div class="nds-form-container nds-switch-container" data-mode-id="dyslexia-friendly"> <div class="nds-form-header"> <label for="a11y-mode-dyslexia-friendly"> <span class="nds-label" data-i18n-name></span> <span class="nds-info" data-i18n-desc></span> </label> </div> <div class="nds-form-control"> <div class="nds-switch"> <input type="checkbox" id="a11y-mode-dyslexia-friendly" class="nds-switch-input" data-a11y-mode="dyslexia-friendly"> <div class="nds-switch-track"><div class="nds-switch-thumb"></div></div> </div> </div> </div> <div class="nds-form-container nds-switch-container" data-mode-id="adhd-friendly"> <div class="nds-form-header"> <label for="a11y-mode-adhd-friendly"> <span class="nds-label" data-i18n-name></span> <span class="nds-info" data-i18n-desc></span> </label> </div> <div class="nds-form-control"> <div class="nds-switch"> <input type="checkbox" id="a11y-mode-adhd-friendly" class="nds-switch-input" data-a11y-mode="adhd-friendly"> <div class="nds-switch-track"><div class="nds-switch-thumb"></div></div> </div> </div> </div> </fieldset> </div> </div> </div> </div> <!-- Item 2: Readable Experience (tile grid) --> <div class="nds-accordion-item"> <h3 class="nds-accordion-header"> <button class="nds-btn nds-subtle nds-menu-btn nds-accordion-btn" type="button" aria-expanded="false" aria-controls="a11yReadableCollapse"> <span class="nds-accordion-title"><span data-i18n="section_readable">Readable Experience</span> <span class="nds-a11y-count nds-tag nds-green nds-rounded nds-sm" data-a11y-count="readable" aria-hidden="true"></span><span class="nds-sr-only" data-a11y-count-sr="readable"></span></span> </button> </h3> <div class="nds-accordion-collapse" id="a11yReadableCollapse"> <div class="nds-accordion-content"> <div class="nds-accordion-body"> <div class="nds-grid" role="group" aria-label="Readable experience controls" data-i18n-attr="aria-label:aria_readable"> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-setting="font-step" data-a11y-cycle="0,1,2,3" aria-pressed="false"> <i class="hgi hgi-stroke hgi-text-smallcaps" aria-hidden="true"></i> <span class="nds-label" data-i18n="font_sizing">Font Sizing</span> <span class="nds-accessibility-tile-bars" data-a11y-bars aria-hidden="true"></span> <span class="nds-sr-only" data-a11y-value data-i18n="default">Default</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-mode="dyslexia" aria-pressed="false"> <i class="hgi hgi-stroke hgi-glasses" aria-hidden="true"></i> <span class="nds-label" data-i18n="dyslexia">Dyslexia Friendly</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-mode="highlight-titles" aria-pressed="false"> <i class="nds-icon nds-hgi-highlighter" aria-hidden="true"></i> <span class="nds-label" data-i18n="highlight_titles">Highlight Titles</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-mode="highlight-links" aria-pressed="false"> <i class="hgi hgi-stroke hgi-link-04" aria-hidden="true"></i> <span class="nds-label" data-i18n="highlight_links">Highlight Links</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-mode="reading-mask" aria-pressed="false"> <i class="nds-icon nds-hgi-search-01" aria-hidden="true"></i> <span class="nds-label" data-i18n="reading_mask">Reading Mask</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-mode="reduce-motion" aria-pressed="false"> <i class="hgi hgi-stroke hgi-pause" aria-hidden="true"></i> <span class="nds-label" data-i18n="pause_motion">Pause Motion</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-setting="text-align" data-a11y-cycle="default,end,start,justify" aria-pressed="false"> <i class="hgi hgi-stroke hgi-text-align-left" aria-hidden="true"></i> <span class="nds-label" data-i18n="text_align">Text Alignment</span> <span class="nds-accessibility-tile-bars" data-a11y-bars aria-hidden="true"></span> <span class="nds-sr-only" data-a11y-value data-i18n="default">Default</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-setting="line-height" data-a11y-cycle="normal,1.6,1.8,2.0" aria-pressed="false"> <i class="hgi hgi-stroke hgi-paragraph-spacing" aria-hidden="true"></i> <span class="nds-label" data-i18n="line_height">Line Height</span> <span class="nds-accessibility-tile-bars" data-a11y-bars aria-hidden="true"></span> <span class="nds-sr-only" data-a11y-value data-i18n="default">Default</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-setting="letter-spacing" data-a11y-cycle="0,0.04em,0.08em,0.12em" data-a11y-exclude-token="letter-spacing" aria-pressed="false"> <i class="hgi hgi-stroke hgi-letter-spacing" aria-hidden="true"></i> <span class="nds-label" data-i18n="letter_spacing">Letter Spacing</span> <span class="nds-accessibility-tile-bars" data-a11y-bars aria-hidden="true"></span> <span class="nds-sr-only" data-a11y-value data-i18n="default">Default</span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-setting="word-spacing" data-a11y-cycle="0,0.16em,0.32em,0.48em" aria-pressed="false"> <i class="hgi hgi-stroke hgi-text-kerning" aria-hidden="true"></i> <span class="nds-label" data-i18n="word_spacing">Word Spacing</span> <span class="nds-accessibility-tile-bars" data-a11y-bars aria-hidden="true"></span> <span class="nds-sr-only" data-a11y-value data-i18n="default">Default</span> </button> </div> </div> </div> </div> </div> <!-- Item 3: Visually Pleasing Experience (single-pick filters + colorblind primitive) --> <div class="nds-accordion-item"> <h3 class="nds-accordion-header"> <button class="nds-btn nds-subtle nds-menu-btn nds-accordion-btn" type="button" aria-expanded="false" aria-controls="a11yVisualCollapse"> <span class="nds-accordion-title"><span data-i18n="section_visual">Visually Pleasing Experience</span> <span class="nds-a11y-count nds-tag nds-green nds-rounded nds-sm" data-a11y-count="visual" aria-hidden="true"></span><span class="nds-sr-only" data-a11y-count-sr="visual"></span></span> </button> </h3> <div class="nds-accordion-collapse" id="a11yVisualCollapse"> <div class="nds-accordion-content"> <div class="nds-accordion-body"> <div class="nds-grid" role="group" aria-label="Visual adjustments" data-i18n-attr="aria-label:aria_visual"> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-visual="boost-contrast" data-visual-id="boost-contrast" aria-pressed="false"> <i class="hgi hgi-stroke hgi-flash" aria-hidden="true"></i> <span class="nds-label" data-i18n-label></span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-visual="monochrome" data-visual-id="monochrome" aria-pressed="false"> <i class="hgi hgi-stroke hgi-color-picker" aria-hidden="true"></i> <span class="nds-label" data-i18n-label></span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-visual="high-contrast" data-visual-id="high-contrast" aria-pressed="false"> <i class="hgi hgi-stroke hgi-blur" aria-hidden="true"></i> <span class="nds-label" data-i18n-label></span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-visual="high-saturation" data-visual-id="high-saturation" aria-pressed="false"> <i class="hgi hgi-stroke hgi-sparkles" aria-hidden="true"></i> <span class="nds-label" data-i18n-label></span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-visual="low-saturation" data-visual-id="low-saturation" aria-pressed="false"> <i class="hgi hgi-stroke hgi-droplet" aria-hidden="true"></i> <span class="nds-label" data-i18n-label></span> </button> <button type="button" class="nds-btn nds-secondary-outline nds-indicator" data-a11y-visual="cvd-deutan" data-visual-id="cvd-deutan" aria-pressed="false"> <i class="hgi hgi-stroke hgi-colors" aria-hidden="true"></i> <span class="nds-label" data-i18n-label></span> </button> </div> </div> </div> </div> </div> </div> </div> <button class="nds-btn nds-subtle nds-show-more" type="button" aria-label="Scroll panel" data-i18n-attr="aria-label:scroll_panel"> <i class="nds-icon nds-hgi-arrow-down-01" aria-hidden="true"></i> </button> </div> </div> <div class="nds-panel-footer"> <button class="nds-btn nds-secondary-outline" type="button" data-accessibility-action="reset"> <span class="nds-label" data-i18n="reset">Reset Settings</span> <div class="nds-progress-circle"> <svg width="100%" height="100%" viewBox="0 0 24 24"> <circle class="nds-progress-bg" cx="12" cy="12" r="10" fill="none" stroke-width="2"></circle> <circle class="nds-progress-track" cx="12" cy="12" r="10" fill="none" stroke-width="2" stroke-dasharray="62.83" stroke-dashoffset="62.83" stroke-linecap="round"></circle> </svg> </div> </button> </div> </aside>

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.

Programmatic open
<a href="#" class="nds-link" id="open-a11y">Accessibility settings</a>

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.

AttributeSet onBehavior
data-fab-pos="auto"FABWhat 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"PanelWhich 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.

BundleWCAGWhat it activates
epilepsy-safe2.3.1Pauses motion and drops saturation to half so flashing media and oversaturated palettes cannot trigger seizures.
visually-impaired1.4.6Forces a high-contrast palette swap and bumps font sizing to 1.30×, the AAA contrast plus large-text combination.
cognitive-disability2.2, 2.3Highlights every heading and pauses motion so the page reads as a clear, static outline.
motor-impaired2.5.5Promotes every button, link, and form control to a 48×48px hit-target.
colorblind1.4.1Applies a deuteranopia simulation filter for designer empathy QA. Color-deficient users themselves should rely on OS-level daltonization.
dyslexia-friendly1.4.8, 1.4.12Swaps 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-friendly2.2, 2.3Pauses 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.

TileMechanismEffect
Font SizingCycle: 0, 1, 2, 3Scales every --typo-* token by 1.15×, 1.30×, or 1.50× via --user-font-scale. Stamps font-step-N.
Dyslexia FriendlyToggleSwaps --nds-font-family to the OpenDyslexic plus Maqroo stack and bumps body-copy weight.
Highlight TitlesToggleOutlines every h1 through h6 with the warning palette so the document outline becomes scannable.
Highlight LinksToggleAdds a dashed warning outline plus underline to every text link, leaving button-styled anchors untouched.
Reading MaskToggleActivates the draggable focus band described in the next section.
Pause MotionToggleMutes animations and transitions site-wide and pauses every autoplay media element on activation.
Text AlignmentCycle: default, end, start, justifyForces alignment on body content via logical start and end so RTL and LTR both read naturally.
Line HeightCycle: normal, 1.6, 1.8, 2.0Sets --user-line-height on body copy. Headings keep their typographic ratios.
Letter SpacingCycle: 0, 0.04em, 0.08em, 0.12emSets --user-letter-spacing. Tile is hidden on Arabic pages because letter-spacing breaks cursive ligatures.
Word SpacingCycle: 0, 0.16em, 0.32em, 0.48emSets --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.

FilterCSSUse case
Boost Contrastcontrast(1.15)Sharpens edges across the whole page without swapping the palette.
Monochromegrayscale(1)Removes color cues so the visitor can verify the page still works without them.
High ContrastToken-level palette swapReplaces 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 Saturationsaturate(2)Doubles vividness for visitors who perceive low-chroma palettes as washed out.
Low Saturationsaturate(0.5)Halves vividness for visitors with light sensitivity or migraine triggers.
DeuteranopiaSVG feColorMatrixMachado-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

KeyAction
Arrow Up / Arrow DownNudge the band by 20px.
Page Up / Page DownMove the band by 100px.
Home / EndJump to the top or bottom of the viewport.
EscapeClose the mask. Routed through toggleMode so a bundle-supplied mask lands in state.excluded.

Settings

SettingRangeDescription
mask-band20px to 160px, 20px stepsHalf-height of the unmasked band. Persists per visitor.
mask-y0 to viewport heightCenter Y of the band. Saved on drag-end and on every keyboard nudge so reload restores the same position.

Built-in Features

Auto-initialization

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.

Seven Accessibility Bundles

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.

Ten Readable Tiles

Cycling and toggle tiles cover font sizing, dyslexia font, link and title highlights, reading mask, motion, alignment, line height, letter spacing, and word spacing.

Mutex Visual Filters

Six color treatments behave as a single-pick group: boost contrast, monochrome, high contrast, high saturation, low saturation, and deuteranopia simulation.

Telescope Reading Mask

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.

Cross-Page Persistence

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.

OS Preference Sync

Honors prefers-reduced-motion and prefers-contrast automatically, and re-applies whenever the visitor flips them in their OS mid-session.

Localized Live Announcements

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-pos on the FAB and data-panel-side on the panel when chat widgets, cookie banners, or sticky CTAs already occupy a corner. Logical start and end flip with text direction; physical left and right stay 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-spacing consumers behind the matching [data-a11y~="has-{prop}"] selectors so untouched pages stay at your defaults
  • When you ship custom --typo-* tokens, wrap their values in calc(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

ClassDescription
nds-accessibility-togglePanel-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-panelPanel-specific chrome. Pair with nds-panel so the Panel component owns position, width, the header offset and the slide.
nds-accessibility-quickInline-flex row of quick-toggle buttons (theme toggle, language switch) at the top of the scrollable body.
nds-accessibility-modesSized accordion modifier used by the Modes section. Adjusts switch-row padding to match the tile grids.
nds-a11y-countTag pill next to each accordion title showing how many controls in that section are active. Hides itself when empty.

Data Attributes

AttributeDescription
data-accessibility-toggleMarks 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-toggleSet to the panel's id so the Panel component opens it, tracks aria-expanded, and returns focus on close.
data-accessibility-panelMark 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-posSet on the FAB to pick its dock edge. Values: end, start, left, right, bottom.
data-panel-sideSet on the panel to pick the edge it slides from. Set it to the same side as the FAB.
data-a11y-modeMark a switch or button as a bundle or primitive toggle. Value matches a key from MODE_BUNDLES or a primitive token name.
data-a11y-visualMark a button as a visual-filter selector. Joining the mutex group: clicking it mutes every other filter automatically.
data-a11y-settingMark a button as a cycling setting. Pair with data-a11y-cycle to declare the value sequence.
data-a11y-cycleComma-separated list of values the setting tile cycles through. The first value is "off" and renders no active tile-bars.
data-a11y-exclude-tokenDrop 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-statusPlace 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 panelManaged 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.

TokenSource
epilepsy-safe, visually-impaired, cognitive-disability, motor-impaired, colorblind, dyslexia-friendly, adhd-friendlyActive 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-maskPrimitive 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-3Discrete font scaling tier. Maps to 1.15×, 1.30×, and 1.50× via --user-font-scale.
text-align-start, text-align-end, text-align-justifyForces alignment on body content.
has-line-height, has-letter-spacing, has-word-spacingPresence 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

PropertyDefaultDescription
--user-font-scale1Set on :root by font-step. Wrap your --typo-* tokens in calc(value * var(--user-font-scale, 1)) to opt in.
--user-line-heightnormalVisitor-tuned line-height applied to body copy when has-line-height is on.
--user-letter-spacing0Visitor-tuned letter-spacing applied when has-letter-spacing is on.
--user-word-spacing0Visitor-tuned word-spacing applied when has-word-spacing is on.
--fab-dock-offsetcalc(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-widthmin(420px, 100vw)Panel width, set on the panel. Falls back to full viewport on narrow screens.
--panel-top--nds-nav-heightTop 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.

// ── Lifecycle ──────────────────────────────────────── NDS.Accessibility.init(); // Idempotent. Re-init tears down listeners + remounts. NDS.Accessibility.open(); // Slides the panel in. Saves the opener for focus return. NDS.Accessibility.close(); // Slides out. Returns focus to the element that opened it. NDS.Accessibility.toggle(); // Open if closed, close if open. // ── State mutators ─────────────────────────────────── // Toggle a bundle by name OR a primitive token. Bundles compose // their primitives; toggling a primitive separately respects the // supply slot (modes / bundle / OS) and never double-counts. NDS.Accessibility.toggleMode('dyslexia-friendly'); // bundle NDS.Accessibility.toggleMode('reduce-motion'); // primitive // Pick exactly one visual filter. Mutex: every other filter is // muted automatically across user, bundle, and OS supply slots. NDS.Accessibility.setVisualFilter('high-contrast'); // Pass the same name again to turn the filter off. NDS.Accessibility.setVisualFilter('high-contrast'); // Cycle a setting key through the supplied value array. The first // entry is treated as "off" by syncUI() (no active tile-bars). NDS.Accessibility.cycleSetting('font-step', [0, 1, 2, 3]); NDS.Accessibility.cycleSetting('text-align', ['default', 'end', 'start', 'justify']); NDS.Accessibility.cycleSetting('line-height', ['normal', '1.6', '1.8', '2.0']); // Wipe everything back to defaults. Bypasses the panel's two-click // confirmation, so reserve this for programmatic flows (e.g. a // "factory reset" button in your own settings UI). NDS.Accessibility.reset(); // ── Read-only state snapshot ───────────────────────── // Returns a deep clone, so mutations do not leak back into the panel. const snapshot = NDS.Accessibility.state; // { // modes: ['highlight-titles'], // bundles: ['dyslexia-friendly'], // excluded: [], // settings: { // 'font-step': 2, // 'text-align': 'default', // 'line-height': '1.6', // 'letter-spacing': '0.12em', // 'word-spacing': '0.16em', // 'mask-band': 60, // 'mask-y': 420 // }, // settingsSnapshots: { 'dyslexia-friendly': { 'line-height': 'normal', ... } } // } // ── Persistence ────────────────────────────────────── // State is auto-persisted to localStorage['nds-a11y'] on every mutation. // Clearing it manually re-syncs on the next init() or reset() call. localStorage.removeItem('nds-a11y'); NDS.Accessibility.reset();
Last Modified Date: 02/08/2026 - 09:52 AM
Was this page useful?
60% of users said Yes from 2843 Feedbacks