Accessibility
Accessibility statement
LUZ aims for WCAG 2.2 level AA. What is checked on every change, what has not been tested, and the known issues. Last reviewed on 5 November 2026.
Target
LUZ aims to meet WCAG 2.2 level AA. The version of EN 301 549 that the European Accessibility Act cites today (3.2.1) asks for WCAG 2.1 level AA; version 4.1.1, published in September 2026, asks for 2.2. LUZ has not been audited against either standard.
A design system cannot make a service accessible by itself: how you use the components decides the result. Each component page says what is yours to do, under Keyboard and ARIA and Writing the content.
What is checked on every change
- An automated scan with axe-core of the home page, in English, Spanish and Arabic, and of every page of the documentation, in the light, dark and high-contrast modes. On component and pattern pages the scan runs once with each tab open, and on a sample of pages once with the search dialog open, with results and with none. A serious or critical finding fails the build.
- Keyboard use of the sidebar drawer, the modal dialog, the tabs, the Code and Design tabs of the component pages, the "On this page" index, whose links open the tab of the section they point at, and the search: Ctrl+K, the arrow keys through the results, Enter and Escape.
- The shell of the documentation pages. The skip link goes to the main content (WCAG 2.4.1, Bypass blocks). The header, the sidebar, the breadcrumb, the page, the rail and the footer are landmarks, and the navigations have their own names. The sidebar is one flat list with every area open. At 769 pixels wide and above it is a column. Below that it is a drawer that the Menu button opens, with the popover API and no script. With JavaScript, the sidebar scrolls the link of the current page into view when the page loads; the page and the focus do not move.
- The rail of a documentation page: it comes before the content in the source, and the skip link goes past it to the content. From 80 rem wide it is a sticky column beside the content, and below that it is drawn above the content, so nothing scrolls sideways; there, with JavaScript, “On this page” is folded when the page loads and its summary opens it. Copy page as Markdown writes the page to the clipboard and says so in a status message, and a failure is said in the same message. It is checked with and without JavaScript.
- The light of the home page: the slider by keyboard, the open and closed state of the bar, the names and the value text in each language of the home page, axe with the bar open in several lights, forced colors, and that with reduced motion nothing asks for an animation frame.
- The pages with JavaScript turned off: content is readable at 320 pixels wide, the Code and Design sections of a component page show one after the other, the search button is a link to the A–Z index, and no control that needs JavaScript is left on screen doing nothing. The color mode control in the page footer shows as an Auto radio button and three radio buttons for the modes, which still set the mode. Without JavaScript the color mode choice applies to the page you are on only. Without JavaScript, a link to a section of the same page leaves the menu open; Escape or the menu button closes it. Copy page as Markdown is a link to the page's
index.mdfile. The Try it form and the Phone, Tablet and Full width choice of the examples are hidden. The language switch of the home page opens its list of languages, which are links, with the popover API and no script. - Reduced motion: no transition or animation runs when the visitor asks for less motion.
- The timeline: its keys (arrows, Home and End on the dates, mirrored right to left), the Previous and Next buttons, the names of the buttons, that autoplay is opt-in with a visible Pause / Play button, and that with reduced motion autoplay never starts and the button is not shown.
- The embed: that the link without JavaScript becomes a button with the same name, that the question takes focus and Not now returns it to the button, that the player's frame takes focus and has a title, that Escape is left alone, and that nothing is requested from the player's origin before the visitor acts or without their consent.
- The chart: that the table is in the page and readable beside the drawing, that the drawing is hidden from assistive technology, that the button swaps the one for the other, and that series are told apart in forced colors.
- Forced colors: a focused button and a focused search result keep a visible outline, the selected tab keeps its underline, and the search dialog keeps its border.
- The home page in Arabic, right to left: it must not overflow at 320 pixels wide, with JavaScript or without it.
- Right-to-left text: the demo of every component, except the player, is laid out with
dir="rtl"next to its left-to-right layout. Each element with text, and each control, must sit where the mirror image puts it, and nothing may overflow. The check runs with and without JavaScript. - Contrast of the text written for the home and documentation pages, in all three modes.
- Before this release, the same automated suites ran once in Firefox 155 and in WebKit 26.6 and 27.2 (the engine of Safari), on Linux, and their findings were fixed. They do not run on every change, and neither is a test on macOS or iOS.
llms.txtandclasses.jsonare regenerated on every change; a stale one fails.- Contrast of every token pair a component draws, by a script, in the three base modes. A theme of your own is measured by the same script, as the themes page shows.
To review by hand: every component and HTML tag on one page, in the three modes and in right-to-left text.
What has not been tested
- Screen readers. No assistive technology has been used to test this release: NVDA, JAWS, VoiceOver, TalkBack and Orca have not been tried. Nothing here says a component works with them.
- Speech recognition and screen magnifier software.
- Right-to-left text by a person who reads it, or with a screen reader. Only the layout is measured by a script, and the player's controls are not part of that measurement.
- Zoom to 400 percent by hand. Only the 320 pixel width is measured.
- Research with disabled people.
- The accessibility reviewer that
@luz-ds/checkprints, andluz-checkitself. They read source files and give advice; they do not open a page, measure on screen or use assistive technology. A run with no findings does not mean a page is accessible.
Automated tools find only part of the problems. Treat this list as the gap, and test your own pages.
Known issues
- Tooltip. Escape does not close it. This partly fails WCAG 1.4.13 (Content on hover or focus). Keep tooltips non-essential.
- Modal. Its buttons use the
commandandcommandforattributes. In a browser without them, and without JavaScript, the open button does nothing. - Player. MIDI needs JavaScript and Web Audio. Without them the player shows a message. On this documentation site, the example video's caption files load only with JavaScript; without it the browser lists the tracks but shows no text.
- Select. The open list is the browser’s or the system’s, so it looks and sounds different in each. Where the browser supports
appearance: base-select(Chrome and Edge 135, Safari 27), LUZ styles the list too, and that list has not been tested with a screen reader yet. It works without JavaScript. - Linked card. The link in the title covers the whole card, so the body text cannot be selected with a mouse. Keyboard selection, find in page and screen readers reach it as usual.
- Table sorted in the browser. The sorted column is exposed with
aria-sorton its column header, and each sort is announced in a status message. Tabbing through the headings announces the column name; the sort state is read on the header cell and in the status message. Checked in the Chromium accessibility tree only; not yet tested with NVDA, JAWS, VoiceOver, TalkBack or Orca. - Avatar with a photo. If the photo does not load, the browser draws its text alternative inside the box, in capitals, and a long name can spill past it.
- Code and Design tabs. The tab you chose last opens after the page has loaded, so the content below the tabs can move once.
- Find in page. In browsers that do not support
hidden="until-found"(Safari before 26.2, Firefox before 139), find in page (Ctrl+F) does not find text in the tab that is not open. In Safari 26.2 and later it opens the tab but does not scroll to the match. Open the other tab, or turn JavaScript off to see both. - Choose your snippet. Without JavaScript the picker is not in the page, every snippet is shown one after the other, and the index table, whose cells are links to the snippets, is the navigation. With JavaScript the picker replaces the table and shows one snippet, and it announces the snippet shown through its
<output>, a status message. The focus stays on the choice you made. That announcement has not been tested with a screen reader. - Make it yours, sizes demo. The sizes demo is optional: the table of presets on the page lists the sizes without it. Without JavaScript the demo is not shown, so no control is left on screen doing nothing. With it, the sliders resize the page itself, not a sample, and the Reset sizes button puts the page back; nothing is stored, so a reload does the same. The size knobs change the text and the space of the page; a reader’s own text size is respected, because the scale is in rem and follows the browser’s text size.
- Search shortcut. Ctrl+K (⌘K on a Mac) opens the documentation search instead of the browser’s own web search box. Use the browser’s address bar to search the web. The shortcut is shown on the search button, does nothing while you type in a text area or editable content, and there is no single-key shortcut. In a one-line text field it still opens the search, so on a Mac it replaces Ctrl+K, “delete to the end of the line”, there. On a site that loads the search with
catalog.js, the search’s own script loads when the page is idle, or when you point at or move to the search: a Ctrl+K pressed before that loads it and then opens the search, after a short wait on a slow connection. On a page without a search, Ctrl+K stays the browser’s. - Search. It has not been tested with a screen reader. The number of results is announced once you stop typing. Until its script has loaded, the search is a link to the A–Z index, in the same place; if you are on that link when the script arrives, it stays until you leave it, and the focus is never moved. If the script cannot load, the link stays. Closing the search returns the focus to where it was; if nothing had the focus, as after Ctrl+K on a page just loaded, it goes to the search button, and the next Tab continues from there.
- Search at 400 percent zoom. The whole search dialog scrolls, so the search field can move out of view while you read the results. Shift+Tab or the Up arrow from the first result brings you back to it.
- Documentation sidebar. Every page of the documentation is a link in the sidebar, in one list with every area open, so the list is long. The page’s own link carries
aria-current="page"and its area carriesaria-current="true". Use the skip link, the search (Ctrl+K) or the A–Z index to avoid tabbing through it. With JavaScript the sidebar scrolls to the current page when the page loads, and the focus stays where it was. Without JavaScript it starts at the top. The drawer below 769 pixels wide is a native popover: Escape closes it. Not tested with a screen reader. - Rail and Copy page as Markdown. The rail holds “On this page”, a native
<details>that is open (with JavaScript, closed on load below 80 rem wide), and three actions. From 80 rem wide the rail is sticky and the mark on the section in view comes from the catalog’s script. Without JavaScript Copy page as Markdown is a link that opens the Markdown file of the page. With JavaScript and the clipboard it is a button with the same text, and it does not leave the page. Its status message (“Copied the page as Markdown.” or “Could not copy the page.”) is a status region and clears after five seconds. If the file loads but the browser refuses to write to the clipboard, the browser opens the file instead. If the file cannot be read, the message says so and the focus stays on the button. Not tested with a screen reader. - Try it and the width of the example. Try it is on the Button page; the Phone, Tablet and Full width choice is on the Button, Card, Form and Table pages. Both are hidden in the markup and shown by the page’s script, so without JavaScript no control is left on screen doing nothing. Both are native radio buttons, and Try it adds a native checkbox. Changing a choice changes the preview and, in Try it, the code under it, and the Copy button copies the new code. Nothing says so aloud: the change is not announced. The preview button is the thing being tried, not a control of the form, and it does nothing. Not tested with a screen reader.
- A–Z index. The index is two pages, A–L and M–Z, so that neither is heavy. The top of each page says so in words, and the row of letters links to the letters of the other page. Names that do not start with a letter from A to Z, such as a digit, are listed under “Other” at the end of the second page. The search button is a link to the first page until the search script has loaded. Not tested with a screen reader.
- Light of the home page. The section under the hero draws the sun and the moon where the visitor is, from the time zone of the browser and without a network request. Without JavaScript the section is empty and a sentence says what it is for. With it, the phrase under the drawing is a button (one of the page’s facts about light, chosen at random on each load) whose name ends with “Show the light bar”, and it carries
aria-expanded. The bar it controls reads, from top to bottom, the place (the city of the browser’s time zone) and the light, the hour in large type, then a slider named “Scrub the day”, whose value text is the light and the hour. The slider is an ordinary range input: the arrow keys, Home and End move it. Below 1024 pixels wide, and on a screen that cannot hover, the button opens and closes the bar, and Escape closes it and returns the focus to the button. From 1024 pixels wide on a screen that can hover, the bar is always open: Escape and the button do not close it. At rest it is drawn at 70 percent opacity, strong enough for the text of the place, the light and the hour to keep the usual text contrast over the sky, and it comes up whole under the pointer or when the focus is inside it. Resizing the window across 1024 pixels opens or closes the bar to match. While you scrub, the whole page takes the light and the mode of that hour. Letting the slider go keeps that hour for the whole site, after a reload too, until Light, Dark or High contrast is chosen in the footer; a “Saved mode” notice in a status region says so and goes after a moment. The slider’s thumb is a sun by day and a moon by night, as well as the value text. The drawing is not a control: the globe is an image that a pointer can drag, and it has no keyboard control. With reduced motion nothing in the drawing animates. With reduced motion the stars at the top of the page at dawn, dusk and night neither twinkle nor drift with the scroll. The globe is drawn at once; its land arrives a moment later, when the browser is idle. In forced colors the drawing uses the text color and the stars are not drawn. The tab icon of the home page follows the sky: a sun over the horizon by day and the moon by night, in the page’s colors, drawn by the page’s script. The other pages keep a fixed icon. Not tested with a screen reader. - Motion of the home page’s light. The globe spins slowly and the stars at dawn, dusk and night twinkle and drift with the scroll, for longer than five seconds. WCAG 2.2 success criterion 2.2.2 (Pause, Stop, Hide, level A) is not met for the globe and the stars. The motion stops only with the system’s reduced-motion setting; the page has no button or other control to pause it. Not tested with a screen reader.
- Motion of the logo. The prism of the site’s header plays a loop of 4.8 seconds without end: its three bands of tone fade and come back one after another. It lasts longer than five seconds and the page has no button or other control to pause it, so WCAG 2.2 success criterion 2.2.2 (Pause, Stop, Hide, level A) is not met. The owner accepted it on 30 October 2026. It stops with the system’s reduced-motion setting. The icon in the browser tab carries the same loop in its own file; whether a browser plays it depends on the browser, and the ones that do not show it still.
- Motion of the tabs on the home page. The three boxes of tokens, core and catalog turn their tabs by themselves in a loop (install, list, code), 3.2 seconds each, while the box is on screen. A line fills under the open tab. Hovering the box or focusing inside it holds the loop, and it runs on 3 seconds after both leave. Choosing a tab, by click, keys or a link, ends it for good in that box. It never moves the focus, announces nothing, does not run while the box is off screen and does not start under the system’s reduced-motion setting. It lasts longer than five seconds and has no pause control of its own, so WCAG 2.2 success criterion 2.2.2 (Pause, Stop, Hide, level A) is not met. The owner accepted it on 31 October 2026.
- Order of the hero buttons on a phone. On the home page, at 640 pixels wide or less,
Get started with AI
is shown aboveGet started
, but it comes second in the page’s code, so Tab and a screen reader reachGet started
first. The visual order and the reading order differ for this one button, which does not meet WCAG 2.4.3 (Focus Order) or 1.3.2 (Meaningful Sequence, level A) to the letter. The three links stay in one column and each says where it goes, so no meaning is lost. The owner accepted it on 30 October 2026; from 641 pixels wide the order is the same in the code and on screen. - Language switcher. The list of languages opens at a fixed place under the header, not next to its button. Below 360 pixels wide its button shows a globe only; the language’s name is still the button’s accessible name. It has not been tested with a screen reader.
- Navbar menu. On narrow screens the menu of a navbar, in a landing, CMS or docs header, opens as a panel under the header. If you keep pressing Tab after its last link, focus moves into the page and the panel stays open over it. Press Escape to close it. (WCAG 2.2 success criterion 2.4.12, level AAA; level AA 2.4.11 is met.) Where CSS anchor positioning is supported (Chrome and Edge 125, Safari 26, Firefox 147) the panel opens under the header wherever it ends. In older browsers it opens at the height of a one-row bar at the default text size (
--luz-bar-h), from the top of the window: with enlarged text and a header that wraps to a second row, or with a banner above the header before the page scrolls, it can cover part of the header. - Submenu. A submenu of a header’s links is a native disclosure: Escape and a click outside do not close it; its own button or opening another one does (several can stay open in Firefox before 130 and Safari before 17.2). From 769 pixels wide an open submenu stays over the top of the page, so if you keep pressing Tab after it, a link behind it can be hidden (WCAG 2.2 success criterion 2.4.11, Focus not obscured, minimum). Press its button to close it. In the narrow menu its links are in the flow and Escape closes the whole menu. It has not been tested with a screen reader.
- Announcement. It cannot be closed. It has no script.
- Page transition. The page fades into the next one in Chrome and Edge 126 and Safari 18.2 and later. Firefox does not play it and navigates as always. When the reader asks for reduced motion, no transition starts. The page cannot be used for the 0.2 seconds the fade lasts. Focus and the announcement of the new page are those of any navigation. It has not been tested with a screen reader.
- FAQ. Each question is a native
<details>, so it opens with the keyboard and works without JavaScript. A link to an answer opens its question in Chrome 97, Firefox 148 and Safari 26.2 and later; in an older browser it lands on the closed question. In Safari 26.2 the page does not scroll well to an answer found with Find in page. Closed answers print where the browser supports::details-content(Chrome 131, Firefox 143, Safari 18.4); an older browser prints a closed question without its answer. It has not been tested with a screen reader. - Cookie consent. Without JavaScript the banner is not shown, because none of the site's own scripts run either; the cookie policy is a link on every page. With it, the banner shows as a band fixed at the bottom of the window, so nothing on the page moves; it comes first after the skip link in the reading and Tab order, takes no focus when the page loads, does not block the page, and Escape neither answers nor hides it. While it shows, the page keeps room for it at its end and scrolls what Tab reaches above it (WCAG 2.2 success criterion 2.4.11); in Safari, which ignores that scroll padding for text fields, a script scrolls a field reached with Tab back above the band. In a window less than 30 rem tall it is in the flow at the top of the page instead, so the page moves down when it appears. Its confirmation hides with its Close button, which returns focus to the button that opened the banner or to the main content, or when focus leaves it for the page; a click on an empty part of the page does not hide it. The answer is stored in the browser, not in a cookie. It has not been tested with a screen reader.
- Theme mode and theme time.
<luz-theme-mode>is one round button that steps through Auto, Light, Dark and High contrast. Its name is the legend and the checked label, such as “Color mode: Auto”, and a status region says the new mode after a choice. Without JavaScript the four radios show and still change the mode. On a page that names no mode, Auto follows the system, with or without JavaScript, and so it does on this site. Without the small scripts for the head, a page whose visitor chose a mode or an hour other than the system’s can paint the system’s first.<luz-theme-time>adds the moments of the day as a choice. On this site the radio checked is the mode in use (the system’s, Light or Dark) and the button is named after it, such as “Color mode: Dark”; Auto is checked, and the button says so, only while an hour of the day is chosen, and without JavaScript no radio is checked and the page follows the system. The hour of the day applies only when the slider of the home page picks it, and the site’s four lights meet the same contrast as its modes. Withprefers-contrast: more, Auto gives high contrast. The slider of the home page’s bar pins an hour that stands in Auto: choosing a mode or Auto with the button, or pressing Escape on the slider, lets it go. Neither has been tested with a screen reader. - Sticky header, enlarged text. A sticky header (
.o-sticky) keeps a field reached with Tab clear of it by a minimum height. That works at 100 and 200 percent text. At 300 percent text, when the header wraps to several rows, Safari can still leave the field partly under it; Chrome, Edge and Firefox keep it clear. The Layout objects page and the landing and CMS Header pages say how to set the padding for your own header. In Firefox with forced colors the dim layer behind the open menu is not drawn; the panel keeps its border. - Band. A band (
.luz-band) cuts off whatever sticks out of it above or below, so a tooltip on the first control of a band is clipped at 200 percent text and larger; start the band with its heading. The hover of an outline or ghost button on a band shows no change. In forced colors the band has no tint, only two thin rules above and below it. - Prose. A table that is wider than the text column and has no
.luz-table-wraparound it makes the whole page scroll sideways on a narrow screen. A long preformatted block scrolls inside itself; give ittabindex="0"and a name, as the code block has, or Safari users cannot scroll it with the keyboard. - Meta line. The dots between the facts are hidden from screen readers with alternative text for generated content. In a browser without it (Firefox before 128, Safari before 17.4) a screen reader reads each dot.
- Lists laid out as a grid, stack or row. Safari does not announce a list whose markers are removed, outside a navigation landmark, as a list. Its items are still read in order, and the headings in them still lead through it.
- Masthead header (cms). In browsers without CSS anchor positioning, the sections menu of the masthead header opens near the top of the window and covers the publication name and the menu button while it is open. Escape, or a click outside the menu, closes it and returns to the page.
- Table of contents (cms). The mark on the section in view comes from the catalog's script (
data-luz-spy); without it, no link is marked, and the links still work. Next to the text, the contents list the sections at the inline end but come first in the source, so Tab reaches them before the text. It has not been tested with a screen reader. - Account list (app). The list of an application's account button is a list of links in a popover, not an ARIA menu: it has no arrow keys, and Tab moves through its links. In browsers without CSS anchor positioning it opens as a sheet at the bottom of the window instead of under the button; Escape, or a click outside it, closes it. The button's name is the initials, then the name, so it reads “AM Ana Martín” at every width. It has not been tested with a screen reader.
- Dashboard (app). The panels keep the order of the markup, so a wide panel that does not fit in what is left of a row starts the next one and leaves a gap; the reading order and the drawn order are always the same. It has not been tested with a screen reader.
- Documentation patterns (docs). The seven documentation patterns of
@luz-ds/catalog, the filescomponents/docs-*.css, are CSS, with no script. The rail comes before the content in the source. Below 80 rem wide it is drawn above the content, under the header; from 80 rem it is a column beside it. The rail is sticky and scrolls by itself when it is taller than the window; the mark on the section in view comes from the catalog’s script, and without it no link is marked and the links still work. The stage of a Do and don’t is inert and hidden from assistive technology, so its caption carries the meaning, and a mistake that would be a barrier is shown as code. In browsers without alternative text for generated content (Safari before 17.4, Firefox before 128), a screen reader may read the tick or the cross before “Do” or “Don’t”; the word that follows is always read. Not tested with a screen reader. - Timeline. Without JavaScript it is an ordered list with a line and a dot per item, and every item is visible. With it, only the current card is read and reached with Tab; the other cards are inert. The Previous and Next buttons are named only “Previous” and “Next”: the name does not say which date they lead to. Whether a screen reader announces the card that appears when you change the item has not been tested, and the timeline has not been tested with a screen reader at all. Autoplay is off unless the page asks for it. When it is on, it moves a card every few seconds, and a visible Pause / Play button stops and resumes it. It also holds while the pointer moves over the timeline, while the keyboard focus is inside it, while less than half of it is on screen and while the tab is hidden, and it never starts with reduced motion. While it runs, the card is not announced.
- Embed. Without JavaScript, or when its address is not valid, the poster is a link to the video where it lives. With it, it is a button with the same name; once the player's frame has loaded, its keys, captions, language and accessibility are those of the other site, and the page cannot change or test them. Escape does nothing in the question: Not now is the way back. It has not been tested with a screen reader.
- Chart. Without JavaScript, or before it loads, the table (or the list of numbers) is all there is, and it holds every figure. With it, the drawing is hidden from assistive technology and the table stays readable; a button swaps the one for the other when the page gives it its two words, and without them the table shows under the drawing. The drawing has no tooltip and no readout: the figures are in the table. A series is told by its colour, by the mark it ends in and by the legend, and in forced colors by its dashes; the plot is drawn left to right in every language. It reads the table when it connects and when it changes, and a cell that holds anything but a plain number is a gap. It has not been tested with a screen reader.
- Languages LUZ does not ship. On a page in a language LUZ has no strings for, such as French or Arabic, the labels and messages its scripts write (player buttons, copy buttons, code block names, sort status) are in English inside a page marked with another language, so a screen reader reads them with that language's voice (WCAG 3.1.2, Language of parts). The scripts warn once in the console. Set all the
data-luz-label-*attributes once on<html>or<body>, or callregisterLocale()before the scripts start, and they are in your language. The home page does this for Arabic. - Legacy theme buttons. On a page in another language, the Apply theme / Applied buttons of the legacy custom-theme markup (
luz-theme-apply-btn) are in English until the first theme change, unless the page setsdata-luz-label-apply-themeanddata-luz-label-theme-applied. Not tested with a screen reader. - Strings in a language LUZ ships. Spanish is a file fetched when a page declares it. If that language file cannot load, the labels LUZ's scripts add are in English inside a page marked with another language, and a screen reader reads them with that language's voice. The page itself, and its authored text, are not affected. The labels are written once the file arrives, or after two seconds without it. A click before the language arrives announces in English, and a late arrival mixes English labels written at start with Spanish labels written on a state change, such as play and pause. Not tested with NVDA, JAWS, VoiceOver, TalkBack or Orca. The standalone modules (
/highlight,/player,/theme,/clipboard) carry English only: a Spanish page that imports them directly must callloadLocales()orregisterLocale(). - Button. A
.luz-btndoes not wrap its label (white-space: nowrap), so a button with a long label can overflow its container at 320 pixels wide with text at 130 percent (WCAG 1.4.10, Reflow). Shorten the label, or setwhite-space: normalin your own layer. - Copy button. When the browser refuses to copy, the failure is announced to screen readers, but the button shows no message.
Report an accessibility problem
If something does not work for you, open an issue on GitHub and say what you were trying to do, the browser and the assistive technology you use.