{"$schema":"./manifest.schema.json","version":"0.2.0","tokens":{"file":"tokens.ts","groups":["color","focus","radius","border","font","weight","text","display","leading","tracking","measure","space","icon","control","target","container","z","duration","ease","breakpoint"]},"components":[{"name":"BusyRegion","slug":"busy-region","level":"atom","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/BusyRegion/BusyRegion.svelte","doc":"components/BusyRegion/BusyRegion.md","stories":"components/BusyRegion/BusyRegion.stories.svelte","test":"components/BusyRegion/BusyRegion.test.ts"},"props":[{"name":"busy","type":"boolean","required":true,"description":"The zone is waiting on something: aria-busy goes on, the ring turns."},{"name":"as","type":"'main' | 'section' | 'div'","required":false,"description":"The element: `main` for the content area, `section` or `div` for a part of it.","default":"'div'"},{"name":"class","type":"string","required":false,"description":"Layout classes only: the zone's own padding and width.","default":"''"},{"name":"children","type":"Snippet","required":true}],"rest":"Omit<HTMLAttributes<HTMLElement>, 'children'>","snippets":["children"],"stories":["idle","busy","asSection"],"classes":["rl-action","rl-border-strong","rl-busy-region","rl-duration-spin","rl-ink-soft","rl-space-5","rl-sunken","rl-tourner","rl-z-busy"],"criteria":[{"code":"7.1","why":"The wait is a state assistive technology can read: aria-busy on the zone, set and cleared by the page, never a spinner alone."},{"code":"13.8","why":"The ring turns under motion only; with reduced motion the hollow and the rule at the top say the wait."}],"replaces":["apps/web: the zone-occupee class and aria-busy written by hand on the lab layout's main"],"patterns":["app-shell"],"related":["button"]},{"name":"Button","slug":"button","level":"atom","status":"stable","surfaces":["site","app","extension"],"since":"2026-08","files":{"component":"components/Button/Button.svelte","doc":"components/Button/Button.md","stories":"components/Button/Button.stories.svelte","test":"components/Button/Button.test.ts"},"props":[{"name":"variant","type":"'primary' | 'secondary' | 'ghost' | 'destructive' | 'link'","required":false,"description":"The role on the page; destructive borrows the non-conformity red because it shares its meaning. `link` is an action that reads as a link in a sentence (\"Renommer\", \"Modifier\"): underlined, no box, the text's own size. Still a button: a destination takes the Link component.","default":"'primary'"},{"name":"size","type":"'sm' | 'md' | 'lg'","required":false,"description":"`sm` for toolbars and table rows, `lg` for a page's one call to action.","default":"'md'"},{"name":"fullWidth","type":"boolean","required":false,"description":"Takes the whole width: narrow forms, panels.","default":"false"},{"name":"busy","type":"boolean","required":false,"description":"The requested work is in progress. aria-busy and aria-disabled, never disabled: the focus is on this button, since it was just pressed, and disabled would drop it to the top of the document for a one-second wait. The attribute does not block a second click; the caller refuses the second submit at the source. No effect on a link.","default":"false"},{"name":"reason","type":"string | undefined","required":false,"description":"Why the button is unavailable. Sets disabled and writes the reason next to it, tied by aria-describedby. Ignored on a link."},{"name":"children","type":"Snippet","required":true,"description":"The label. A verb, never an icon alone."}],"rest":"ButtonOrLinkAttributes","snippets":["children"],"stories":["primary","secondary","ghost","destructive","inSentence","small","large","fullWidth","asLink","busy","disabled","withReason"],"classes":["rl-action","rl-action-hover","rl-bg","rl-border-strong","rl-button","rl-button--","rl-button--destructive","rl-button--full","rl-button--ghost","rl-button--lg","rl-button--link","rl-button--primary","rl-button--secondary","rl-button--sm","rl-button-ink","rl-button__reason","rl-button__with-reason","rl-duration-fast","rl-duration-spin","rl-ease","rl-ink","rl-ink-soft","rl-line","rl-line-strong","rl-nc","rl-on-fill","rl-radius","rl-space-1-5","rl-space-2","rl-space-2-5","rl-space-3","rl-space-5","rl-space-6","rl-sunken","rl-text-base","rl-text-sm","rl-text-xs","rl-tourner","rl-weight-bold"],"criteria":[{"code":"7.3","why":"A real button or a real link: reachable by Tab, activated by Enter and Space, with no script to reproduce what the element does natively."},{"code":"6.1","why":"With href it is a link whose text says the destination; without, a button whose text says the action."},{"code":"3.2","why":"Every variant, the unavailable state and the busy state hold 4.5:1 on their own background (tokens/contrast.json)."}],"replaces":["apps/web: raw btn-* classes on twelve pages, three ways of writing Cancel, three ways of full width","apps/extension: a11y-btn-primary, a11y-verdict-btn, a11y-export-modal-btn, a11y-answer-btn"],"patterns":["destructive-confirmation","disabled-with-reason","form-with-error-summary"],"related":["alert","icon"]},{"name":"Checkbox","slug":"checkbox","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Checkbox/Checkbox.svelte","doc":"components/Checkbox/Checkbox.md","stories":"components/Checkbox/Checkbox.stories.svelte","test":"components/Checkbox/Checkbox.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The control's id; the label's `for` is the same value."},{"name":"label","type":"string","required":true,"description":"The visible label: what checking means."},{"name":"name","type":"string","required":false,"description":"The name sent with the form."},{"name":"value","type":"string","required":false,"description":"The value sent when checked."},{"name":"checked","type":"boolean | undefined","required":false,"description":"Checked or not; bindable, absent reads as unchecked.","bindable":true},{"name":"hint","type":"string | undefined","required":false,"description":"A help sentence, announced with the control."},{"name":"error","type":"string | undefined","required":false,"description":"The error message; its presence alone marks the control invalid."},{"name":"required","type":"boolean","required":false,"description":"Required: the word goes into the accessible name and the attribute onto the control.","default":"false"},{"name":"disabled","type":"boolean","required":false,"description":"Unavailable. Prefer a reason written next to it, as Button does, when there is one.","default":"false"}],"snippets":[],"stories":["plain","checked","withHint","required","error","disabled"],"classes":["rl-action","rl-border-strong","rl-checkbox","rl-checkbox__control","rl-checkbox__error","rl-checkbox__hint","rl-checkbox__label","rl-checkbox__mark","rl-checkbox__row","rl-focus","rl-focus-offset","rl-focus-width","rl-ink","rl-ink-soft","rl-nc","rl-space-1","rl-space-1-5","rl-space-2-5","rl-sr-only","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"11.1","why":"The label's for and the control's id come from one prop, so a checkbox cannot be rendered without its label."},{"code":"11.10","why":"The error is tied by aria-describedby and aria-invalid together, and read as an alert without moving the focus."},{"code":"3.1","why":"The native mark says the checked state as a shape; accent-color only tunes the colour."}],"replaces":["apps/web: the hand-built checkboxes of the settings, the sharing and the admin forms, once the app is ported"],"patterns":["form-with-error-summary"],"related":["radio-group","switch","field"]},{"name":"Chip","slug":"chip","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Chip/Chip.svelte","doc":"components/Chip/Chip.md","stories":"components/Chip/Chip.stories.svelte","test":"components/Chip/Chip.test.ts"},"props":[{"name":"tone","type":"'neutral' | 'info' | 'success' | 'warning' | 'error'","required":false,"description":"The ink and the line; neutral says nothing but the word.","default":"'neutral'"},{"name":"icon","type":"IconName","required":false,"description":"A drawing before the word, hidden; the word still says everything."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":true,"description":"The word, or two."}],"snippets":["children"],"stories":["neutral","info","success","warning","error","withIcon"],"classes":["rl-action","rl-attention","rl-border","rl-c","rl-chip","rl-chip--","rl-chip--error","rl-chip--info","rl-chip--success","rl-chip--warning","rl-ink-soft","rl-line","rl-nc","rl-radius","rl-space-1","rl-space-1-5","rl-space-2-5","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"3.1","why":"The word is required and carries the information; the tone is an ink on a line that only confirms it, and a drawing is hidden."}],"replaces":["packages/design base.css: the chip class, and its raw uses in the app, once the app is ported"],"related":["status-chip","level-badge"]},{"name":"CodeBlock","slug":"code-block","level":"atom","status":"stable","surfaces":["app","site"],"since":"2026-09","files":{"component":"components/CodeBlock/CodeBlock.svelte","doc":"components/CodeBlock/CodeBlock.md","stories":"components/CodeBlock/CodeBlock.stories.svelte","test":"components/CodeBlock/CodeBlock.test.ts"},"props":[{"name":"inline","type":"boolean","required":false,"description":"In the running text, as a `code` element; otherwise a block.","default":"false"},{"name":"label","type":"string","required":false,"description":"What the block holds, as its accessible name: \"Clé de secours\". Block only."},{"name":"children","type":"Snippet","required":true,"description":"The text."}],"snippets":["children"],"stories":["inline","block","long"],"classes":["rl-border","rl-code","rl-code-block","rl-font-mono","rl-ink","rl-line","rl-radius","rl-space-1-5","rl-space-3","rl-space-4","rl-sunken","rl-text-sm"],"criteria":[{"code":"10.11","why":"A long value wraps anywhere, so a token or an address never makes the page scroll sideways at 320 pixels."},{"code":"10.4","why":"The size is a token of the scale, so the text grows with the reader's zoom like the rest."}],"replaces":["apps/web: the code elements and mono spans written by hand for selectors, secrets and tokens"],"patterns":[],"related":["kbd","figure"]},{"name":"DisabledReason","slug":"disabled-reason","level":"atom","status":"stable","surfaces":["app","extension"],"since":"2026-09","files":{"component":"components/DisabledReason/DisabledReason.svelte","doc":"components/DisabledReason/DisabledReason.md","stories":"components/DisabledReason/DisabledReason.stories.svelte","test":"components/DisabledReason/DisabledReason.test.ts"},"props":[{"name":"reason","type":"string","required":true,"description":"Why the control is unavailable: a sentence that says what to do first (\"Terminez l'audit pour publier.\")."},{"name":"layout","type":"'beside' | 'under'","required":false,"description":"`beside` writes the reason next to the control; `under` writes it below, for a full-width control or a group.","default":"'beside'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet<[DisabledMarks]>","required":true,"description":"The control, which spreads the marks it receives: `{#snippet children(marks)}<IconButton {...marks} />{/snippet}`."}],"snippets":["children"],"stories":["iconButton","stacked","group"],"classes":["rl-disabled-reason","rl-disabled-reason--","rl-disabled-reason--under","rl-disabled-reason__reason","rl-ink-soft","rl-space-1-5","rl-space-2","rl-space-3","rl-text-sm"],"criteria":[{"code":"11.10","why":"The reason is tied to the control by aria-describedby: a reader hears why the control refuses, next to its name, and not only a disabled state."},{"code":"3.1","why":"The refusal is a sentence, not a greyed look: the soft ink of the control only confirms what the words say."},{"code":"10.11","why":"The sentence wraps under the control on a narrow screen; nothing is cut."}],"replaces":["apps/extension: the title attribute on the theme rows' disabled wizard button ('Revenez à la page auditée')"],"patterns":["disabled-with-reason"],"related":["button","icon-button"]},{"name":"Eyebrow","slug":"eyebrow","level":"atom","status":"stable","surfaces":["site","app"],"since":"2026-08","files":{"component":"components/Eyebrow/Eyebrow.svelte","doc":"components/Eyebrow/Eyebrow.md","stories":"components/Eyebrow/Eyebrow.stories.svelte","test":"components/Eyebrow/Eyebrow.test.ts"},"props":[{"name":"children","type":"Snippet","required":true,"description":"The words, short and true: a version, a scope, a browser list."}],"snippets":["children"],"stories":["plain","long"],"classes":["rl-eyebrow","rl-ink-soft","rl-text-xs","rl-tracking-eyebrow","rl-weight-bold"],"criteria":[{"code":"9.1","why":"Rendered as a paragraph, never as a heading: it is not a level of the outline, and a screen reader must not count it as one."}],"patterns":[],"related":["heading","prose","section"]},{"name":"Figure","slug":"figure","level":"atom","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/Figure/Figure.svelte","doc":"components/Figure/Figure.md","stories":"components/Figure/Figure.stories.svelte","test":"components/Figure/Figure.test.ts"},"props":[{"name":"src","type":"string","required":true,"description":"The picture's address."},{"name":"alt","type":"string","required":true,"description":"What the picture shows and proves, as a sentence."},{"name":"width","type":"number","required":false,"description":"The intrinsic size, so the page reserves the room before the picture arrives."},{"name":"height","type":"number","required":false,"description":"The intrinsic size."},{"name":"href","type":"string","required":false,"description":"The address of the picture at full size, opened in a new window; the picture becomes the link."},{"name":"caption","type":"string","required":false,"description":"The caption under the picture: the selector, the source."},{"name":"code","type":"boolean","required":false,"description":"The caption is a value in the mono face: a selector.","default":"false"},{"name":"size","type":"'full' | 'natural'","required":false,"description":"`natural` keeps the picture at its size up to the frame; `full` stretches it to the frame's width.","default":"'full'"}],"snippets":[],"stories":["evidence","opensLarge","natural"],"classes":["rl-border","rl-figure","rl-figure--","rl-figure--full","rl-figure--natural","rl-figure__caption","rl-figure__caption--code","rl-figure__image","rl-figure__link","rl-font-mono","rl-ink-soft","rl-line-control","rl-line-strong","rl-radius","rl-space-1","rl-surface","rl-text-xs"],"criteria":[{"code":"1.1","why":"The picture carries an alt that says what it proves; the caption says where."},{"code":"13.2","why":"When the picture opens large in a new window, its name says so."},{"code":"10.11","why":"The picture never exceeds its frame, and a caption that is a selector wraps anywhere."}],"replaces":["apps/web: the figure written by hand on the audited page and in the report for the screenshots of a non-conformity"],"patterns":[],"related":["code-block"]},{"name":"GlossaryTerm","slug":"glossary-term","level":"atom","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/GlossaryTerm/GlossaryTerm.svelte","doc":"components/GlossaryTerm/GlossaryTerm.md","stories":"components/GlossaryTerm/GlossaryTerm.stories.svelte","test":"components/GlossaryTerm/GlossaryTerm.test.ts"},"props":[{"name":"term","type":"string","required":true,"description":"The term as written in the sentence."},{"name":"open","type":"boolean","required":false,"description":"Open at first; bindable.","default":"false","bindable":true},{"name":"children","type":"Snippet","required":true,"description":"The definition."}],"snippets":["children"],"stories":["closed","open"],"classes":["rl-border","rl-border-strong","rl-ink","rl-ink-soft","rl-line-strong","rl-space-2","rl-space-3","rl-sr-only","rl-sunken","rl-term","rl-term__definition","rl-text-sm"],"criteria":[{"code":"7.1","why":"A button with aria-expanded and aria-controls, operable at the keyboard, where a tooltip on hover would be reached by nobody."},{"code":"10.13","why":"The definition appears on a press and stays until pressed again; it is not content shown on hover or focus that vanishes."}],"replaces":["apps/extension: the .a11y-terme buttons the wizards wrote and attacherLesTermes wired"],"related":["disclosure","question","tooltip"]},{"name":"Heading","slug":"heading","level":"atom","status":"stable","surfaces":["site","app"],"since":"2026-08","files":{"component":"components/Heading/Heading.svelte","doc":"components/Heading/Heading.md","stories":"components/Heading/Heading.stories.svelte","test":"components/Heading/Heading.test.ts"},"props":[{"name":"level","type":"1 | 2 | 3 | 4","required":true,"description":"The real level in the document, independent of the displayed size."},{"name":"size","type":"'display' | '3xl' | '2xl' | 'xl' | 'lg' | 'base'","required":false,"description":"`display` for the thesis of a page, otherwise a size of the scale; `base` for a fourth level inside a document.","default":"'2xl'"},{"name":"measure","type":"number","required":false,"description":"The maximum line length in characters, so a long heading breaks where it reads."},{"name":"id","type":"string","required":false,"description":"An id, for a section that names itself by its heading through aria-labelledby."},{"name":"class","type":"string","required":false,"description":"Layout classes only: margins.","default":"''"},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["display","level2","levelWithoutSize","measured"],"classes":["rl-display","rl-display-leading","rl-display-tracking","rl-display-weight","rl-font-sans","rl-heading","rl-heading--","rl-heading--2xl","rl-heading--3xl","rl-heading--base","rl-heading--display","rl-heading--lg","rl-heading--xl","rl-ink","rl-text-2xl","rl-text-3xl","rl-text-base","rl-text-lg","rl-text-xl"],"criteria":[{"code":"9.1","why":"The level is a required prop, separate from the size: the outline of the page is decided by the document, never by how big a title should look."}],"patterns":[],"related":["eyebrow","prose","section"]},{"name":"Icon","slug":"icon","level":"atom","status":"stable","surfaces":["site","app","extension"],"since":"2026-09","files":{"component":"components/Icon/Icon.svelte","doc":"components/Icon/Icon.md","stories":"components/Icon/Icon.stories.svelte","test":"components/Icon/Icon.test.ts"},"props":[{"name":"name","type":"IconName","required":true,"description":"Which drawing, from icons/svg. A status takes its name from `statusIcon`."},{"name":"size","type":"'sm' | 'md' | 'lg'","required":false,"description":"`sm` sits in small text and chips, `md` in body text and buttons, `lg` in headings.","default":"'md'"},{"name":"class","type":"string","required":false,"description":"Layout classes only: a margin, a colour. The size comes from `size`.","default":"''"}],"snippets":[],"stories":["small","medium","large","statuses","all"],"classes":["rl-icon","rl-icon--","rl-icon--lg","rl-icon--sm","rl-icon-lg","rl-icon-md","rl-icon-sm"],"criteria":[{"code":"1.2","why":"The svg is always aria-hidden and focusable=false; assistive technology skips it and reads the word next to it."}],"replaces":["apps/web: the inline SVGs of the lab layout, one per navigation entry","apps/extension: the SVG strings of the panel header and the wizards"],"patterns":[],"related":["status-chip","alert","button"]},{"name":"IconButton","slug":"icon-button","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/IconButton/IconButton.svelte","doc":"components/IconButton/IconButton.md","stories":"components/IconButton/IconButton.stories.svelte","test":"components/IconButton/IconButton.test.ts"},"props":[{"name":"label","type":"string","required":true,"description":"The name, French: what the button does (\"Fermer\", \"Menu\"). Read by assistive technology, never shown."},{"name":"name","type":"IconName","required":true,"description":"The drawing, from the icon set."},{"name":"size","type":"'sm' | 'md' | 'lg'","required":false,"description":"`sm` for a toolbar or a table row, `lg` for a page's one such control.","default":"'md'"},{"name":"variant","type":"'ghost' | 'secondary'","required":false,"description":"`ghost` draws nothing until hovered; `secondary` has the control line.","default":"'ghost'"},{"name":"pressed","type":"boolean | undefined","required":false,"description":"A toggle: on or off, written as aria-pressed. Absent for a button that is not one."}],"rest":"ButtonOrLinkAttributes","snippets":[],"stories":["ghost","secondary","small","large","toggle","asLink","disabled"],"classes":["rl-border","rl-control-lg","rl-control-md","rl-control-sm","rl-duration-fast","rl-ease","rl-hover","rl-icon-button","rl-icon-button--","rl-icon-button--lg","rl-icon-button--secondary","rl-icon-button--sm","rl-ink","rl-ink-soft","rl-line-control","rl-line-strong","rl-radius","rl-sunken"],"criteria":[{"code":"11.9","why":"The name is a required prop written to aria-label, so a button that shows only a drawing still says what it does."},{"code":"7.1","why":"A toggle writes aria-pressed from its state at every render; a button that is not one carries no state."},{"code":"3.1","why":"The pressed state is told by the fill and the line, and under forced colours by the system's highlight."}],"replaces":["apps/web: the close, menu and rail buttons of the lab layout and the row menus, once the app is ported","apps/extension: the header and toolbar buttons of the panel, when it is on Svelte"],"patterns":["app-shell"],"related":["button","icon"]},{"name":"InlineMarkup","slug":"inline-markup","level":"atom","status":"stable","surfaces":["site","extension"],"since":"2026-08","files":{"component":"components/InlineMarkup/InlineMarkup.svelte","doc":"components/InlineMarkup/InlineMarkup.md","stories":"components/InlineMarkup/InlineMarkup.stories.svelte","test":"components/InlineMarkup/InlineMarkup.test.ts"},"props":[{"name":"text","type":"string","required":true,"description":"The sentence, with backticks for code, double asterisks for strong, single ones for emphasis."}],"snippets":[],"stories":["plain","code","emphasis","mixed","codeKeepsMarkers"],"classes":["rl-font-mono","rl-ink","rl-inline-markup__code","rl-inline-markup__strong","rl-radius","rl-sunken","rl-weight-bold"],"criteria":[{"code":"10.7","why":"Rendered through real elements, never an HTML string, so a criterion sheet cannot inject markup even by accident."}],"patterns":[],"related":["prose"]},{"name":"Kbd","slug":"kbd","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Kbd/Kbd.svelte","doc":"components/Kbd/Kbd.md","stories":"components/Kbd/Kbd.stories.svelte","test":"components/Kbd/Kbd.test.ts"},"props":[{"name":"keys","type":"string[]","required":false,"description":"The keys of a combination, each in its own kbd, joined by a plus."},{"name":"children","type":"Snippet","required":false,"description":"One key, when there is no combination."}],"snippets":["children"],"stories":["single","combination","inSentence"],"classes":["rl-border","rl-border-strong","rl-font-mono","rl-ink","rl-ink-soft","rl-kbd","rl-kbd--combo","rl-kbd__plus","rl-line-control","rl-radius","rl-space-1","rl-space-1-5","rl-surface"],"criteria":[{"code":"8.9","why":"The kbd element is used for what it means, a key the user presses, and never for its look; a key drawn with a span would be presentation only."},{"code":"12.10","why":"Where a single-key shortcut exists, the component is how the page says it, so the shortcut is documented next to the control it drives."}],"related":["inline-markup"]},{"name":"LevelBadge","slug":"level-badge","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/LevelBadge/LevelBadge.svelte","doc":"components/LevelBadge/LevelBadge.md","stories":"components/LevelBadge/LevelBadge.stories.svelte","test":"components/LevelBadge/LevelBadge.test.ts"},"props":[{"name":"level","type":"'A' | 'AA' | 'AAA'","required":true,"description":"The level, as the referential writes it."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["a","aa","aaa","inRow"],"classes":["rl-border","rl-font-mono","rl-ink","rl-level-badge","rl-line-strong","rl-radius","rl-space-1-5","rl-sr-only","rl-text-xs","rl-tracking-label","rl-weight-bold"],"criteria":[{"code":"3.1","why":"The level is a code, not a colour: the three tiers share one drawing and the word says which."}],"replaces":["apps/web: the level letters written raw next to the criteria of the audit and the report, once the app is ported"],"related":["chip","status-chip"]},{"name":"Link","slug":"link","level":"atom","status":"stable","surfaces":["site","app","extension"],"since":"2026-09","files":{"component":"components/Link/Link.svelte","doc":"components/Link/Link.md","stories":"components/Link/Link.stories.svelte","test":"components/Link/Link.test.ts"},"props":[{"name":"href","type":"string","required":true,"description":"The destination."},{"name":"class","type":"string","required":false,"description":"Layout classes only: a margin.","default":"''"},{"name":"children","type":"Snippet","required":true,"description":"The text, which says the destination on its own."}],"rest":"Omit<HTMLAnchorAttributes, 'children' | 'href'>","snippets":["children"],"stories":["plain","inSentence"],"classes":["rl-action","rl-action-hover","rl-link","rl-weight-bold"],"criteria":[{"code":"3.1","why":"The underline carries the link and the hover thickens it; the colour only supports both, so the link reads without colour."},{"code":"6.1","why":"The text is the name: the component adds nothing to it, so the caller has to write a text that says the destination."}],"replaces":["apps/site and apps/web: the link-underline class written by hand in fifty files"],"patterns":[],"related":["button"]},{"name":"LiveRegion","slug":"live-region","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/LiveRegion/LiveRegion.svelte","doc":"components/LiveRegion/LiveRegion.md","stories":"components/LiveRegion/LiveRegion.stories.svelte","test":"components/LiveRegion/LiveRegion.test.ts"},"props":[{"name":"message","type":"string","required":false,"description":"The text to announce. Empty at first; a change is what gets announced, so a message repeated verbatim says nothing and should vary.","default":"''"},{"name":"tone","type":"'polite' | 'assertive'","required":false,"description":"`polite` waits for a pause; `assertive` interrupts and is for an error that blocks.","default":"'polite'"},{"name":"visible","type":"boolean","required":false,"description":"Show the message as text; hidden by default, the page shows the outcome its own way.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["polite","assertive","hidden"],"classes":["rl-ink","rl-live-region","rl-space-3","rl-sr-only","rl-text-sm"],"criteria":[{"code":"7.5","why":"The region is in the document from the first render, empty, and only its text changes: that is the one shape every screen reader restitutes, and the component makes the other shapes impossible."},{"code":"7.1","why":"The role is chosen from the tone, status or alert, and aria-live is written with it, so the announcement is compatible with the readers that honour one or the other."}],"replaces":["apps/web: the aria-live paragraphs written by hand on the audit page, the settings and the admin lists, once the app is ported","apps/extension: the aria-live attribute on the whole panel, which announced every redraw, when the panel is on Svelte"]},{"name":"Logo","slug":"logo","level":"atom","status":"stable","surfaces":["site","app","extension"],"since":"2026-08","files":{"component":"components/Logo/Logo.svelte","doc":"components/Logo/Logo.md","stories":"components/Logo/Logo.stories.svelte","test":"components/Logo/Logo.test.ts"},"props":[{"name":"height","type":"number","required":false,"description":"The drawing's height in pixels; the word scales with it.","default":"28"},{"name":"markOnly","type":"boolean","required":false,"description":"Hides the word: for constrained spaces, never by default.","default":"false"}],"snippets":[],"stories":["header","small","large","markOnly"],"classes":["rl-ink-soft","rl-logo","rl-logo-height","rl-logo__mark","rl-logo__word","rl-logo__word-soft","rl-space-2","rl-sr-only","rl-tracking-tight","rl-weight-bold","rl-weight-regular"],"criteria":[{"code":"1.1","why":"The drawing is aria-hidden and the word carries the name: an alt on the image would make a screen reader say the brand twice."},{"code":"3.2","why":"Drawn inline in currentColor, the mark follows the ink of the theme and keeps its contrast on both sets; as an image it would stay black on the dark set."}],"patterns":[],"related":[]},{"name":"ProgressBar","slug":"progress-bar","level":"atom","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/ProgressBar/ProgressBar.svelte","doc":"components/ProgressBar/ProgressBar.md","stories":"components/ProgressBar/ProgressBar.stories.svelte","test":"components/ProgressBar/ProgressBar.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The element's id; the label's `for` is the same value."},{"name":"label","type":"string","required":true,"description":"The visible label: what is progressing, as a noun phrase. Read only by assistive technology on a strip."},{"name":"value","type":"number | undefined","required":false,"description":"The progress, from 0 to `max`. Absent, the bar is indeterminate."},{"name":"max","type":"number","required":false,"description":"The value that means done.","default":"100"},{"name":"showValue","type":"boolean","required":false,"description":"Writes the percentage next to the bar. Never on a strip.","default":"true"},{"name":"strip","type":"boolean","required":false,"description":"A thin line across a shell's edge, the label hidden and the value unwritten: for a progress the screen says in words elsewhere.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["measured","indeterminate","done","strip"],"classes":["rl-action","rl-border","rl-font-mono","rl-ink","rl-ink-soft","rl-line","rl-progress","rl-progress--strip","rl-progress__bar","rl-progress__label","rl-progress__row","rl-progress__value","rl-space-1","rl-space-1-5","rl-space-3","rl-sr-only","rl-sunken","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"7.1","why":"The native progress element carries its role and its value; nothing is declared by hand, and an absent value is the indeterminate state."},{"code":"11.1","why":"The label's for and the element's id come from one prop, so the bar is named like a field."},{"code":"3.1","why":"The value is written as a number next to the bar; the fill only draws it."}],"replaces":["apps/site: the measured case of the chargement-et-progression demo, kept as teaching material","apps/extension: the hand-drawn progress strip of shell/Sidebar.svelte, and the page analysis's when the panel draws it"],"related":["busy-region","live-region"]},{"name":"Prose","slug":"prose","level":"atom","status":"stable","surfaces":["site","app"],"since":"2026-08","files":{"component":"components/Prose/Prose.svelte","doc":"components/Prose/Prose.md","stories":"components/Prose/Prose.stories.svelte","test":"components/Prose/Prose.test.ts"},"props":[{"name":"size","type":"'base' | 'lg' | 'sm'","required":false,"description":"The type size: `sm` for a caption, `lg` for an introduction.","default":"'base'"},{"name":"measure","type":"number","required":false,"description":"The maximum line length in characters; a maximum, never a fixed width.","default":"58"},{"name":"class","type":"string","required":false,"description":"Layout classes only: margins.","default":"''"},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["base","small","large","narrow"],"classes":["rl-ink-soft","rl-paragraph","rl-paragraph--","rl-paragraph--lg","rl-paragraph--sm","rl-text-base","rl-text-lg","rl-text-sm"],"criteria":[{"code":"10.11","why":"The measure is a maximum in characters, not a fixed width: the paragraph reflows at 320 pixels without horizontal scrolling."}],"patterns":[],"related":["heading","eyebrow","inline-markup"]},{"name":"ScrollRegion","slug":"scroll-region","level":"atom","status":"stable","surfaces":["site","app"],"since":"2026-08","files":{"component":"components/ScrollRegion/ScrollRegion.svelte","doc":"components/ScrollRegion/ScrollRegion.md","stories":"components/ScrollRegion/ScrollRegion.stories.svelte","test":"components/ScrollRegion/ScrollRegion.test.ts"},"props":[{"name":"label","type":"string","required":false,"description":"What one enters. Becomes the region's accessible name, with \"(zone défilante)\" appended."},{"name":"labelledBy","type":"string","required":false,"description":"The id of a heading already on the page, when the region is announced by it. One of `label` or `labelledBy` is required, or the stop is silent."},{"name":"class","type":"string","required":false,"description":"Layout classes for the container: margins, gutters.","default":"''"},{"name":"framed","type":"boolean","required":false,"description":"A hairline frame. Off by default: the region is not a control.","default":"false"},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["labelled","framed","labelledBy"],"classes":["rl-border","rl-line","rl-radius","rl-scroll-region","rl-scroll-region--framed"],"criteria":[{"code":"12.9","why":"A region that scrolls takes the focus, so its off-screen columns are reachable with the keyboard alone."},{"code":"12.8","why":"The region has a name, so the tab stop is not a silent one."}],"replaces":["apps/web: nine hand-written scroll wrappers on the dashboard, the site page and the report"],"patterns":["data-table-page"],"related":["data-table"]},{"name":"Select","slug":"select","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Select/Select.svelte","doc":"components/Select/Select.md","stories":"components/Select/Select.stories.svelte","test":"components/Select/Select.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The control's id; the label's `for` is the same value."},{"name":"label","type":"string","required":true,"description":"The visible label, a noun."},{"name":"name","type":"string","required":false,"description":"The name sent with the form."},{"name":"value","type":"string | undefined","required":false,"description":"The chosen value; bindable, absent reads as nothing chosen.","bindable":true},{"name":"options","type":"Option[]","required":false,"description":"The options, when they are not grouped.","default":"[]"},{"name":"groups","type":"Group[]","required":false,"description":"The options by group.","default":"[]"},{"name":"placeholder","type":"string","required":false,"description":"The text of the empty option, \"Choisissez\" by default; an empty string removes it.","default":"'Choisissez'"},{"name":"hint","type":"string | undefined","required":false,"description":"A help sentence, announced with the field."},{"name":"error","type":"string | undefined","required":false,"description":"The error message; its presence alone marks the field invalid."},{"name":"required","type":"boolean","required":false,"description":"Required: the word goes into the accessible name and the attribute onto the control.","default":"false"},{"name":"disabled","type":"boolean","required":false,"description":"Unavailable.","default":"false"}],"snippets":[],"stories":["plain","chosen","grouped","required","error","disabled"],"classes":["rl-action","rl-border","rl-ink","rl-ink-soft","rl-line","rl-line-control","rl-nc","rl-radius","rl-select","rl-select__control","rl-select__error","rl-select__hint","rl-select__label","rl-select__mark","rl-space-1-5","rl-space-2","rl-space-3","rl-sr-only","rl-surface","rl-text-base","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"11.1","why":"The label's for and the control's id come from one prop, so a select cannot be rendered without its label."},{"code":"11.10","why":"The error is tied by aria-describedby and aria-invalid together, and an empty option keeps nothing chosen distinct from a choice."},{"code":"7.3","why":"The native element gives the arrows, the first letters and the system's picker; nothing is remade, so nothing is lost."}],"replaces":["apps/site: the liste-déroulante demo, kept as teaching material","apps/web: the hand-built selects of the audit's report parameters, the new audit and the new account"],"patterns":["form-with-error-summary"],"related":["field","radio-group"]},{"name":"SkipLink","slug":"skip-link","level":"atom","status":"stable","surfaces":["design","site","app"],"since":"2026-09","files":{"component":"components/SkipLink/SkipLink.svelte","doc":"components/SkipLink/SkipLink.md","stories":"components/SkipLink/SkipLink.stories.svelte","test":"components/SkipLink/SkipLink.test.ts"},"props":[{"name":"href","type":"string","required":false,"description":"The target: the id of the main landmark, with the hash. The element must carry tabindex=\"-1\" to take the focus.","default":"'#main'"},{"name":"children","type":"Snippet","required":false,"description":"The text; \"Aller au contenu\" when none."}],"snippets":["children"],"stories":["plain","custom"],"classes":["rl-action","rl-duration-base","rl-ease","rl-on-fill","rl-radius","rl-skip-link","rl-space-2","rl-space-4","rl-weight-bold","rl-z-skip"],"criteria":[{"code":"12.7","why":"It is the skip link to the main content the criterion asks for, first in the tab order, and it cannot be rendered without a target."},{"code":"12.6","why":"The header and the navigation, present on every page, can be avoided in one keystroke."},{"code":"10.7","why":"The link appears on focus, in the action colour on its own fill: the stop is visible, which a link hidden with display none never is."}],"replaces":["packages/design base.css: the skip-link class, and its three raw uses on the design site, the public site and the lab layout"],"patterns":["app-shell"],"related":["link"]},{"name":"StatusChip","slug":"status-chip","level":"atom","status":"stable","surfaces":["site","app"],"since":"2026-08","files":{"component":"components/StatusChip/StatusChip.svelte","doc":"components/StatusChip/StatusChip.md","stories":"components/StatusChip/StatusChip.stories.svelte","test":"components/StatusChip/StatusChip.test.ts"},"props":[{"name":"status","type":"StatutDeCritere","required":true,"description":"One of the four statuses of core: C, NC, NA, NT."},{"name":"short","type":"boolean","required":false,"description":"Hides the word on screen when the context already says it (a column header); it stays in the name.","default":"false"}],"snippets":[],"stories":["c","nc","na","nt","short"],"classes":["rl-border","rl-c","rl-font-mono","rl-na","rl-nc","rl-nt","rl-radius","rl-space-1","rl-space-1-5","rl-space-2","rl-sr-only","rl-status-chip","rl-status-chip--","rl-status-chip--c","rl-status-chip--na","rl-status-chip--nc","rl-status-chip--nt","rl-status-chip__code","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"3.1","why":"The abbreviation and the word are part of the component; a chip that says its status by colour alone cannot be built with it."},{"code":"3.2","why":"Each status ink is measured on the page, the surface and the hollow (tokens/contrast.json)."}],"replaces":["apps/web: status badges written by hand in the report and the audit pages"],"patterns":[],"related":["conformity-rate","icon"]},{"name":"Switch","slug":"switch","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Switch/Switch.svelte","doc":"components/Switch/Switch.md","stories":"components/Switch/Switch.stories.svelte","test":"components/Switch/Switch.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The control's id, for a form that references it."},{"name":"label","type":"string","required":true,"description":"The visible label: the setting, as a noun phrase. It is the switch's name."},{"name":"checked","type":"boolean | undefined","required":false,"description":"On or off; bindable, absent reads as off.","bindable":true},{"name":"onchange","type":"(checked: boolean) => void","required":false,"description":"Called with the new state, at once: a switch has no submit."},{"name":"hint","type":"string | undefined","required":false,"description":"A help sentence, announced with the control."},{"name":"disabled","type":"boolean","required":false,"description":"Unavailable.","default":"false"}],"snippets":[],"stories":["plain","on","withHint","disabled"],"classes":["rl-action","rl-border","rl-duration-fast","rl-ease","rl-ink","rl-ink-soft","rl-line","rl-line-control","rl-on-fill","rl-space-1","rl-space-4","rl-surface","rl-switch","rl-switch__control","rl-switch__hint","rl-switch__label","rl-switch__track","rl-text-sm"],"criteria":[{"code":"7.1","why":"A button with the switch role and aria-checked written from the state on every render: the attribute cannot go stale, and the product's engine checks it is there."},{"code":"7.3","why":"A button toggles on Enter and Space and Tab reaches it; nothing is written by hand for the keyboard."},{"code":"3.1","why":"The state is told by the thumb's position as much as by the track's colour, and under forced colours by the system's palette."}],"replaces":["apps/site: the interrupteur demo, kept as teaching material","apps/extension: the hand-made toggles of the panel's settings, when it is on Svelte"],"related":["checkbox"]},{"name":"Text","slug":"text","level":"atom","status":"stable","surfaces":["app","extension","site"],"since":"2026-09","files":{"component":"components/Text/Text.svelte","doc":"components/Text/Text.md","stories":"components/Text/Text.stories.svelte","test":"components/Text/Text.test.ts"},"props":[{"name":"as","type":"'p' | 'span' | 'div'","required":false,"description":"The element: `p` for a sentence on its own line, `span` inside one, `div` for a block that holds more than text.","default":"'p'"},{"name":"size","type":"'xs' | 'sm' | 'base' | 'lg'","required":false,"description":"The type size, from the scale.","default":"'base'"},{"name":"tone","type":"'default' | 'soft' | 'success' | 'warning' | 'error'","required":false,"description":"The ink: `soft` for what supports, `success`, `warning` and `error` for a state the words already say.","default":"'default'"},{"name":"bold","type":"boolean","required":false,"description":"Bold, for a word that leads.","default":"false"},{"name":"mono","type":"boolean","required":false,"description":"The mono face, for a value: a selector, a code, an address.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only: a margin.","default":"''"},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["plain","soft","tones","inline"],"classes":["rl-attention","rl-c","rl-font-mono","rl-ink","rl-ink-soft","rl-leading-body","rl-nc","rl-text","rl-text--","rl-text--bold","rl-text--error","rl-text--lg","rl-text--mono","rl-text--sm","rl-text--soft","rl-text--success","rl-text--warning","rl-text--xs","rl-text-base","rl-text-lg","rl-text-sm","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"3.1","why":"A tone is an ink; the words carry the state, and the doc refuses a tone on a sentence that does not say it."},{"code":"10.4","why":"Sizes are from the scale in rem, so the text zooms with the browser's setting."}],"replaces":["apps/web: the text-sm text-ink-soft utility pairs on every screen's help lines and captions","apps/extension: the hand-written caption and hint classes of the panel's screens"],"patterns":[],"related":["prose","heading","eyebrow"]},{"name":"Tooltip","slug":"tooltip","level":"atom","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Tooltip/Tooltip.svelte","doc":"components/Tooltip/Tooltip.md","stories":"components/Tooltip/Tooltip.stories.svelte","test":"components/Tooltip/Tooltip.test.ts"},"props":[{"name":"text","type":"string","required":true,"description":"The description, one or two sentences."},{"name":"children","type":"Snippet","required":true,"description":"The term, as the trigger's text. It is the trigger's name; the bubble only describes it."}],"snippets":["children"],"stories":["inSentence","short"],"classes":["rl-action","rl-border-strong","rl-ink","rl-leading-body","rl-line-control","rl-line-strong","rl-radius","rl-space-1-5","rl-space-2","rl-space-3","rl-surface","rl-text-sm","rl-tooltip","rl-tooltip__box","rl-tooltip__bubble","rl-tooltip__trigger","rl-weight-regular","rl-z-drawer"],"criteria":[{"code":"10.13","why":"The bubble can be dismissed with Escape without moving the pointer or the focus, the pointer can enter it, and it stays while the trigger has the focus or the hover."},{"code":"7.1","why":"The bubble is the trigger's accessible description through aria-describedby, read at focus even while hidden; the trigger keeps its own name."},{"code":"7.3","why":"The trigger is a button: the keyboard reaches it and the description is read at focus, with no hover needed."}],"replaces":["apps/site: the infobulle demo, kept as teaching material"],"related":["kbd","inline-markup"]},{"name":"Accordion","slug":"accordion","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Accordion/Accordion.svelte","doc":"components/Accordion/Accordion.md","stories":"components/Accordion/Accordion.stories.svelte","test":"components/Accordion/Accordion.test.ts"},"props":[{"name":"items","type":"Item[]","required":true,"description":"The panels, in order."},{"name":"level","type":"2 | 3 | 4 | 5 | 6","required":false,"description":"The heading level of every title.","default":"3"},{"name":"open","type":"string[]","required":false,"description":"The ids of the panels open at first.","default":"[]"},{"name":"exclusive","type":"boolean","required":false,"description":"Opening a panel closes the others. Off by default: independent panels surprise nobody.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"panel","type":"Snippet<[Item]>","required":true,"description":"The content of one panel, given its item."}],"snippets":["panel"],"stories":["independent","exclusive","preset"],"classes":["rl-accordion","rl-disclosure"],"criteria":[{"code":"7.1","why":"Each panel is a Disclosure: a native button with aria-expanded and aria-controls, the state and the relation assistive technology already knows."},{"code":"9.1","why":"Every title is a heading at the level the page gives, so the set reads as a list of sections in the outline."},{"code":"12.8","why":"A folded panel carries hidden and its content leaves the tab order, whatever the number of panels."}],"replaces":["apps/site: the accordéon demo, kept as teaching material"],"patterns":[],"related":["disclosure","tabs"]},{"name":"Alert","slug":"alert","level":"molecule","status":"stable","surfaces":["site","app"],"since":"2026-08","files":{"component":"components/Alert/Alert.svelte","doc":"components/Alert/Alert.md","stories":"components/Alert/Alert.stories.svelte","test":"components/Alert/Alert.test.ts"},"props":[{"name":"tone","type":"Tone","required":false,"description":"What the message is: the word, the drawing and the ink follow.","default":"'info'"},{"name":"title","type":"string","required":false,"description":"An optional title. The tone's word stays in the accessible name in every case."},{"name":"live","type":"boolean","required":false,"description":"`true` when the message appears after load, in response to an action. False by default: a message already present has nothing to announce.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only: margins, never appearance.","default":"''"},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["info","success","warning","error","titled","liveError","liveSuccess"],"classes":["rl-alert","rl-alert--","rl-alert--error","rl-alert--info","rl-alert--success","rl-alert--warning","rl-alert__body","rl-alert__icon","rl-alert__message","rl-alert__word","rl-attention","rl-border","rl-c","rl-ink-soft","rl-nc","rl-radius","rl-space-1","rl-space-3","rl-space-4","rl-sr-only","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"3.1","why":"Every tone carries a readable word (Erreur, Attention, Fait, Information) and a drawing; a red box with no word says nothing to whoever does not see the hue."},{"code":"7.1","why":"The ARIA role is chosen from the tone: alert interrupts, status waits for a pause, and a message present at load announces nothing."}],"replaces":["apps/web: three hand-built inline notices on the audit page, the report and the page view"],"patterns":["form-with-error-summary"],"related":["callout","icon"]},{"name":"AnswerButtons","slug":"answer-buttons","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/AnswerButtons/AnswerButtons.svelte","doc":"components/AnswerButtons/AnswerButtons.md","stories":"components/AnswerButtons/AnswerButtons.stories.svelte","test":"components/AnswerButtons/AnswerButtons.test.ts"},"props":[{"name":"answers","type":"Answer[]","required":true,"description":"The answers offered, in order."},{"name":"value","type":"string | null | undefined","required":false,"description":"The answer retained, if any.","default":"null"},{"name":"onanswer","type":"(value: string) => void","required":true,"description":"Called with the value of the answer pressed."},{"name":"labelledBy","type":"string","required":true,"description":"The id of the question, which names the group."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["none","retained","three"],"classes":["rl-answers","rl-answers__button","rl-border-strong","rl-hover","rl-ink","rl-line-strong","rl-on-fill","rl-radius","rl-space-2","rl-space-3","rl-space-4","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"7.1","why":"A group of toggle buttons: role group named by the question, aria-pressed on each, a real button element that the keyboard operates."},{"code":"3.1","why":"The retained answer is said by aria-pressed and drawn by a fill, a change of shape; it never rests on a hue alone."},{"code":"12.10","why":"The single-key shortcut of each answer is printed on the button, next to what it drives."}],"replaces":["apps/extension: the five families of answer buttons (verdict, answer, final answer, inline answer, question 31) the wizards wrote"],"related":["question","kbd","wizard-footer"]},{"name":"AutoVerdict","slug":"auto-verdict","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/AutoVerdict/AutoVerdict.svelte","doc":"components/AutoVerdict/AutoVerdict.md","stories":"components/AutoVerdict/AutoVerdict.stories.svelte","test":"components/AutoVerdict/AutoVerdict.test.ts"},"props":[{"name":"status","type":"StatutDeCritere","required":true},{"name":"source","type":"'engine' | 'page' | 'auditor'","required":false,"description":"Who concluded: the detection engine, the page's own markup, or the auditor's own answers summed elsewhere, as a criterion concluded element by element on a walk.","default":"'engine'"},{"name":"findings","type":"Finding[]","required":false,"default":"[]"},{"name":"onshow","type":"((selector: string) => void) | undefined","required":false,"description":"Called with the selector of the finding whose \"Montrer\" was pressed."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":false,"description":"What replaces the closing sentence, when the wizard has something else to say."}],"snippets":["children"],"stories":["fromPage","fromEngine","ownSentence","fromAuditor"],"classes":["rl-action","rl-auto-verdict","rl-auto-verdict--","rl-auto-verdict--c","rl-auto-verdict--na","rl-auto-verdict--nc","rl-auto-verdict__findings","rl-auto-verdict__icon","rl-auto-verdict__note","rl-auto-verdict__show","rl-auto-verdict__verdict","rl-border","rl-c","rl-ink","rl-ink-soft","rl-line-strong","rl-na","rl-nc","rl-radius","rl-space-1","rl-space-1-5","rl-space-2","rl-space-3","rl-space-5","rl-sunken","rl-text-sm","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"3.1","why":"The status is the word in bold; the line and the drawing's ink confirm it and never carry it alone."},{"code":"7.1","why":"Showing the element is a real button, with the selector as data, not a link to nowhere."}],"replaces":["apps/extension: insererOrigineDuVerdict and the auto-verdict blocks each wizard wrote"],"related":["question","status-chip","answer-buttons"]},{"name":"Banner","slug":"banner","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Banner/Banner.svelte","doc":"components/Banner/Banner.md","stories":"components/Banner/Banner.stories.svelte","test":"components/Banner/Banner.test.ts"},"props":[{"name":"label","type":"string","required":true,"description":"The word that says what the banner is: \"Bêta\", \"Maintenance\". It names the aside."},{"name":"dismissKey","type":"string","required":true,"description":"The storage key that remembers a dismissal. Change it when the information changes, and the banner comes back once."},{"name":"dismissLabel","type":"string","required":false,"description":"The close button's name.","default":"\"Masquer l'annonce\""},{"name":"children","type":"Snippet","required":true,"description":"The announcement: a sentence or two, with a link when there is more to read."}],"snippets":["children"],"stories":["beta","maintenance"],"classes":["rl-action","rl-banner","rl-banner__body","rl-banner__label","rl-banner__text","rl-border","rl-border-strong","rl-ink","rl-line","rl-space-2","rl-space-3","rl-space-4","rl-sunken","rl-text-sm","rl-text-xs","rl-tracking-label","rl-weight-bold"],"criteria":[{"code":"12.6","why":"A named aside: the banner is a landmark that can be reached or skipped, and it does not impose itself on the reading order."},{"code":"7.5","why":"No alert role and no live region: nothing in a banner is urgent, and announcing it would interrupt the reading for what does not deserve it."},{"code":"3.1","why":"The word carries the status and the line on the left only confirms it."},{"code":"11.9","why":"The close button is a real button named 'Masquer l'annonce', reachable at the keyboard."}],"replaces":["apps/web: lib/components/BandeauBeta.svelte, gone with the lab layout's port"],"patterns":["app-shell"],"related":["alert","icon-button"]},{"name":"Breadcrumb","slug":"breadcrumb","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Breadcrumb/Breadcrumb.svelte","doc":"components/Breadcrumb/Breadcrumb.md","stories":"components/Breadcrumb/Breadcrumb.stories.svelte","test":"components/Breadcrumb/Breadcrumb.test.ts"},"props":[{"name":"items","type":"Item[]","required":true,"description":"The steps, from the root to the current page."},{"name":"label","type":"string","required":false,"description":"The landmark's name.","default":"\"Fil d'Ariane\""},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["plain","deep"],"classes":["rl-action","rl-breadcrumb","rl-breadcrumb__current","rl-breadcrumb__item","rl-breadcrumb__link","rl-breadcrumb__list","rl-breadcrumb__separator","rl-ink","rl-ink-soft","rl-space-1","rl-space-2","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"12.6","why":"A navigation landmark with its own name, so the path can be reached or skipped like the other regions."},{"code":"9.3","why":"The steps are an ordered list with its role restored, so the count and the rank are announced."},{"code":"3.1","why":"The current page is told by aria-current, not only by its weight; the links by their underline, not only by their colour."}],"replaces":["apps/web: lib/components/Breadcrumb.svelte, gone","apps/site: the fil-d-ariane demo, kept as teaching material"],"patterns":["app-shell"],"related":["link"]},{"name":"Callout","slug":"callout","level":"molecule","status":"stable","surfaces":["site"],"since":"2026-08","files":{"component":"components/Callout/Callout.svelte","doc":"components/Callout/Callout.md","stories":"components/Callout/Callout.stories.svelte","test":"components/Callout/Callout.test.ts"},"props":[{"name":"rule","type":"'strong' | 'subtle'","required":false,"description":"`strong` for a block that carries an argument, `subtle` for an aside.","default":"'strong'"},{"name":"status","type":"'NC' | 'C'","required":false,"description":"Borrows the colour of an audit status. Always with the word inside the block."},{"name":"aside","type":"boolean","required":false,"description":"Renders an `aside`: for content truly beside the point.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only, margins and the like.","default":"''"},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["strong","subtle","aside","nc","c"],"classes":["rl-border","rl-border-strong","rl-c","rl-callout","rl-callout--","rl-callout--c","rl-callout--nc","rl-callout--subtle","rl-line","rl-line-strong","rl-nc","rl-radius","rl-space-6","rl-space-8"],"criteria":[{"code":"3.1","why":"The status changes only the line; a status colour on the frame never carries the information alone, the words inside do."},{"code":"3.2","why":"No fill, so there is no second background whose contrast would have to be checked."}],"patterns":[],"related":["alert"]},{"name":"Card","slug":"card","level":"molecule","status":"stable","surfaces":["site"],"since":"2026-08","files":{"component":"components/Card/Card.svelte","doc":"components/Card/Card.md","stories":"components/Card/Card.stories.svelte","test":"components/Card/Card.test.ts"},"props":[{"name":"href","type":"string","required":false,"description":"The destination; the whole card becomes the link."},{"name":"listItem","type":"boolean","required":false,"description":"Forces the `li` wrapper. Outside a list grid only: inside one, the grid says so."},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["plain","link","inList"],"classes":["rl-bg","rl-border","rl-card","rl-card__body","rl-card__link","rl-line","rl-space-6"],"criteria":[{"code":"6.1","why":"When the card is a link, the whole card is the link and its text is the name: a clickable zone the size of a word is a defect of ergonomics as much as of accessibility."},{"code":"9.3","why":"Inside a list grid the card is a list item; the grid tells it, so a ul can never hold an article by mistake."}],"patterns":[],"related":["card-grid"]},{"name":"CardGrid","slug":"card-grid","level":"molecule","status":"stable","surfaces":["site"],"since":"2026-08","files":{"component":"components/CardGrid/CardGrid.svelte","doc":"components/CardGrid/CardGrid.md","stories":"components/CardGrid/CardGrid.stories.svelte","test":"components/CardGrid/CardGrid.test.ts"},"props":[{"name":"columns","type":"2 | 3 | 4","required":false,"description":"Columns from the small breakpoint; one column below it.","default":"3"},{"name":"list","type":"boolean","required":false,"description":"Renders a `ul` announced as a list: as soon as the cards are an enumeration.","default":"false"},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["three","two","four","list"],"classes":["rl-border","rl-card-grid","rl-card-grid--","rl-card-grid--2","rl-card-grid--3","rl-card-grid--4"],"criteria":[{"code":"9.3","why":"An enumeration of cards is a real list (ul with role list), announced with its count; the grid passes the fact to its cards so the markup cannot be half a list."}],"patterns":[],"related":["card"]},{"name":"CommentField","slug":"comment-field","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/CommentField/CommentField.svelte","doc":"components/CommentField/CommentField.md","stories":"components/CommentField/CommentField.stories.svelte","test":"components/CommentField/CommentField.test.ts"},"props":[{"name":"id","type":"string","required":true},{"name":"label","type":"string","required":true,"description":"The label: \"Commentaire sur ce cadre\"."},{"name":"value","type":"string | undefined","required":false,"description":"The text; bindable, absent reads as empty. No fallback: a page binding a note not written yet binds undefined, which Svelte refuses on a prop that has one.","bindable":true},{"name":"placeholder","type":"string","required":false,"default":"'Ce que vous avez constaté…'"},{"name":"optional","type":"boolean","required":false,"description":"Whether the field is optional, said in the label.","default":"true"},{"name":"hint","type":"string | undefined","required":false,"description":"A line under the label: the keys, what the note is for."},{"name":"rows","type":"number","required":false,"default":"2"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["empty","filled","withHint"],"classes":["rl-border","rl-comment","rl-comment__field","rl-comment__hint","rl-comment__label","rl-comment__optional","rl-ink","rl-ink-soft","rl-line-control","rl-radius","rl-space-1","rl-space-2","rl-surface","rl-text-sm","rl-text-xs","rl-weight-bold","rl-weight-regular"],"criteria":[{"code":"11.1","why":"The textarea has a visible label tied by for and id, given by the page so two fields never share one."},{"code":"11.2","why":"The label says what the note is about, the element judged, and says when it is optional."},{"code":"11.10","why":"The hint under the label says the keys before the auditor types: Enter advances, Shift and Enter breaks the line."}],"replaces":["apps/extension: the comment textarea each wizard wrote with inline styles and the shared id a-11y-wizard-comment"],"related":["field","answer-buttons","wizard-footer"]},{"name":"ConfirmDialog","slug":"confirm-dialog","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/ConfirmDialog/ConfirmDialog.svelte","doc":"components/ConfirmDialog/ConfirmDialog.md","stories":"components/ConfirmDialog/ConfirmDialog.stories.svelte","test":"components/ConfirmDialog/ConfirmDialog.test.ts"},"props":[{"name":"open","type":"boolean | undefined","required":false,"description":"Shown or not; absent reads as closed. Bindable, set back to false when the dialog closes by any means.","bindable":true},{"name":"title","type":"string","required":true,"description":"The question, as the heading."},{"name":"confirmLabel","type":"string","required":true,"description":"The label of the button that does it: a verb that says the effect (\"Retirer la page\"), never \"OK\"."},{"name":"cancelLabel","type":"string","required":false,"description":"The label of the button that does not.","default":"'Annuler'"},{"name":"destructive","type":"boolean","required":false,"description":"The action damages something: the confirm button takes the destructive variant.","default":"false"},{"name":"onconfirm","type":"() => void","required":false,"description":"Called when the user confirmed."},{"name":"oncancel","type":"() => void","required":false,"description":"Called when the user did not: the cancel button, the close button or Escape."},{"name":"children","type":"Snippet","required":true,"description":"The consequence, as the body: what the action does, what it does not undo."}],"snippets":["children"],"stories":["plain","destructive"],"classes":["rl-confirm-dialog__form","rl-space-3"],"criteria":[{"code":"11.9","why":"The confirm button is labelled by the caller with a verb that says the effect; the component refuses to render one without a label, so no dialog closes on an OK."},{"code":"7.3","why":"Two real buttons in a dialog form: Enter confirms, Escape cancels, Tab walks between them, and the browser holds the focus inside."},{"code":"12.8","why":"The initial focus lands on the cancel button and the browser returns it to the opener afterwards, so a careless Enter does nothing and the order resumes where it was."}],"replaces":["apps/web: the seven window.confirm() calls and the inline confirmation panel of the extension page"],"patterns":["destructive-confirmation"],"related":["dialog","button"]},{"name":"ConformityRate","slug":"conformity-rate","level":"molecule","status":"stable","surfaces":["app"],"since":"2026-08","files":{"component":"components/ConformityRate/ConformityRate.svelte","doc":"components/ConformityRate/ConformityRate.md","stories":"components/ConformityRate/ConformityRate.stories.svelte","test":"components/ConformityRate/ConformityRate.test.ts"},"props":[{"name":"rate","type":"number | null","required":true,"description":"The rate in percent, or `null` when the audit cannot publish one."},{"name":"unavailableReason","type":"string | null","required":false,"description":"What prevents publishing, word for word from the official grid.","default":"null"},{"name":"suffix","type":"string","required":false,"description":"What the figure qualifies, read after it: \"de conformité\".","default":"''"},{"name":"decimals","type":"number","required":false,"description":"A site's average carries one decimal; a page's rate none.","default":"0"}],"snippets":[],"stories":["high","middle","low","average","unavailable","unavailableBare"],"classes":["rl-c","rl-conformity-rate","rl-conformity-rate--","rl-conformity-rate--high","rl-conformity-rate--low","rl-conformity-rate--middle","rl-conformity-rate--unavailable","rl-conformity-rate__figure","rl-ink","rl-ink-soft","rl-nc","rl-weight-bold"],"criteria":[{"code":"3.1","why":"A symbol (● ◐ ○) says the band; the colour only confirms it."}],"replaces":["apps/web: five renderings of the rate across the dashboard, the site page, the audit page and the report"],"patterns":[],"related":["status-chip","stat"]},{"name":"CriteriaRecap","slug":"criteria-recap","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/CriteriaRecap/CriteriaRecap.svelte","doc":"components/CriteriaRecap/CriteriaRecap.md","stories":"components/CriteriaRecap/CriteriaRecap.stories.svelte","test":"components/CriteriaRecap/CriteriaRecap.test.ts"},"props":[{"name":"criteria","type":"Criterion[]","required":true},{"name":"current","type":"string | undefined","required":false,"description":"The criterion the screen is on."},{"name":"reachable","type":"string[]","required":false,"description":"The criteria the wizard can jump to; their pills are buttons.","default":"[]"},{"name":"onjump","type":"((id: string) => void) | undefined","required":false},{"name":"label","type":"string","required":false,"description":"The list's name.","default":"'Critères du thème'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["plain","reachable"],"classes":["rl-border","rl-c","rl-font-mono","rl-hover","rl-ink","rl-ink-soft","rl-line","rl-line-control","rl-na","rl-nc","rl-radius","rl-recap","rl-recap__pill","rl-recap__pill--","rl-recap__pill--c","rl-recap__pill--na","rl-recap__pill--nc","rl-recap__pill--nt","rl-space-1","rl-space-1-5","rl-space-2","rl-space-4","rl-sr-only","rl-text-xs"],"criteria":[{"code":"3.1","why":"Each pill carries the status drawing and a hidden word; the ink never says the status alone, and the current pill is underlined, not tinted."},{"code":"7.1","why":"A pill the wizard can jump to is a real button with aria-current; a pill it cannot is plain, so nothing looks pressable and is not."}],"replaces":["apps/extension: renderCriteriaRecap and renderCriterionBadge, with their inline styles"],"related":["status-chip","status-counters","wizard-frame","jump-list"]},{"name":"DataTable","slug":"data-table","level":"molecule","status":"stable","surfaces":["site"],"since":"2026-08","files":{"component":"components/DataTable/DataTable.svelte","doc":"components/DataTable/DataTable.md","stories":"components/DataTable/DataTable.stories.svelte","test":"components/DataTable/DataTable.test.ts"},"props":[{"name":"caption","type":"string","required":true,"description":"The table's title. Required: that is criterion 5.4."},{"name":"captionHidden","type":"boolean","required":false,"description":"Hides the caption on screen without removing it from the accessible name.","default":"false"},{"name":"columns","type":"Column[]","required":true,"description":"The columns, in order; `key` reads the row, `label` is the header."},{"name":"rows","type":"Array<Record<string, unknown>>","required":true,"description":"The records, one per row."},{"name":"rowKey","type":"string","required":true,"description":"The key that identifies a row, so Svelte never mixes two up."},{"name":"cell","type":"Snippet<[{ row: Record<string, unknown>; column: Column }]>","required":false,"description":"Renders a cell when the raw value is not enough."},{"name":"empty","type":"Snippet","required":false,"description":"Shown instead of the table when there are no rows."}],"snippets":["cell","empty"],"stories":["plain","withCell","empty","captionHidden"],"classes":["rl-border","rl-border-strong","rl-data-table","rl-data-table__caption","rl-data-table__cell","rl-data-table__cell--numeric","rl-data-table__header","rl-data-table__header--numeric","rl-ink","rl-ink-soft","rl-line","rl-line-strong","rl-space-2","rl-space-3","rl-sr-only","rl-text-sm","rl-text-xs","rl-tracking-label","rl-weight-bold"],"criteria":[{"code":"5.4","why":"The caption is a required prop: a data table has a title that tells it from the others on the page."},{"code":"5.6","why":"Every column header is a th with scope, so every cell knows what it is about."},{"code":"12.9","why":"The table sits in the scroll region component, focusable and named, so a wide one can be scrolled with the keyboard alone."}],"patterns":["data-table-page"],"related":["scroll-region"]},{"name":"DefinitionList","slug":"definition-list","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/DefinitionList/DefinitionList.svelte","doc":"components/DefinitionList/DefinitionList.md","stories":"components/DefinitionList/DefinitionList.stories.svelte","test":"components/DefinitionList/DefinitionList.test.ts"},"props":[{"name":"items","type":"Item[]","required":true,"description":"The pairs, in order."},{"name":"columns","type":"1 | 2 | 3","required":false,"description":"The columns of pairs when there is room.","default":"2"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"value","type":"Snippet<[Item]>","required":false,"description":"Renders a value when a string is not enough, given its item."}],"snippets":["value"],"stories":["plain","rendered","single"],"classes":["rl-definition-list","rl-definition-list--","rl-definition-list--2","rl-definition-list--3","rl-definition-list__pair","rl-definition-list__term","rl-definition-list__value","rl-ink","rl-ink-soft","rl-space-1","rl-space-4","rl-space-6","rl-text-xs","rl-tracking-label","rl-weight-bold"],"criteria":[{"code":"8.9","why":"A definition list is used for what it is, pairs of a term and a value, where a table would have nothing to cross and divs would have no structure."},{"code":"9.3","why":"The list is a real dl with dt and dd, so each pair is read as a pair."}],"replaces":["apps/web: the property blocks of the audit, site and admin pages written as divs, once the app is ported"],"related":["table","stat-group"]},{"name":"Dialog","slug":"dialog","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Dialog/Dialog.svelte","doc":"components/Dialog/Dialog.md","stories":"components/Dialog/Dialog.stories.svelte","test":"components/Dialog/Dialog.test.ts"},"props":[{"name":"open","type":"boolean | undefined","required":false,"description":"Shown or not; absent reads as closed. Bindable, and set back to false when the dialog closes by any means: Escape, the close button, an action.","bindable":true},{"name":"title","type":"string","required":true,"description":"The heading, which names the dialog. A question or a noun phrase, French."},{"name":"size","type":"'sm' | 'md' | 'lg'","required":false,"description":"`sm` for a question, `md` for a short form, `lg` for a list or a table.","default":"'sm'"},{"name":"onclose","type":"(returnValue: string) => void","required":false,"description":"Called after the dialog closed, with the value of the button that closed it, or an empty string for Escape and the close button."},{"name":"closeLabel","type":"string","required":false,"description":"The close button's label.","default":"'Fermer'"},{"name":"class","type":"string","required":false,"description":"Layout classes only: margins, never appearance.","default":"''"},{"name":"children","type":"Snippet","required":true,"description":"The body."},{"name":"actions","type":"Snippet","required":false,"description":"The actions at the foot: buttons of type submit in a `form method=\"dialog\"`, whose value comes back through `onclose`."}],"snippets":["children","actions"],"stories":["question","form"],"classes":["rl-backdrop","rl-border","rl-border-strong","rl-control-sm","rl-dialog","rl-dialog--","rl-dialog--lg","rl-dialog--md","rl-dialog--sm","rl-dialog-width","rl-dialog__actions","rl-dialog__body","rl-dialog__close","rl-dialog__close-button","rl-dialog__title","rl-ink","rl-ink-soft","rl-line-control","rl-line-strong","rl-radius","rl-space-12","rl-space-3","rl-space-4","rl-space-5","rl-space-8","rl-surface","rl-text-xl","rl-tracking-tight","rl-weight-bold"],"criteria":[{"code":"7.1","why":"The native element carries the dialog role and the name comes from its heading through aria-labelledby; nothing is declared by hand, so nothing can be declared wrong."},{"code":"7.3","why":"showModal holds the focus inside, Escape closes, the close button is a real button: the whole dialog is driven from the keyboard by the browser itself."},{"code":"12.8","why":"When the dialog closes, the browser puts the focus back on the element that opened it, so the tab order resumes where the user left it."},{"code":"12.9","why":"The focus is held inside while the dialog is open, and Escape always releases it: a trap with an exit is not a trap."}],"replaces":["apps/site: the modale demo, kept as teaching material; apps/web: the inline confirmation panel of the audit page and the eight confirm() calls, once the app is ported","apps/extension: openPanelModal and its hand-made focus trap, once the panel is on Svelte"],"patterns":["destructive-confirmation"],"related":["confirm-dialog","button","live-region"]},{"name":"Disclosure","slug":"disclosure","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Disclosure/Disclosure.svelte","doc":"components/Disclosure/Disclosure.md","stories":"components/Disclosure/Disclosure.stories.svelte","test":"components/Disclosure/Disclosure.test.ts"},"props":[{"name":"title","type":"string","required":true,"description":"The title, as the button's text. A noun phrase or a question."},{"name":"level","type":"2 | 3 | 4 | 5 | 6","required":false,"description":"The heading level the title takes in the page's outline.","default":"3"},{"name":"open","type":"boolean","required":false,"description":"Whether the panel is shown; bindable.","default":"false","bindable":true},{"name":"ontoggle","type":"(open: boolean) => void","required":false,"description":"Called after each toggle, with the new state."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":true,"description":"The panel."}],"snippets":["children"],"stories":["closed","open","level"],"classes":["rl-action","rl-border","rl-disclosure","rl-disclosure__button","rl-disclosure__chevron","rl-disclosure__heading","rl-disclosure__panel","rl-duration-fast","rl-ease","rl-ink","rl-ink-soft","rl-line","rl-space-1","rl-space-2-5","rl-space-3","rl-space-4","rl-space-8","rl-text-base","rl-weight-bold"],"criteria":[{"code":"7.1","why":"A native button with aria-expanded and aria-controls: the state and the relation are the ones assistive technology already knows, nothing is invented."},{"code":"7.3","why":"A button toggles on Enter and Space by itself; there is no key handler to get wrong."},{"code":"9.1","why":"The button sits inside the heading, so the title keeps its level in the page's outline and a reader jumps between panels by headings."},{"code":"12.8","why":"A folded panel carries hidden: its links leave the tab order with it, so Tab never lands on content nobody sees."}],"replaces":["apps/web: the hand-built criterion rows that fold their detail, once the app is ported","apps/extension: the derogation panel and the folded theme rows of the panel, when it is on Svelte"],"patterns":[],"related":["accordion","tabs"]},{"name":"EmptyState","slug":"empty-state","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/EmptyState/EmptyState.svelte","doc":"components/EmptyState/EmptyState.md","stories":"components/EmptyState/EmptyState.stories.svelte","test":"components/EmptyState/EmptyState.test.ts"},"props":[{"name":"title","type":"string","required":true,"description":"What there is none of, as a sentence: \"Aucun audit pour l'instant.\""},{"name":"level","type":"2 | 3 | 4","required":false,"description":"The heading level, from the page's outline.","default":"2"},{"name":"icon","type":"IconName","required":false,"description":"A drawing above the title, hidden."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":false,"description":"What to do about it, in a sentence or two."},{"name":"action","type":"Snippet","required":false,"description":"The action, when there is one: a Button."}],"snippets":["children","action"],"stories":["withAction","search","withIcon"],"classes":["rl-border","rl-empty-state","rl-empty-state__action","rl-empty-state__icon","rl-empty-state__text","rl-empty-state__title","rl-ink","rl-ink-soft","rl-line-strong","rl-radius","rl-space-10","rl-space-2","rl-space-3","rl-space-6","rl-text-lg","rl-weight-bold"],"criteria":[{"code":"9.1","why":"The message is a heading at the level the page gives, so a reader jumping by headings finds the empty list where the list would be."},{"code":"11.9","why":"The action, when there is one, is a Button with a verb; a link that says 'ici' has no place here."}],"replaces":["apps/web: the soft paragraphs under empty lists of the lab, the projects and the admin pages, once the app is ported"],"patterns":["empty-state"],"related":["button","data-table"]},{"name":"ErrorSummary","slug":"error-summary","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/ErrorSummary/ErrorSummary.svelte","doc":"components/ErrorSummary/ErrorSummary.md","stories":"components/ErrorSummary/ErrorSummary.stories.svelte","test":"components/ErrorSummary/ErrorSummary.test.ts"},"props":[{"name":"errors","type":"ErrorItem[]","required":true,"description":"The errors, in the form's order. Nothing renders when empty. Set it on submit, not on input: the summary takes the focus each time."},{"name":"title","type":"string","required":false,"description":"The heading; by default \"Il y a un problème\" or \"Il y a N problèmes\"."},{"name":"level","type":"2 | 3 | 4","required":false,"description":"The heading level, from the form's place in the outline.","default":"2"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["form"],"classes":["rl-border-strong","rl-error-summary","rl-error-summary__link","rl-error-summary__list","rl-error-summary__title","rl-focus","rl-focus-offset","rl-focus-width","rl-nc","rl-radius","rl-space-1-5","rl-space-2","rl-space-4","rl-space-5","rl-text-lg","rl-weight-bold"],"criteria":[{"code":"11.10","why":"The summary is how the form reports its input control: every error is listed, tied to its field, and the focus goes to the list at submission."},{"code":"11.11","why":"The entries repeat the field messages, which say what to write, so the correction is suggested where the errors are counted."},{"code":"7.5","why":"The summary takes the focus, so it is not a status message and carries neither role alert nor aria-live; announcing it twice would be the fault."},{"code":"12.8","why":"Each entry is a link that puts the focus on the field at fault, so the correction path follows the reading order."}],"replaces":["apps/site: the récapitulatif-d-erreurs demo, kept as teaching material","apps/web: the error paragraphs at the top of the auth and settings forms, once the app is ported"],"patterns":["form-with-error-summary"],"related":["field","live-region","alert"]},{"name":"Field","slug":"field","level":"molecule","status":"stable","surfaces":["site","app"],"since":"2026-08","files":{"component":"components/Field/Field.svelte","doc":"components/Field/Field.md","stories":"components/Field/Field.stories.svelte","test":"components/Field/Field.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The control's id; the label's `for` is the same value."},{"name":"label","type":"string","required":true,"description":"The visible label, a noun."},{"name":"type","type":"'text' | 'email' | 'password' | 'url' | 'number' | 'tel' | 'search'","required":false,"description":"The input type; a choice among options is another component.","default":"'text'"},{"name":"value","type":"string | undefined","required":false,"description":"The value, bindable; absent reads as empty."},{"name":"name","type":"string","required":false,"description":"The name sent with the form."},{"name":"required","type":"boolean","required":false,"description":"Required: the word goes into the accessible name and the attribute onto the control.","default":"false"},{"name":"hint","type":"string | undefined","required":false,"description":"A help sentence, announced with the field."},{"name":"error","type":"string | undefined","required":false,"description":"The error message; its presence alone marks the field invalid. Undefined when the field is fine, so a form passes what its validation computed."},{"name":"autocomplete","type":"FullAutoFill","required":false,"description":"Typed as the closed list of the standard rather than a string: autocomplete is criterion 11.13, and a typo in it is silent, the browser ignores the unknown value and the field loses its autofill with nothing to say so."},{"name":"placeholder","type":"string","required":false,"description":"A hint inside the control, never the only place a format is said."},{"name":"multiline","type":"boolean","required":false,"description":"Renders a textarea rather than an input.","default":"false"},{"name":"rows","type":"number","required":false,"description":"The textarea's visible rows.","default":"4"},{"name":"minlength","type":"number","required":false,"description":"The shortest value accepted; the browser checks it before the server does."},{"name":"maxlength","type":"number","required":false,"description":"The longest value accepted."},{"name":"inputmode","type":"HTMLInputAttributes['inputmode']","required":false,"description":"The keyboard a phone opens: `numeric` for a code."},{"name":"autofocus","type":"boolean","required":false,"description":"Takes the focus at load. Justified on a page that holds this one field and nothing else, such as the verification code; nowhere else.","default":"false"},{"name":"code","type":"boolean","required":false,"description":"A code the user copies or types once: the mono face, spaced, larger.","default":"false"},{"name":"children","type":"Snippet","required":false,"description":"Anything to render under the field, such as a character count."}],"snippets":["children"],"stories":["text","email","required","withHint","error","multiline","code"],"classes":["rl-action","rl-border","rl-field","rl-field__control","rl-field__control--code","rl-field__error","rl-field__hint","rl-field__label","rl-field__mark","rl-font-mono","rl-ink","rl-ink-soft","rl-line-control","rl-nc","rl-radius","rl-space-1-5","rl-space-2","rl-space-3","rl-sr-only","rl-surface","rl-text-base","rl-text-lg","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"11.1","why":"for and id come from the same prop, so a label cannot be left unassociated."},{"code":"11.2","why":"The label is visible and never replaced by a placeholder, which disappears at the first keystroke."},{"code":"11.10","why":"Required is in the accessible name and in the attribute; the error is tied to the field with aria-describedby and aria-invalid."},{"code":"11.13","why":"autocomplete is typed as the closed list of the standard, so a typo cannot silently switch autofill off."}],"replaces":["apps/web: sixteen hand-built label and input pairs with help text in three sizes"],"patterns":["form-with-error-summary"],"related":["button","alert","icon"]},{"name":"FilterGroup","slug":"filter-group","level":"molecule","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/FilterGroup/FilterGroup.svelte","doc":"components/FilterGroup/FilterGroup.md","stories":"components/FilterGroup/FilterGroup.stories.svelte","test":"components/FilterGroup/FilterGroup.test.ts"},"props":[{"name":"label","type":"string","required":true,"description":"The visible label that names the group: \"Statut\", \"Niveau\"."},{"name":"filters","type":"Filter[]","required":true,"description":"The filters, in order. With `exclusive`, the first one is the one that shows everything."},{"name":"selected","type":"string[]","required":false,"description":"The values pressed; bindable.","default":"[]","bindable":true},{"name":"exclusive","type":"boolean","required":false,"description":"One filter at a time, and one always pressed.","default":"false"},{"name":"onchange","type":"(selected: string[]) => void","required":false,"description":"Called with the values pressed after each press."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["exclusive","several"],"classes":["rl-border","rl-filter-group","rl-filter-group__count","rl-filter-group__filter","rl-filter-group__filters","rl-filter-group__label","rl-hover","rl-ink","rl-line-control","rl-line-strong","rl-on-fill","rl-radius","rl-space-1","rl-space-1-5","rl-space-2","rl-space-3","rl-target-min","rl-text-sm","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"7.1","why":"Native buttons with aria-pressed in a group named by its visible label: the state and the group are the ones assistive technology knows."},{"code":"3.1","why":"A pressed filter is told by aria-pressed and the fill with the bold, not by a hue on an outline."},{"code":"11.1","why":"The group's label is visible and tied by aria-labelledby, so the filters are heard as 'Statut, groupe'."}],"replaces":["apps/web: the row of status filter buttons above the audits list, written as links with a current class"],"patterns":["filter-group"],"related":["radio-group","select","live-region","chip"]},{"name":"JumpList","slug":"jump-list","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/JumpList/JumpList.svelte","doc":"components/JumpList/JumpList.md","stories":"components/JumpList/JumpList.stories.svelte","test":"components/JumpList/JumpList.test.ts"},"props":[{"name":"items","type":"Item[]","required":true},{"name":"current","type":"number","required":true},{"name":"onjump","type":"(index: number) => void","required":true},{"name":"summary","type":"string","required":true,"description":"The disclosure's title: what the list holds, \"12 éléments, 3 restants\"."},{"name":"open","type":"boolean","required":false,"description":"Open at first; closed by default so the list never buries the question. Bindable.","default":"false","bindable":true},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["closed","open"],"classes":["rl-c","rl-derogation","rl-font-mono","rl-hover","rl-ink","rl-ink-soft","rl-jump","rl-jump__icon","rl-jump__item","rl-jump__item--","rl-jump__item--a-revoir","rl-jump__item--c","rl-jump__item--deroge","rl-jump__item--nc","rl-jump__label","rl-jump__list","rl-jump__rank","rl-nc","rl-space-1-5","rl-space-2","rl-text-sm","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"7.1","why":"One real button per element, named by its rank, its label and its status, with aria-current on the current one."},{"code":"12.8","why":"Collapsed by default under a disclosure, the list never separates the question from its answers in the tab order."},{"code":"3.1","why":"The status is in the button's name and drawn by the icon; the current element is a line and the bold, not a hue."}],"replaces":["apps/extension: renderListeSaut, the jump list of the resume module"],"related":["disclosure","criteria-recap","resume-banner"]},{"name":"Menu","slug":"menu","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Menu/Menu.svelte","doc":"components/Menu/Menu.md","stories":"components/Menu/Menu.stories.svelte","test":"components/Menu/Menu.test.ts"},"props":[{"name":"label","type":"string","required":true,"description":"Names the button, when it shows only the dots, and the menu: \"Actions pour camille@exemple.fr\"."},{"name":"items","type":"Item[]","required":true,"description":"The commands, in order."},{"name":"onselect","type":"(id: string) => void","required":true,"description":"Called with the chosen command's id, after the menu closed and the focus returned to the button."},{"name":"children","type":"Snippet","required":false,"description":"The button's visible text. Without it the button shows three dots and is named by the label."},{"name":"align","type":"'start' | 'end'","required":false,"description":"Which edge of the button the menu aligns with.","default":"'start'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":["children"],"stories":["dots","labelled","withDisabled"],"classes":["rl-border","rl-border-strong","rl-control-sm","rl-hover","rl-ink","rl-ink-soft","rl-line-control","rl-line-strong","rl-menu","rl-menu__item","rl-menu__item--destructive","rl-menu__list","rl-menu__list--","rl-menu__list--end","rl-menu__list--start","rl-menu__trigger","rl-menu__trigger--dots","rl-nc","rl-radius","rl-space-1","rl-space-2","rl-space-3","rl-surface","rl-text-sm","rl-weight-bold","rl-z-drawer"],"criteria":[{"code":"7.1","why":"The button says it opens a menu with aria-haspopup and aria-expanded; the menu carries role menu with a name, the entries role menuitem in items with role none."},{"code":"7.3","why":"The arrows, Home, End and Escape are handled; the first letters find an entry; the focus returns to the button when the menu closes."},{"code":"12.8","why":"One entry is in the tab order at a time, so the group costs one Tab stop, and Tab leaves the menu without taking the focus back."},{"code":"12.10","why":"The first-letter search is a single-key shortcut bound to the open menu with the focus inside it, and to nothing else."}],"replaces":["apps/web: lib/components/MenuActions.svelte and the row menu that used it, gone","apps/site: the bouton-menu demo, kept as teaching material"],"patterns":["data-table-page"],"related":["icon-button","disclosure","button"]},{"name":"OTPField","slug":"otp-field","level":"molecule","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/OTPField/OTPField.svelte","doc":"components/OTPField/OTPField.md","stories":"components/OTPField/OTPField.stories.svelte","test":"components/OTPField/OTPField.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The control's id; the label's `for` is the same value."},{"name":"label","type":"string","required":false,"description":"The visible label.","default":"'Code de vérification'"},{"name":"value","type":"string | undefined","required":false,"description":"The value, bindable; absent reads as empty.","bindable":true},{"name":"name","type":"string","required":false,"description":"The name sent with the form."},{"name":"length","type":"number","required":false,"description":"The number of digits: the longest value accepted.","default":"6"},{"name":"required","type":"boolean","required":false,"description":"Required, which a code always is on its page.","default":"true"},{"name":"hint","type":"string | undefined","required":false,"description":"A help sentence: where the code comes from, how long it lasts."},{"name":"error","type":"string | undefined","required":false,"description":"The error message; its presence alone marks the field invalid."},{"name":"autofocus","type":"boolean","required":false,"description":"Takes the focus at load: justified on a page that holds this one field and nothing else.","default":"false"}],"snippets":[],"stories":["code","seven","error"],"classes":["rl-otp-field"],"criteria":[{"code":"11.1","why":"Field's label, tied by for and id."},{"code":"11.13","why":"autocomplete is one-time-code: the system offers the code it just received, and the user types nothing."},{"code":"11.10","why":"The error and the hint are tied to the control as Field ties them; the value stays for correction."}],"replaces":["apps/web: the Field of the two-factor page written with inputmode, autocomplete, maxlength and code by hand"],"patterns":["form-with-error-summary"],"related":["field","password-field"]},{"name":"PageHeader","slug":"page-header","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/PageHeader/PageHeader.svelte","doc":"components/PageHeader/PageHeader.md","stories":"components/PageHeader/PageHeader.stories.svelte","test":"components/PageHeader/PageHeader.test.ts"},"props":[{"name":"title","type":"string","required":true,"description":"The page's title, as its h1."},{"name":"eyebrow","type":"string | undefined","required":false,"description":"A line above the title in small capitals: the section, a count. Undefined when the page has none to say, so a page passes what it computed."},{"name":"lead","type":"string | undefined","required":false,"description":"One sentence under the title. Undefined when there is none."},{"name":"level","type":"1 | 2","required":false,"description":"The heading level; 1 on a page, 2 for the head of a section laid out the same way.","default":"1"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"actions","type":"Snippet","required":false,"description":"The page's actions: a primary Button, or a Menu."}],"snippets":["actions"],"stories":["plain","withAction","section"],"classes":["rl-page-header","rl-page-header__actions","rl-page-header__text","rl-space-2","rl-space-3","rl-space-4","rl-space-6","rl-space-8"],"criteria":[{"code":"9.1","why":"The title is the page's h1, or the h2 of a section laid out the same way; the level is a prop, never a size."},{"code":"12.8","why":"The actions come after the title in the DOM, where the reading order and the tab order expect them."}],"replaces":["apps/web: the three h1 shapes and the eight ruled section heads of the lab pages, once the app is ported"],"patterns":["data-table-page"],"related":["heading","eyebrow","prose","button","breadcrumb"]},{"name":"Pagination","slug":"pagination","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Pagination/Pagination.svelte","doc":"components/Pagination/Pagination.md","stories":"components/Pagination/Pagination.stories.svelte","test":"components/Pagination/Pagination.test.ts"},"props":[{"name":"page","type":"number","required":true,"description":"The current page, from 1."},{"name":"pages","type":"number","required":true,"description":"The number of pages."},{"name":"label","type":"string","required":true,"description":"The landmark's name: \"Pagination des utilisateurs\". Two paginations on one screen need two names."},{"name":"href","type":"(page: number) => string","required":true,"description":"The address of a page."},{"name":"onnavigate","type":"(page: number, event: MouseEvent) => void","required":false,"description":"For a list updated in place: called with the page and the click, which the page prevents."},{"name":"total","type":"number","required":false,"description":"The number of rows in all, for the count."},{"name":"perPage","type":"number","required":false,"description":"The rows per page, for the count."},{"name":"rowsName","type":"string","required":false,"description":"What is counted, in the plural: \"utilisateurs\".","default":"'résultats'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["short","long","counted"],"classes":["rl-action","rl-border","rl-hover","rl-ink","rl-ink-soft","rl-line-control","rl-line-strong","rl-pagination","rl-pagination__count","rl-pagination__gap","rl-pagination__link","rl-pagination__list","rl-radius","rl-space-1","rl-space-2","rl-space-3","rl-sr-only","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"12.6","why":"A navigation landmark with its own name, so two paginations on one screen are told apart in the list of regions."},{"code":"6.1","why":"Every link says its destination out of context: 'Page 4', 'Page suivante'; a bare number would not."},{"code":"7.5","why":"The count is a status region present from the first render, so a list updated in place is announced."},{"code":"3.1","why":"The current page is told by aria-current and the strong line under it, not by colour alone."}],"replaces":["apps/web: lib/components/Pagination.svelte, gone","apps/site: the pagination demo, kept as teaching material"],"patterns":["data-table-page"],"related":["table","data-table","link"]},{"name":"PasswordField","slug":"password-field","level":"molecule","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/PasswordField/PasswordField.svelte","doc":"components/PasswordField/PasswordField.md","stories":"components/PasswordField/PasswordField.stories.svelte","test":"components/PasswordField/PasswordField.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The control's id; the label's `for` is the same value."},{"name":"label","type":"string","required":true,"description":"The visible label: \"Mot de passe\", \"Nouveau mot de passe\"."},{"name":"value","type":"string | undefined","required":false,"description":"The value, bindable; absent reads as empty.","bindable":true},{"name":"name","type":"string","required":false,"description":"The name sent with the form."},{"name":"required","type":"boolean","required":false,"description":"Required: the word goes into the accessible name and the attribute onto the control.","default":"false"},{"name":"hint","type":"string | undefined","required":false,"description":"A help sentence, announced with the field: the rules of a new password."},{"name":"error","type":"string | undefined","required":false,"description":"The error message; its presence alone marks the field invalid."},{"name":"autocomplete","type":"FullAutoFill","required":false,"description":"`current-password` at sign-in, `new-password` when choosing one: the password manager fills the right one.","default":"'current-password'"},{"name":"minlength","type":"number","required":false,"description":"The shortest password accepted, for a new one."},{"name":"showLabel","type":"string","required":false,"description":"The toggle's name, the same whether the password shows or not; the state says which.","default":"'Afficher le mot de passe'"}],"snippets":[],"stories":["signIn","newPassword","error"],"classes":["rl-action","rl-action-hover","rl-password-field","rl-password-field__toggle","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"11.1","why":"Field's label, tied by for and id; the toggle is a button named by its text."},{"code":"7.1","why":"The toggle is a native button with aria-pressed: the state is the one assistive technology knows, and the name never changes."},{"code":"11.13","why":"autocomplete says current-password or new-password, so the manager fills or offers the right one."},{"code":"11.10","why":"The error and the hint are tied to the control as Field ties them."}],"replaces":["apps/web: the plain password Field of the sign-in, register and reset pages, which had no way to show what was typed"],"patterns":["form-with-error-summary"],"related":["field","otp-field","button"]},{"name":"Popover","slug":"popover","level":"molecule","status":"stable","surfaces":["app","extension"],"since":"2026-09","files":{"component":"components/Popover/Popover.svelte","doc":"components/Popover/Popover.md","stories":"components/Popover/Popover.stories.svelte","test":"components/Popover/Popover.test.ts"},"props":[{"name":"label","type":"string","required":true,"description":"The button's text: what the panel holds (\"Filtres\", \"Aide\"). It names the panel too, unless there is a title."},{"name":"title","type":"string | undefined","required":false,"description":"A heading at the top of the panel, which then names it."},{"name":"open","type":"boolean","required":false,"description":"Shown or not; bindable.","default":"false","bindable":true},{"name":"align","type":"'start' | 'end'","required":false,"description":"Which edge of the button the panel aligns with.","default":"'start'"},{"name":"variant","type":"'secondary' | 'ghost'","required":false,"description":"The button's look: `secondary` has the control line, `ghost` draws nothing until hovered.","default":"'secondary'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":true,"description":"The panel's content."}],"snippets":["children"],"stories":["help","controls","end"],"classes":["rl-border","rl-border-strong","rl-duration-fast","rl-ease","rl-hover","rl-ink","rl-ink-soft","rl-line-control","rl-line-strong","rl-popover","rl-popover__chevron","rl-popover__panel","rl-popover__panel--","rl-popover__panel--end","rl-popover__panel--start","rl-popover__title","rl-popover__trigger","rl-popover__trigger--","rl-popover__trigger--ghost","rl-radius","rl-space-1","rl-space-2","rl-space-3","rl-space-4","rl-surface","rl-text-sm","rl-weight-bold","rl-z-drawer"],"criteria":[{"code":"7.1","why":"The button says its state with aria-expanded and names its panel with aria-controls; the panel is a named non-modal dialog that takes the focus."},{"code":"7.3","why":"Escape closes and returns the focus to the button; Tab leaves the panel and closes it; nothing is trapped."},{"code":"10.13","why":"The panel appears on a press, never on hover, and stays until dismissed."}],"replaces":[],"patterns":[],"related":["tooltip","dialog","menu","disclosure"]},{"name":"PrevNext","slug":"prev-next","level":"molecule","status":"stable","surfaces":["site","app","extension"],"since":"2026-09","files":{"component":"components/PrevNext/PrevNext.svelte","doc":"components/PrevNext/PrevNext.md","stories":"components/PrevNext/PrevNext.stories.svelte","test":"components/PrevNext/PrevNext.test.ts"},"props":[{"name":"prev","type":"Step | undefined","required":false,"description":"The step before; absent at the first page."},{"name":"next","type":"Step | undefined","required":false,"description":"The step after; absent at the last page."},{"name":"label","type":"string","required":false,"description":"The landmark's name. Two pairs on one page need two names.","default":"'Pages voisines'"},{"name":"prevWord","type":"string","required":false,"description":"The word of the step before.","default":"'Précédent'"},{"name":"nextWord","type":"string","required":false,"description":"The word of the step after.","default":"'Suivant'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["both","first","last","buttons"],"classes":["rl-action","rl-border","rl-ink","rl-ink-soft","rl-line","rl-line-strong","rl-prev-next","rl-prev-next__label","rl-prev-next__step","rl-prev-next__step--","rl-prev-next__step--next","rl-prev-next__word","rl-radius","rl-space-1","rl-space-3","rl-space-4","rl-surface","rl-text-sm","rl-text-xs","rl-tracking-label","rl-weight-bold"],"criteria":[{"code":"12.6","why":"A navigation landmark named for what it links, so a reader finds it in the landmark list and skips it."},{"code":"6.1","why":"Each link's text says its direction and its destination: 'Précédent, Le contraste des textes'."},{"code":"7.3","why":"A link when there is an address, a native button when the step is an action; a missing side is absent, never a disabled control."}],"replaces":["apps/site: the previous and next links at the foot of the sheets and the articles"],"patterns":["wizard-step"],"related":["pagination","breadcrumb","wizard-footer","link"]},{"name":"PropositionList","slug":"proposition-list","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-10","files":{"component":"components/PropositionList/PropositionList.svelte","doc":"components/PropositionList/PropositionList.md","stories":"components/PropositionList/PropositionList.stories.svelte","test":"components/PropositionList/PropositionList.test.ts"},"props":[{"name":"propositions","type":"Proposition[]","required":true},{"name":"onaccept","type":"(criteria: string[]) => void","required":true,"description":"Called with the criteria the auditor keeps."},{"name":"ondismiss","type":"(criterion: string) => void","required":true,"description":"Called with the criterion the auditor sets aside."},{"name":"onannounce","type":"(sentence: string) => void","required":false,"description":"Called with the sentence to announce; the panel owns its live regions."}],"snippets":[],"stories":["mixed","single","none","hooked"],"classes":["rl-border","rl-ink-soft","rl-line","rl-proposition","rl-proposition__critere","rl-proposition__reserves","rl-proposition__source","rl-propositions","rl-propositions-titre","rl-propositions__ecartees","rl-propositions__geste","rl-propositions__liste","rl-propositions__titre","rl-radius","rl-space-1","rl-space-2","rl-space-3","rl-sr-only","rl-text-sm"],"criteria":[{"code":"7.1","why":"Real buttons for accepting and dismissing, never a div with a click handler."},{"code":"9.1","why":"A heading names the section, so the list is reachable by heading navigation."},{"code":"12.9","why":"The gesture that commits many criteria is undone from where it was made: the focus does not travel."}],"related":["button","status-chip","theme-list"]},{"name":"Question","slug":"question","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/Question/Question.svelte","doc":"components/Question/Question.md","stories":"components/Question/Question.stories.svelte","test":"components/Question/Question.test.ts"},"props":[{"name":"criterion","type":"string | string[]","required":true,"description":"The criterion the answer settles: \"2.2\". Several when one answer settles two."},{"name":"id","type":"string","required":false,"description":"An id, so the answer buttons can be named by the question."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":true,"description":"The question, one sentence ending with a question mark."},{"name":"help","type":"Snippet","required":false,"description":"One line under the question: what to look at."}],"snippets":["children","help"],"stories":["single","twoCriteria","withHelp"],"classes":["rl-border","rl-font-mono","rl-ink","rl-ink-soft","rl-line-strong","rl-question","rl-question__criterion","rl-question__help","rl-question__text","rl-radius","rl-space-1","rl-space-1-5","rl-space-2","rl-sr-only","rl-text-base","rl-text-sm","rl-text-xs","rl-weight-bold","rl-weight-regular"],"criteria":[{"code":"3.1","why":"The criterion is said by its number and a hidden word, never by the badge's line alone; the question is the text, not a colour."},{"code":"10.11","why":"The sentence wraps and the badge sits inline, so the question reads at 216 pixels, the width of Firefox's sidebar."}],"replaces":["apps/extension: the question paragraph and the criterion badge each wizard wrote by hand"],"related":["wizard-frame","answer-buttons","glossary-term"]},{"name":"RadioGroup","slug":"radio-group","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/RadioGroup/RadioGroup.svelte","doc":"components/RadioGroup/RadioGroup.md","stories":"components/RadioGroup/RadioGroup.stories.svelte","test":"components/RadioGroup/RadioGroup.test.ts"},"props":[{"name":"name","type":"string","required":true,"description":"The shared name: it makes the group. Unique on the page."},{"name":"legend","type":"string","required":true,"description":"The question, as the legend."},{"name":"options","type":"Option[]","required":true,"description":"The options, in order."},{"name":"value","type":"string | undefined","required":false,"description":"The chosen value; bindable, absent reads as no choice.","bindable":true},{"name":"hint","type":"string | undefined","required":false,"description":"A help sentence, announced with the group."},{"name":"error","type":"string | undefined","required":false,"description":"The error message, tied to the group; its presence draws the line of non-conformity around the options."},{"name":"required","type":"boolean","required":false,"description":"Required: the word goes into the legend and the attribute onto the controls.","default":"false"},{"name":"inline","type":"boolean","required":false,"description":"The options side by side rather than stacked, for two or three short ones.","default":"false"}],"snippets":[],"stories":["plain","chosen","inline","required","error"],"classes":["rl-action","rl-border-strong","rl-focus","rl-focus-offset","rl-focus-width","rl-ink","rl-ink-soft","rl-nc","rl-radio-group","rl-radio-group--inline","rl-radio-group--invalid","rl-radio-group__control","rl-radio-group__error","rl-radio-group__hint","rl-radio-group__label","rl-radio-group__legend","rl-radio-group__mark","rl-radio-group__option","rl-radio-group__option-hint","rl-radio-group__options","rl-radio-group__text","rl-space-1","rl-space-1-5","rl-space-2","rl-space-2-5","rl-space-3","rl-space-6","rl-sr-only","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"11.5","why":"The options are grouped in a fieldset; the component cannot render them outside one."},{"code":"11.6","why":"The legend is required and is the question, read before the options."},{"code":"11.1","why":"Every option has a label associated by for and id, generated from one prop."},{"code":"12.8","why":"One name for the group, so the options cost one Tab stop and the arrows move the choice."}],"replaces":["apps/web: the hand-built radio list of the account sheet's role and the dashboard's filter buttons"],"patterns":["form-with-error-summary"],"related":["checkbox","select","field"]},{"name":"ResumeBanner","slug":"resume-banner","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/ResumeBanner/ResumeBanner.svelte","doc":"components/ResumeBanner/ResumeBanner.md","stories":"components/ResumeBanner/ResumeBanner.stories.svelte","test":"components/ResumeBanner/ResumeBanner.test.ts"},"props":[{"name":"summary","type":"string","required":true,"description":"The sentence: \"3 réponses reprises, 1 élément à revérifier.\""},{"name":"alert","type":"boolean","required":false,"description":"Something needs the auditor: an element gone, an answer to review.","default":"false"},{"name":"note","type":"string | undefined","required":false,"description":"A second sentence, when there is one: what the vanished answers still count for."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["resumed","alert"],"classes":["rl-attention","rl-border","rl-ink","rl-ink-soft","rl-line","rl-resume","rl-resume--alert","rl-resume__icon","rl-resume__note","rl-resume__summary","rl-space-1","rl-space-2","rl-space-4","rl-sunken","rl-text-sm"],"criteria":[{"code":"3.1","why":"The alert is a drawing and a sentence that says what needs the auditor; the attention ink on the drawing never carries alone."}],"replaces":["apps/extension: renderBandeauReprise in the resume module"],"related":["wizard-frame","review-flag","jump-list"]},{"name":"ReviewFlag","slug":"review-flag","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/ReviewFlag/ReviewFlag.svelte","doc":"components/ReviewFlag/ReviewFlag.md","stories":"components/ReviewFlag/ReviewFlag.stories.svelte","test":"components/ReviewFlag/ReviewFlag.test.ts"},"props":[{"name":"label","type":"string","required":true,"description":"The element: \"Photo du produit\"."},{"name":"answer","type":"string | undefined","required":false,"description":"The answer as words: \"6.1 conforme, 6.2 conforme\"."},{"name":"differences","type":"string[]","required":false,"description":"What changed, one sentence each.","default":"[]"},{"name":"onconfirm","type":"() => void","required":true},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["withDifferences","bare"],"classes":["rl-attention","rl-ink","rl-ink-soft","rl-radius","rl-review","rl-review__differences","rl-review__icon","rl-review__note","rl-review__title","rl-space-2","rl-space-3","rl-space-5","rl-sunken","rl-text-sm"],"criteria":[{"code":"3.1","why":"The flag is the word À revérifier and the drawing; the attention ink on the line only confirms it."},{"code":"7.1","why":"Confirming is a real button, named with the element and the answer, since Confirmer alone names nothing."}],"replaces":["apps/extension: renderSignalementRevoir in the resume module"],"related":["resume-banner","answer-buttons","jump-list"]},{"name":"SearchField","slug":"search-field","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/SearchField/SearchField.svelte","doc":"components/SearchField/SearchField.md","stories":"components/SearchField/SearchField.stories.svelte","test":"components/SearchField/SearchField.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The field's id; the label's `for` is the same value."},{"name":"label","type":"string","required":true,"description":"The label, read and not shown: \"Rechercher un utilisateur\"."},{"name":"placeholder","type":"string","required":false,"description":"What the empty field shows; a hint, never the label."},{"name":"value","type":"string | undefined","required":false,"description":"The value; bindable, absent reads as empty.","bindable":true},{"name":"name","type":"string","required":false,"description":"The parameter's name in the address.","default":"'q'"},{"name":"action","type":"string","required":false,"description":"Where the form goes; the current address when absent."},{"name":"buttonLabel","type":"string","required":false,"description":"The button's text.","default":"'Rechercher'"},{"name":"onsearch","type":"(value: string, event: SubmitEvent) => void","required":false,"description":"For a search done in place: called with the value and the event, which the page prevents."},{"name":"clearHref","type":"string","required":false,"description":"The address of the screen without a search; with a value, a link to it is offered."},{"name":"clearLabel","type":"string","required":false,"description":"The clear link's text.","default":"'Effacer la recherche'"}],"snippets":[],"stories":["inPlace","withClear"],"classes":["rl-action","rl-border","rl-ink","rl-ink-soft","rl-line-control","rl-radius","rl-search-field","rl-search-field__clear","rl-search-field__input","rl-space-2","rl-space-3","rl-sr-only","rl-surface","rl-text-base","rl-text-sm"],"criteria":[{"code":"12.6","why":"The form carries role search, so the zone is listed with the page's landmarks and reached in one move."},{"code":"11.1","why":"The label is tied to the field by for and id and hidden rather than absent; the button's word next to the field is what makes hiding it acceptable."},{"code":"11.9","why":"The submit button says 'Rechercher'; the magnifier is a hidden drawing of the same word."}],"replaces":["apps/web: lib/components/ChampRecherche.svelte, gone with the admin lists' port","apps/site: the champ-de-recherche demo, kept as teaching material"],"patterns":["data-table-page"],"related":["field","button","live-region"]},{"name":"Stat","slug":"stat","level":"molecule","status":"stable","surfaces":["site"],"since":"2026-08","files":{"component":"components/Stat/Stat.svelte","doc":"components/Stat/Stat.md","stories":"components/Stat/Stat.stories.svelte","test":"components/Stat/Stat.test.ts"},"props":[{"name":"value","type":"string","required":true,"description":"The figure, already formatted (\"106\", \"94,9 %\")."},{"name":"secondaryValue","type":"string | undefined","required":false,"description":"A denominator or a unit after the figure, in the soft ink (\"/87\")."},{"name":"label","type":"string","required":true,"description":"What the figure counts, in small capitals under it."},{"name":"tone","type":"'default' | 'success' | 'error' | 'soft'","required":false,"description":"The value's ink. The label always says what the figure is; the tone only confirms it (criterion 3.1).","default":"'default'"},{"name":"children","type":"Snippet","required":false,"description":"A sentence under the label: the source, the date."}],"snippets":["children"],"stories":["plain","fraction","withSentence","toned"],"classes":["rl-c","rl-ink","rl-ink-soft","rl-nc","rl-space-1","rl-space-2","rl-stat","rl-stat--","rl-stat--error","rl-stat--soft","rl-stat--success","rl-stat__detail","rl-stat__label","rl-stat__secondary","rl-stat__value","rl-text-3xl","rl-text-sm","rl-text-xs","rl-tracking-stat","rl-tracking-tight","rl-weight-bold"],"criteria":[{"code":"3.1","why":"The figure and its label are text; nothing about the value is said by colour."}],"patterns":[],"related":["conformity-rate"]},{"name":"StatGroup","slug":"stat-group","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/StatGroup/StatGroup.svelte","doc":"components/StatGroup/StatGroup.md","stories":"components/StatGroup/StatGroup.stories.svelte","test":"components/StatGroup/StatGroup.test.ts"},"props":[{"name":"items","type":"Item[]","required":true,"description":"The figures, in reading order."},{"name":"label","type":"string","required":true,"description":"The group's name, read before the figures: \"Bilan de l'audit\"."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["audit","two"],"classes":["rl-border","rl-line","rl-space-4","rl-space-5","rl-stat-group","rl-stat-group__item","rl-surface"],"criteria":[{"code":"9.3","why":"The figures are a list, named, so their count and rank are announced and they read as one group."},{"code":"3.1","why":"A tone on a figure only confirms the label under it, which says what the figure counts."}],"replaces":["apps/web: lib/components/StatCard.svelte and the grids of figures of the dashboard and the audit page, once the app is ported"],"related":["stat","card-grid"]},{"name":"StatusBreakdown","slug":"status-breakdown","level":"molecule","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/StatusBreakdown/StatusBreakdown.svelte","doc":"components/StatusBreakdown/StatusBreakdown.md","stories":"components/StatusBreakdown/StatusBreakdown.stories.svelte","test":"components/StatusBreakdown/StatusBreakdown.test.ts"},"props":[{"name":"counts","type":"Record<Status, number>","required":true,"description":"The count of criteria in each status."},{"name":"total","type":"number","required":true,"description":"The number the bar is drawn against: the criteria of the set, tested or not."},{"name":"label","type":"string","required":true,"description":"The list's name: \"Répartition des statuts de la page\"."},{"name":"bar","type":"boolean","required":false,"description":"Draws the bar under the counts; false keeps the counts only, for a dense row.","default":"true"}],"snippets":[],"stories":["finished","inProgress","countsOnly"],"classes":["rl-border","rl-c","rl-na","rl-nc","rl-radius","rl-space-1-5","rl-space-2","rl-space-3","rl-status-breakdown","rl-status-breakdown__bar","rl-status-breakdown__count","rl-status-breakdown__counts","rl-status-breakdown__figure","rl-status-breakdown__segment","rl-status-breakdown__segment--","rl-status-breakdown__segment--c","rl-status-breakdown__segment--na","rl-status-breakdown__segment--nc","rl-sunken","rl-text-sm"],"criteria":[{"code":"3.1","why":"The counts with their StatusChips are the information; the bar of colours only repeats them and is hidden from assistive technology."},{"code":"9.3","why":"The counts are a named list, so a screen reader announces how many statuses are present and reads each figure with its status."}],"replaces":["apps/web: the two loops and three colours the audit page and the audited page each wrote for the split of statuses"],"patterns":[],"related":["status-chip","stat-group","progress-bar"]},{"name":"StatusCounters","slug":"status-counters","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/StatusCounters/StatusCounters.svelte","doc":"components/StatusCounters/StatusCounters.md","stories":"components/StatusCounters/StatusCounters.stories.svelte","test":"components/StatusCounters/StatusCounters.test.ts"},"props":[{"name":"counts","type":"Record<StatutDeCritere, number>","required":true},{"name":"label","type":"string","required":false,"description":"What the counts are of, for the group's name: \"les critères du thème\".","default":"'Statuts des critères'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["inProgress","finished"],"classes":["rl-c","rl-counters","rl-counters__figure","rl-counters__item","rl-counters__item--","rl-counters__item--c","rl-counters__item--na","rl-counters__item--nc","rl-counters__word","rl-ink","rl-ink-soft","rl-na","rl-nc","rl-space-1","rl-space-2","rl-space-3","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"3.1","why":"Each count carries its word; the status ink on the figure never carries alone."}],"replaces":["apps/extension: renderCompteurs, the counters strip of the wizard foot"],"related":["status-chip","wizard-footer","status-breakdown"]},{"name":"StatusToggle","slug":"status-toggle","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/StatusToggle/StatusToggle.svelte","doc":"components/StatusToggle/StatusToggle.md","stories":"components/StatusToggle/StatusToggle.stories.svelte","test":"components/StatusToggle/StatusToggle.test.ts"},"props":[{"name":"criterionId","type":"string","required":true,"description":"The criterion's code, which names the group and each button: \"Statut du critère 1.1\"."},{"name":"value","type":"StatutDeCritere | undefined","required":false,"description":"The status retained; `NT` or absent leaves every button unpressed."},{"name":"onchange","type":"(status: ChosenStatus) => void","required":true,"description":"Called with the status pressed. Pressing the retained one calls it again; the caller decides whether that clears it."},{"name":"statuses","type":"ChosenStatus[]","required":false,"description":"The statuses offered, in order.","default":"['C', 'NC', 'NA']"},{"name":"size","type":"'sm' | 'md'","required":false,"description":"`sm` in a row of the panel, `md` in a form.","default":"'sm'"},{"name":"hooks","type":"((status: ChosenStatus) => DataHooks) | undefined","required":false,"description":"The `data-*` attributes of each button, from its status: what a click delegate or a walk reads."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["untested","pressed","twoOnly","medium","hooked"],"classes":["rl-border","rl-c","rl-hover","rl-ink","rl-ink-soft","rl-line-control","rl-line-strong","rl-na","rl-nc","rl-on-fill","rl-radius","rl-space-1","rl-space-1-5","rl-space-3","rl-space-half","rl-status-toggle","rl-status-toggle--","rl-status-toggle--md","rl-status-toggle__button","rl-status-toggle__button--","rl-status-toggle__button--c","rl-status-toggle__button--na","rl-status-toggle__button--nc","rl-surface","rl-target-min","rl-text-base","rl-text-sm","rl-weight-bold"],"criteria":[{"code":"3.1","why":"The pressed status is told by aria-pressed, the symbol and the letter, and only then by the fill; the word is in the name."},{"code":"7.1","why":"Native buttons with aria-pressed: the pressed state is the one assistive technology already knows, in a group named for the criterion."},{"code":"11.9","why":"The name of every button is explicit out of context: the letter, the word from core and the criterion."}],"replaces":["apps/extension: the three status buttons of CriterionRow.svelte and of the string renderer before it"],"patterns":["panel-shell"],"related":["criterion-row","status-chip","answer-buttons"]},{"name":"Table","slug":"table","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Table/Table.svelte","doc":"components/Table/Table.md","stories":"components/Table/Table.stories.svelte","test":"components/Table/Table.test.ts"},"props":[{"name":"caption","type":"string","required":true,"description":"The table's title. Required: that is criterion 5.4, and it names the scroll region."},{"name":"captionHidden","type":"boolean","required":false,"description":"Hides the caption on screen without removing it from the accessible name.","default":"false"},{"name":"variant","type":"'default' | 'report'","required":false,"description":"`report` for the exported documents: spaced small capitals in the header, cells aligned at the top.","default":"'default'"},{"name":"fixed","type":"boolean","required":false,"description":"A fixed layout, with a colgroup that declares the widths; without one the columns split evenly.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":true,"description":"The thead and tbody, with scope on every th."}],"snippets":["children"],"stories":["plain","report","captionHidden"],"classes":["rl-border","rl-border-strong","rl-ink","rl-leading-row","rl-line","rl-line-strong","rl-space-2","rl-space-3","rl-sr-only","rl-table","rl-table--","rl-table--fixed","rl-table--report","rl-table__caption","rl-table__center","rl-table__clip","rl-table__line","rl-table__num","rl-text-sm","rl-text-xs","rl-tracking-label","rl-weight-bold"],"criteria":[{"code":"5.4","why":"The caption is a required prop: a data table cannot be rendered without its title."},{"code":"5.6","why":"The headers are the caller's, and the doc and the test hold them to a scope on every th."},{"code":"7.3","why":"The table sits in a scroll region named by its caption, so a table wider than its column can be walked at the keyboard."}],"replaces":["packages/design base.css: the tableau, tableau-fixe, tableau-coupe, tableau-ligne, tableau-nombre, tableau-centre and tableau-rapport classes, and their raw uses in the app, once the app is ported"],"related":["data-table","scroll-region"]},{"name":"Tabs","slug":"tabs","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Tabs/Tabs.svelte","doc":"components/Tabs/Tabs.md","stories":"components/Tabs/Tabs.stories.svelte","test":"components/Tabs/Tabs.test.ts"},"props":[{"name":"tabs","type":"Tab[]","required":true,"description":"The tabs, in order."},{"name":"selected","type":"string | undefined","required":false,"description":"The id of the selected tab; bindable. The first tab when absent.","bindable":true},{"name":"label","type":"string","required":true,"description":"The name of the tab list, said before the tabs: \"Ce que fait l'extension, liste d'onglets\"."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"panel","type":"Snippet<[Tab]>","required":true,"description":"The content of one panel, given its tab."}],"snippets":["panel"],"stories":["plain","preset"],"classes":["rl-bg","rl-border","rl-border-strong","rl-ink","rl-ink-soft","rl-line-strong","rl-radius","rl-space-1","rl-space-2-5","rl-space-4","rl-tabs","rl-tabs__list","rl-tabs__panel","rl-tabs__tab","rl-weight-bold"],"criteria":[{"code":"7.1","why":"tablist, tab and tabpanel with aria-selected, aria-controls and aria-labelledby: the roles and relations are declared in full, since HTML has none for this pattern."},{"code":"7.3","why":"The arrows, Home and End move between tabs and select; the panel takes the focus after the list, so a panel that scrolls is reachable."},{"code":"12.8","why":"One tab is in the tab order at a time: a group of six costs one Tab before the content, not six."},{"code":"3.1","why":"The selected tab is told by its line joining the panel and by its ink, not by colour alone."}],"replaces":["apps/site: the onglets demo, kept as teaching material","apps/web: the hand-built view switcher of the report page, once the app is ported"],"patterns":[],"related":["accordion","disclosure"]},{"name":"ThemeSwitch","slug":"theme-switch","level":"molecule","status":"stable","surfaces":["site","app","extension"],"since":"2026-08","files":{"component":"components/ThemeSwitch/ThemeSwitch.svelte","doc":"components/ThemeSwitch/ThemeSwitch.md","stories":"components/ThemeSwitch/ThemeSwitch.stories.svelte","test":"components/ThemeSwitch/ThemeSwitch.test.ts"},"props":[{"name":"name","type":"string","required":false,"description":"The radio group's name. Two switches on one page each need their own, or the browser merges them into one group.","default":"'theme'"},{"name":"onchange","type":"((theme: Theme) => void) | undefined","required":false,"description":"Called with the choice once it is stored and applied: a surface that repaints something else with the theme (the extension's page furniture) hears it here."}],"snippets":[],"stories":["plain","named"],"classes":["rl-action","rl-border","rl-border-strong","rl-focus","rl-focus-offset","rl-focus-width","rl-ink","rl-ink-soft","rl-line-control","rl-on-fill","rl-radius","rl-sr-only","rl-sunken","rl-text-xs","rl-theme-switch","rl-theme-switch__label","rl-theme-switch__option","rl-weight-bold"],"criteria":[{"code":"11.1","why":"Three native radios in a fieldset whose legend names the group; each option is a label."},{"code":"11.5","why":"The three choices are grouped in a fieldset with a legend."},{"code":"10.7","why":"The radio stays in the tab order and the focus shows on its visible label."}],"patterns":[],"related":[]},{"name":"Toast","slug":"toast","level":"molecule","status":"stable","surfaces":[],"since":"2026-09","files":{"component":"components/Toast/Toast.svelte","doc":"components/Toast/Toast.md","stories":"components/Toast/Toast.stories.svelte","test":"components/Toast/Toast.test.ts"},"props":[{"name":"messages","type":"Message[]","required":true,"description":"The messages shown, newest last. The page removes one when it is done with it."},{"name":"ondismiss","type":"(id: string | number) => void","required":false,"description":"Called with the id of the message the user closed. Without it, no close button."},{"name":"fixed","type":"boolean","required":false,"description":"Drawn at the bottom of the screen; false keeps it in the flow, for a page that places it.","default":"true"},{"name":"passthrough","type":"boolean","required":false,"description":"Clicks go through the messages: for a corner laid over controls the user is about to press, as the panel's bar of a wizard. Only without `ondismiss`, since a close button that cannot be pressed is a lie.","default":"false"},{"name":"dismissLabel","type":"string","required":false,"description":"The close buttons' label.","default":"'Fermer le message'"}],"snippets":[],"stories":["flow","tones","passthrough"],"classes":["rl-border-strong","rl-c","rl-ink","rl-ink-soft","rl-line-strong","rl-nc","rl-radius","rl-space-2","rl-space-3","rl-space-4","rl-surface","rl-text-sm","rl-toast","rl-toast--fixed","rl-toast--passthrough","rl-toast__icon","rl-toast__item","rl-toast__item--","rl-toast__item--error","rl-toast__item--success","rl-toast__text","rl-z-toast"],"criteria":[{"code":"7.5","why":"One live region present from the first render holds every message; a message added to it is announced, where a toast created with its region is not."},{"code":"3.1","why":"The tone is a line and a drawing; the message is the word, and says the same thing without them."},{"code":"11.9","why":"The close button is named 'Fermer le message', not an X."}],"replaces":["apps/extension: showToast and the green toast of the panel and the content script, when the panel is on Svelte"],"related":["live-region","alert"]},{"name":"WizardFooter","slug":"wizard-footer","level":"molecule","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/WizardFooter/WizardFooter.svelte","doc":"components/WizardFooter/WizardFooter.md","stories":"components/WizardFooter/WizardFooter.stories.svelte","test":"components/WizardFooter/WizardFooter.test.ts"},"props":[{"name":"last","type":"boolean","required":false,"description":"On the last element, the forward button finishes instead.","default":"false"},{"name":"onprev","type":"(() => void) | undefined","required":false},{"name":"onnext","type":"(() => void) | undefined","required":false},{"name":"onfinish","type":"(() => void) | undefined","required":false},{"name":"prevDisabled","type":"boolean","required":false,"default":"false"},{"name":"finishReason","type":"string | undefined","required":false,"description":"Why the finish is unavailable, when it is: shown next to the button."},{"name":"prevLabel","type":"string","required":false,"default":"'Précédent'"},{"name":"nextLabel","type":"string","required":false,"default":"'Suivant'"},{"name":"finishLabel","type":"string","required":false,"default":"'Terminer'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"counters","type":"Snippet","required":false,"description":"The theme's counters, on the left."}],"snippets":["counters"],"stories":["middle","first","last","blocked"],"classes":["rl-button","rl-space-2","rl-space-3","rl-wizard-footer","rl-wizard-footer__counters","rl-wizard-footer__nav"],"criteria":[{"code":"7.1","why":"Real buttons, one disabled with a reason when the wizard cannot finish, never a div with a click handler."},{"code":"12.10","why":"The Enter shortcut that advances is printed on the button it drives."}],"replaces":["apps/extension: renderPiedDInventaire and the four copies it replaced"],"related":["wizard-frame","button","status-counters"]},{"name":"CriterionRow","slug":"criterion-row","level":"organism","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/CriterionRow/CriterionRow.svelte","doc":"components/CriterionRow/CriterionRow.md","stories":"components/CriterionRow/CriterionRow.stories.svelte","test":"components/CriterionRow/CriterionRow.test.ts"},"props":[{"name":"id","type":"string","required":true,"description":"The criterion's code: \"1.1\"."},{"name":"title","type":"string","required":true,"description":"The criterion's title, as the referential writes it."},{"name":"status","type":"StatutDeCritere | undefined","required":false,"description":"The status given; absent reads as non testé."},{"name":"onstatus","type":"(status: ChosenStatus) => void","required":true,"description":"Called with the status pressed."},{"name":"comment","type":"string | undefined","required":false,"description":"The auditor's comment, shown under the controls."},{"name":"oncomment","type":"(() => void) | undefined","required":false,"description":"Opens the note editor; the button appears when given, named for adding or editing."},{"name":"engine","type":"boolean","required":false,"description":"The status was written by the engine: the row says so with a word, and the panel adds the engine's line under it.","default":"false"},{"name":"action","type":"CriterionAction | undefined","required":false,"description":"A wizard of the criterion's own, next to its theme's: \"Tester les boutons\" for 11.9."},{"name":"hooks","type":"CriterionRowHooks | undefined","required":false,"description":"The `data-*` attributes of the row and its buttons: what a click delegate or a walk reads."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":false,"description":"What the panel puts under the row: the note editor, the engine's line, the doc, the proof."}],"snippets":["children"],"stories":["untested","judged","engine","hooked"],"classes":["rl-action","rl-border","rl-criterion","rl-criterion__actions","rl-criterion__comment","rl-criterion__id","rl-criterion__info","rl-criterion__title","rl-ink-soft","rl-leading-body","rl-leading-snug","rl-line","rl-measure-stat","rl-space-1","rl-space-1-5","rl-space-2","rl-space-4","rl-sr-only","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"9.2","why":"A list item in the theme's list: the criteria of a theme are a list, and a reader counts them."},{"code":"11.9","why":"Every button of the row says the criterion in its name: the note, the statuses, the wizard of its own."},{"code":"3.1","why":"An engine-written status is told by a word in a chip, never by a tint on the row."}],"replaces":["apps/extension: sidepanel/sidebar/CriterionRow.svelte, whose part under the row is now CriterionDetail.svelte"],"patterns":["panel-shell"],"related":["status-toggle","theme-list","evidence-block","button","chip"]},{"name":"DangerZone","slug":"danger-zone","level":"organism","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/DangerZone/DangerZone.svelte","doc":"components/DangerZone/DangerZone.md","stories":"components/DangerZone/DangerZone.stories.svelte","test":"components/DangerZone/DangerZone.test.ts"},"props":[{"name":"title","type":"string","required":true,"description":"The zone's heading, which says what is at stake: \"Suppression du site\"."},{"name":"level","type":"2 | 3","required":false,"description":"The heading level, from the page's outline.","default":"2"},{"name":"actions","type":"DangerAction[]","required":true,"description":"The irreversible actions, each confirmed before it runs."},{"name":"class","type":"string","required":false,"description":"Layout classes only: a margin.","default":"''"},{"name":"children","type":"Snippet","required":false,"description":"The explanation: what the actions destroy, in a sentence or two."}],"snippets":["children"],"stories":["single","several","busy"],"classes":["rl-border-strong","rl-danger-zone","rl-danger-zone__actions","rl-danger-zone__text","rl-ink-soft","rl-line-strong","rl-measure-prose","rl-radius","rl-space-2","rl-space-3","rl-space-4","rl-space-6"],"criteria":[{"code":"3.1","why":"The warning is in the words, the heading, the sentence and the verb on each button; the frame is the structural line, not the red of non-conformity, which is left to the destructive button."},{"code":"9.1","why":"The zone is a section named by a heading at the level the page gives, so a reader jumping by headings finds it and knows what is at stake before reaching a button."},{"code":"7.1","why":"Every action asks through ConfirmDialog, a native modal dialog named by its question with the cancel button focused, never window.confirm(), which a browser lets the user silence."},{"code":"11.9","why":"Each button is named by its verb and what it acts on ('Supprimer le site'), and the dialog's confirm button repeats that verb, never 'OK'."}],"replaces":["apps/web: the settings page's zone-de-suppression section, its frame and its hand-wired ConfirmDialog"],"patterns":["destructive-confirmation"],"related":["confirm-dialog","button","dialog"]},{"name":"Derogation","slug":"derogation","level":"organism","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/Derogation/Derogation.svelte","doc":"components/Derogation/Derogation.md","stories":"components/Derogation/Derogation.stories.svelte","test":"components/Derogation/Derogation.test.ts"},"props":[{"name":"label","type":"string","required":true,"description":"The element, for the buttons' names: \"Photo du produit\"."},{"name":"justification","type":"string | null","required":true,"description":"The justification of the derogation posed, or null when none is."},{"name":"id","type":"string","required":true,"description":"An id prefix, unique on the screen."},{"name":"onderogate","type":"(request: DerogationRequest) => void","required":true},{"name":"onremove","type":"() => void","required":true},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"}],"snippets":[],"stories":["closed","posed"],"classes":["rl-derogation","rl-derogation--posed","rl-derogation__help","rl-derogation__icon","rl-derogation__justification","rl-derogation__state","rl-ink-soft","rl-radius","rl-space-2","rl-space-3","rl-sr-only","rl-sunken","rl-text-sm"],"criteria":[{"code":"11.1","why":"Every control of the form has its label: the natures as a radio group with a legend, the alternative as a checkbox, the justification as a field."},{"code":"11.10","why":"The justification is required and said so; an empty submission writes the error next to the field and posts nothing."},{"code":"3.1","why":"A derogation posed is said by the word and the drawing; the derogation ink on the line only confirms it."}],"replaces":["apps/extension: renderDerogation, the derogation form and its posed state in the resume module"],"related":["disclosure","radio-group","field","checkbox","resume-banner"]},{"name":"EvidenceBlock","slug":"evidence-block","level":"organism","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/EvidenceBlock/EvidenceBlock.svelte","doc":"components/EvidenceBlock/EvidenceBlock.md","stories":"components/EvidenceBlock/EvidenceBlock.stories.svelte","test":"components/EvidenceBlock/EvidenceBlock.test.ts"},"props":[{"name":"criterionId","type":"string","required":true,"description":"The criterion's code, in the name of every button."},{"name":"items","type":"EvidenceItem[]","required":true,"description":"The elements at fault, detected or pointed at, and the captures kept of elements no longer detected."},{"name":"picking","type":"boolean","required":false,"description":"The auditor is pointing an element in the page: the block shows the instruction and the way out.","default":"false"},{"name":"oncapture","type":"(selector: string) => void","required":true,"description":"Takes or retakes the capture of an element."},{"name":"onshow","type":"((selector: string) => void) | undefined","required":false,"description":"Shows the element in the page; without it, and without `show`, the rows have no Montrer button."},{"name":"onpick","type":"(() => void) | undefined","required":false,"description":"Starts pointing an element in the page; without it the block offers no pointing."},{"name":"oncancel","type":"(() => void) | undefined","required":false,"description":"Stops pointing."},{"name":"hooks","type":"EvidenceBlockHooks | undefined","required":false,"description":"The `data-*` attributes of the capture, pointing and cancel buttons: what a click delegate or a walk reads."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"shot","type":"Snippet<[EvidenceItem]>","required":false,"description":"The capture of an item, rendered under its row when it has one."},{"name":"show","type":"Snippet<[EvidenceItem]>","required":false,"description":"The consumer's own Montrer control for an item, in place of the plain button `onshow` draws."}],"snippets":["shot","show"],"stories":["detected","complete","empty","picking","kept"],"classes":["rl-border","rl-c","rl-evidence","rl-evidence__blocked","rl-evidence__empty","rl-evidence__flag","rl-evidence__flag--complete","rl-evidence__flag--missing","rl-evidence__instruction","rl-evidence__item","rl-evidence__label","rl-evidence__list","rl-evidence__pick","rl-evidence__point","rl-evidence__row","rl-evidence__title","rl-font-mono","rl-ink","rl-ink-soft","rl-line","rl-nc","rl-space-1","rl-space-2","rl-sr-only","rl-text-sm","rl-text-xs","rl-weight-bold","rl-weight-regular"],"criteria":[{"code":"3.1","why":"A missing capture, a refused one and a complete set are each a drawing and a word; the ink of the flag only confirms."},{"code":"11.9","why":"Every capture button names the element and the criterion, so twenty 'Capturer' buttons on the panel are twenty names."},{"code":"12.10","why":"The keys that point an element in the page are written next to the gesture, in kbd elements, while the pointing runs."},{"code":"9.2","why":"The elements at fault are a list named by the title that counts them."}],"replaces":["apps/extension: sidepanel/sidebar/EvidenceBlock.svelte"],"patterns":["panel-shell"],"related":["criterion-row","figure","button","kbd"]},{"name":"Onboarding","slug":"onboarding","level":"organism","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/Onboarding/Onboarding.svelte","doc":"components/Onboarding/Onboarding.md","stories":"components/Onboarding/Onboarding.stories.svelte","test":"components/Onboarding/Onboarding.test.ts"},"props":[{"name":"title","type":"string","required":true,"description":"The block's heading: \"Bienvenue sur RGAA Lab\"."},{"name":"level","type":"2 | 3","required":false,"description":"The heading level, from the page's outline.","default":"2"},{"name":"steps","type":"OnboardingStep[]","required":true,"description":"The steps, in order."},{"name":"doneWord","type":"string","required":false,"description":"The word written next to a step done.","default":"'Fait'"},{"name":"dismissLabel","type":"string","required":false,"description":"The words of the dismiss button; the button shows only with `ondismiss`.","default":"'Masquer ces étapes'"},{"name":"ondismiss","type":"() => void","required":false,"description":"Called when the user dismisses the block. The page hides it and remembers."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"children","type":"Snippet","required":false,"description":"The sentence under the heading that says what the steps lead to."},{"name":"actions","type":"Snippet","required":false,"description":"The call to action under the steps: a Button."},{"name":"footer","type":"Snippet","required":false,"description":"A note under a line at the foot: another way in, a pointer to settings."}],"snippets":["children","actions","footer"],"stories":["welcome","underWay","dismissible"],"classes":["rl-bg","rl-border","rl-c","rl-font-mono","rl-ink","rl-ink-soft","rl-line","rl-measure-prose","rl-onboarding","rl-onboarding__action","rl-onboarding__actions","rl-onboarding__body","rl-onboarding__description","rl-onboarding__done","rl-onboarding__footer","rl-onboarding__head","rl-onboarding__intro","rl-onboarding__label","rl-onboarding__rank","rl-onboarding__step","rl-onboarding__steps","rl-radius","rl-space-1","rl-space-2","rl-space-3","rl-space-4","rl-space-6","rl-space-8","rl-sr-only","rl-text-sm","rl-text-xs","rl-weight-bold"],"criteria":[{"code":"9.1","why":"The block is a section named by a heading at the level the page gives, so a reader jumping by headings finds the first steps where the list of audits will be."},{"code":"9.3","why":"The steps are an ordered list: the order is the list's, heard as '1 sur 3', and the rank drawn next to a step is hidden rather than read twice."},{"code":"3.1","why":"A step done carries the check drawing and the word 'Fait'; the ink of conformity only confirms what the word says."},{"code":"11.9","why":"Every action, a step's own, the call to action and the dismiss, is named by its visible words, never by an aria-label that differs from them."}],"replaces":["apps/web: the dashboard's welcome card, its etapes list and its hand-drawn ranks"],"patterns":["empty-state"],"related":["empty-state","card","button","link"]},{"name":"PageSwitcher","slug":"page-switcher","level":"organism","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/PageSwitcher/PageSwitcher.svelte","doc":"components/PageSwitcher/PageSwitcher.md","stories":"components/PageSwitcher/PageSwitcher.stories.svelte","test":"components/PageSwitcher/PageSwitcher.test.ts"},"props":[{"name":"open","type":"boolean | undefined","required":false,"description":"Shown or not; bindable, set back to false when the dialog closes by any means.","bindable":true},{"name":"pages","type":"AuditPage[]","required":true,"description":"The audit's pages, in order."},{"name":"currentId","type":"string | undefined","required":false,"description":"The id of the page shown in the panel."},{"name":"onswitch","type":"(pageId: string) => void","required":true,"description":"Called with the id of the page chosen."},{"name":"ondelete","type":"((pageId: string) => void) | undefined","required":false,"description":"Deletes a page; the button shows when there are several pages and this is given."},{"name":"addPath","type":"string | undefined","required":false,"description":"The path of the tab's page, offered for adding; absent when the tab's page is already in the audit."},{"name":"addNote","type":"string | undefined","required":false,"description":"A mention under the add button: what the plan allows."},{"name":"onadd","type":"(() => void) | undefined","required":false,"description":"Adds the tab's page."},{"name":"onclose","type":"(() => void) | undefined","required":false,"description":"Called after the dialog closed."},{"name":"title","type":"string","required":false,"description":"The dialog's heading.","default":"'Pages de l’audit'"}],"snippets":[],"stories":["pages","single"],"classes":["rl-action","rl-border","rl-font-mono","rl-hover","rl-ink","rl-ink-soft","rl-line","rl-line-strong","rl-page-switcher","rl-page-switcher-progress","rl-page-switcher__add","rl-page-switcher__already","rl-page-switcher__bar","rl-page-switcher__fill","rl-page-switcher__info","rl-page-switcher__name","rl-page-switcher__note","rl-page-switcher__page","rl-page-switcher__path","rl-page-switcher__percent","rl-page-switcher__progress","rl-page-switcher__row","rl-page-switcher__url","rl-radius","rl-space-1","rl-space-1-5","rl-space-10","rl-space-2-5","rl-space-3","rl-space-half","rl-sr-only","rl-sunken","rl-text-sm","rl-text-xs","rl-weight-bold","rl-weight-regular"],"criteria":[{"code":"7.1","why":"A native dialog opened with showModal: the focus is held, Escape closes, the page behind is inert, and the focus returns to the button that opened it."},{"code":"6.1","why":"Each page is a link to its address whose text is the page's name; a modified click keeps the link's own meaning."},{"code":"3.1","why":"The current page is said by aria-current and drawn by its line; the progress is a figure with its word, the bar only draws it."},{"code":"11.9","why":"Each delete button is named for its page: three 'Supprimer' buttons are three names."}],"replaces":["apps/extension: sidepanel/dialogs/PagesDialog.svelte, once the panel reads its store through props"],"patterns":["panel-shell"],"related":["dialog","panel-shell","button","icon-button"]},{"name":"Section","slug":"section","level":"organism","status":"stable","surfaces":["site"],"since":"2026-08","files":{"component":"components/Section/Section.svelte","doc":"components/Section/Section.md","stories":"components/Section/Section.stories.svelte","test":"components/Section/Section.test.ts"},"props":[{"name":"rhythm","type":"'lead' | 'default' | 'tight'","required":false,"description":"`lead` opens the page and breathes more; `tight` follows a section closely.","default":"'default'"},{"name":"rule","type":"boolean","required":false,"description":"A structural rule above, to separate without adding a background.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only: an exceptional margin.","default":"''"},{"name":"children","type":"Snippet","required":true}],"snippets":["children"],"stories":["lead","plain","tight","ruled"],"classes":["rl-border-strong","rl-container-wide","rl-line-strong","rl-section","rl-section--","rl-section--default","rl-section--lead","rl-section--ruled","rl-section--tight","rl-space-12","rl-space-16","rl-space-20","rl-space-24","rl-space-4","rl-space-6"],"criteria":[{"code":"9.2","why":"A section element with no name is not a landmark and does not clutter the landmark list; the page's structure stays the headings'."}],"patterns":[],"related":["heading","eyebrow","prose"]},{"name":"ThemeList","slug":"theme-list","level":"organism","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/ThemeList/ThemeList.svelte","doc":"components/ThemeList/ThemeList.md","stories":"components/ThemeList/ThemeList.stories.svelte","test":"components/ThemeList/ThemeList.test.ts"},"props":[{"name":"themes","type":"Theme[]","required":true,"description":"The themes, in the referential's order, with their criteria and what the audit says of each."},{"name":"open","type":"number[]","required":false,"description":"The ids of the themes unfolded; bindable, and followed when the caller changes it.","default":"[]","bindable":true},{"name":"ontoggle","type":"((themeId: number, open: boolean) => void) | undefined","required":false,"description":"Called when a theme is unfolded or folded."},{"name":"onstatus","type":"(criterionId: string, status: ChosenStatus) => void","required":true,"description":"Called with the criterion and the status pressed."},{"name":"oncomment","type":"((criterionId: string) => void) | undefined","required":false,"description":"Opens the note editor of a criterion; without it the rows have no note button."},{"name":"onwizard","type":"((themeId: number) => void) | undefined","required":false,"description":"Opens the theme's wizard; without it no row has a wizard button, and a theme with `wizard: false` has none either."},{"name":"wizardReason","type":"string | undefined","required":false,"description":"Why the wizards cannot run now (\"Revenez à la page auditée pour lancer ce test.\"): the buttons are unavailable with the sentence."},{"name":"busyTheme","type":"number | undefined","required":false,"description":"The theme whose wizard is collecting on the page: its button says it works and keeps the focus."},{"name":"criterionAction","type":"((criterion: ThemeCriterion) => CriterionAction | undefined) | undefined","required":false,"description":"A wizard of a criterion's own, next to its theme's: 11.9 is judged button by button."},{"name":"hooks","type":"ThemeListHooks | undefined","required":false,"description":"The `data-*` attributes of the folding and wizard buttons, the lists and the rows: what a click delegate or a walk reads."},{"name":"level","type":"2 | 3 | 4","required":false,"description":"The heading level of the theme names.","default":"3"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"detail","type":"Snippet<[ThemeCriterion, Theme]>","required":false,"description":"What the panel puts under a criterion's row: the note editor, the engine's line, the doc, the proof."}],"snippets":["detail"],"stories":["themes","offPage","collecting","hooked","partiel"],"classes":["rl-action","rl-border","rl-control-sm","rl-duration-fast","rl-ease","rl-hover","rl-ink","rl-ink-soft","rl-line","rl-line-control","rl-nc","rl-radius","rl-space-1","rl-space-1-5","rl-space-2","rl-space-3","rl-space-4","rl-space-half","rl-sr-only","rl-sunken","rl-surface","rl-target-min","rl-text-sm","rl-text-xs","rl-theme-list","rl-theme-list__chevron","rl-theme-list__criteria","rl-theme-list__header","rl-theme-list__name","rl-theme-list__nc","rl-theme-list__number","rl-theme-list__reason","rl-theme-list__theme","rl-theme-list__toggle","rl-weight-bold","rl-weight-regular"],"criteria":[{"code":"9.1","why":"Each theme is a section named by a heading at the level the panel gives, so a reader jumps from theme to theme by headings."},{"code":"7.1","why":"The folding button says its state with aria-expanded and names its list with aria-controls; the wizard button is a real button, unavailable with a reason or busy with the attribute."},{"code":"3.1","why":"The count of non-conformities carries the symbol and the word, the engine's count its word; the status ink only confirms."},{"code":"12.8","why":"A folded list carries hidden: its rows leave the tab order, and Tab never lands on a criterion nobody sees."}],"replaces":["apps/extension: sidepanel/sidebar/ThemeRow.svelte, and the theme loop of ThemeList.svelte, now ThemeScreen.svelte on this list"],"patterns":["panel-shell"],"related":["criterion-row","status-toggle","panel-shell","disclosure","button"]},{"name":"WizardFrame","slug":"wizard-frame","level":"organism","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/WizardFrame/WizardFrame.svelte","doc":"components/WizardFrame/WizardFrame.md","stories":"components/WizardFrame/WizardFrame.stories.svelte","test":"components/WizardFrame/WizardFrame.test.ts"},"props":[{"name":"title","type":"string","required":true,"description":"The screen's title: \"Audit des cadres\"."},{"name":"kind","type":"string","required":false,"description":"The wizard's kind, written on the frame for the panel's own doors between wizards."},{"name":"progress","type":"{ current: number; total: number } | undefined","required":false,"description":"Where the auditor is: current element and total, shown as \"3 / 12\"."},{"name":"onclose","type":"(() => void) | undefined","required":false,"description":"Closes the wizard; the button carries the label."},{"name":"closeLabel","type":"string","required":false,"default":"'Fermer'"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"tools","type":"Snippet","required":false,"description":"The header's tools, between the progress and the close button: the veil switch, an inspector."},{"name":"above","type":"Snippet","required":false,"description":"What sits under the header, before the body: a resume banner, a recap."},{"name":"children","type":"Snippet","required":true,"description":"The screen's body, which scrolls."},{"name":"footer","type":"Snippet","required":false,"description":"The footer: the navigation, the counters."}],"snippets":["tools","above","children","footer"],"stories":["bare","withProgress","withFooter"],"classes":["rl-bg","rl-border","rl-ink","rl-ink-soft","rl-line","rl-space-2","rl-space-3","rl-space-4","rl-sr-only","rl-text-base","rl-text-sm","rl-tracking-tight","rl-weight-bold","rl-wizard","rl-wizard__body","rl-wizard__footer","rl-wizard__header","rl-wizard__progress","rl-wizard__title","rl-wizard__tools"],"criteria":[{"code":"9.1","why":"The screen's title is a heading that names the region, and the outline of the panel is what the wizard shows."},{"code":"12.8","why":"The header comes first in the DOM and the footer last, so the tab order follows the reading order: what the screen is, its content, then its navigation."}],"replaces":["apps/extension: the shell each wizard wrote as a string, header, body and footer, eight copies"],"patterns":["wizard-step"],"related":["question","answer-buttons","wizard-footer","criteria-recap","jump-list"]},{"name":"AppShell","slug":"app-shell","level":"view","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/AppShell/AppShell.svelte","doc":"components/AppShell/AppShell.md","stories":"components/AppShell/AppShell.stories.svelte","test":"components/AppShell/AppShell.test.ts"},"props":[{"name":"home","type":"NavLink","required":true,"description":"The first link of the column, the dashboard. Current on its exact path only."},{"name":"sections","type":"NavSection[]","required":true,"description":"The sections, in order. A section with no links and no `closed` is not rendered."},{"name":"currentPath","type":"string","required":true,"description":"The page's path, so the current link says so."},{"name":"collapsed","type":"boolean","required":false,"description":"Folded to a rail at first render, as the server knows it. After that the button decides.","default":"false"},{"name":"oncollapse","type":"(collapsed: boolean) => void","required":false,"description":"Called when the user folds or unfolds, so the page can remember it."},{"name":"busy","type":"boolean","required":false,"description":"A navigation is loading: aria-busy on the content area and the ring.","default":"false"},{"name":"announcement","type":"string","required":false,"description":"What the status region says; empty when nothing loads.","default":"''"},{"name":"email","type":"string","required":true,"description":"Who is signed in, in the column's foot."},{"name":"logoutAction","type":"string","required":true,"description":"Where the sign-out form posts."},{"name":"logoutEnhance","type":"(form: HTMLFormElement) => { destroy?: () => void } | void","required":false,"description":"An action for the sign-out form, such as SvelteKit's `enhance` with its submit function already bound."},{"name":"siteHref","type":"string","required":false,"description":"A link out, in the foot.","default":"'https://rgaa.dev'"},{"name":"siteLabel","type":"string","required":false,"default":"'rgaa.dev'"},{"name":"navLabel","type":"string","required":false,"description":"The navigation landmark's name.","default":"'Navigation'"},{"name":"themeName","type":"string","required":false,"description":"The theme switch's group name; two shells on one page need two.","default":"'theme'"},{"name":"banner","type":"Snippet","required":false,"description":"An announcement across the top of the content: the beta banner."},{"name":"as","type":"'main' | 'div'","required":false,"description":"`main` on a page; `div` inside a page that already has one.","default":"'main'"},{"name":"children","type":"Snippet","required":true}],"snippets":["banner","children"],"stories":["open","rail","closed","busy","withBanner"],"classes":["rl-action","rl-app-shell","rl-app-shell-bar","rl-app-shell-column","rl-app-shell-height","rl-app-shell-rail","rl-app-shell__aside","rl-app-shell__backdrop","rl-app-shell__bar","rl-app-shell__bar-brand","rl-app-shell__body","rl-app-shell__brand","rl-app-shell__chevron","rl-app-shell__closed","rl-app-shell__content","rl-app-shell__detail","rl-app-shell__done","rl-app-shell__email","rl-app-shell__fold","rl-app-shell__foot","rl-app-shell__head","rl-app-shell__item","rl-app-shell__item--child","rl-app-shell__item--section","rl-app-shell__link","rl-app-shell__list","rl-app-shell__logout","rl-app-shell__logout-button","rl-app-shell__main","rl-app-shell__meta","rl-app-shell__nav","rl-app-shell__page","rl-app-shell__panel","rl-app-shell__panel-title","rl-app-shell__row","rl-app-shell__section","rl-app-shell__site","rl-app-shell__text","rl-app-shell__theme","rl-app-shell__title","rl-backdrop","rl-bg","rl-border","rl-border-strong","rl-c","rl-container-wide","rl-duration-base","rl-ease","rl-ink","rl-ink-soft","rl-leading-body","rl-line","rl-line-control","rl-line-strong","rl-radius","rl-space-1","rl-space-10","rl-space-2","rl-space-3","rl-space-4","rl-space-6","rl-space-8","rl-sr-only","rl-sunken","rl-surface","rl-text-sm","rl-text-xs","rl-tracking-label","rl-weight-bold","rl-z-drawer","rl-z-sticky"],"criteria":[{"code":"12.6","why":"The column is one navigation landmark with a name; the foot, which holds a form and a fieldset, sits outside it."},{"code":"12.7","why":"The content area is the main landmark with the id the skip link targets and tabindex -1."},{"code":"12.8","why":"The mark, the fold button, the sections, the foot and the content follow in the DOM in the order the eye reads them, on every width."},{"code":"12.9","why":"Escape closes a rail panel and gives the focus back to its button; Escape closes the drawer; the closed drawer leaves the tab order."},{"code":"7.1","why":"A section folded to the rail is a button with aria-expanded and a panel it controls, a disclosure and not a menu; the fold button says its state the same way."},{"code":"10.7","why":"Every link, button and the sign-out control keep the base layer's focus ring; the rail's icon-only controls have a hidden name."},{"code":"3.1","why":"The current page is said by aria-current and drawn by a line on the left, never by colour alone; a finished audit shows a check with a hidden word."},{"code":"11.1","why":"The sign-out control is a submit button in a form, named by its word, hidden at the rail but never removed."},{"code":"9.1","why":"The section titles are labels tied to their lists with aria-labelledby, not headings: the column comes before the page's h1."},{"code":"4.1","why":"The status region that says a page is loading sits outside the main landmark, since aria-busy on an ancestor would silence it."}],"replaces":["apps/web: routes/lab/+layout.svelte, the top bar, the column, the rail, the drawer and the content area written by hand"],"patterns":["app-shell"],"related":["busy-region","skip-link","banner","icon-button","theme-switch","logo"]},{"name":"AuthShell","slug":"auth-shell","level":"view","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/AuthShell/AuthShell.svelte","doc":"components/AuthShell/AuthShell.md","stories":"components/AuthShell/AuthShell.stories.svelte","test":"components/AuthShell/AuthShell.test.ts"},"props":[{"name":"title","type":"string","required":true,"description":"The page's title, as its h1."},{"name":"logoHref","type":"string","required":false,"description":"Where the mark leads: the site for a visitor, the lab for a signed-in user.","default":"'/'"},{"name":"themeName","type":"string","required":false,"description":"The theme switch's radio group name; two shells on one page each need their own.","default":"'theme-auth'"},{"name":"width","type":"'md' | 'lg'","required":false,"description":"`md` for a form, `lg` for a page with more to read.","default":"'md'"},{"name":"as","type":"'main' | 'div'","required":false,"description":"`main` on a page; `div` inside a page that already has one.","default":"'main'"},{"name":"headingLevel","type":"1 | 2","required":false,"description":"The title's level: 1 on a page, 2 inside a page that already has an h1.","default":"1"},{"name":"lead","type":"Snippet","required":false,"description":"A sentence under the title, with its links."},{"name":"children","type":"Snippet","required":true,"description":"The card's content: the form, the messages."},{"name":"footer","type":"Snippet","required":false,"description":"The way back, under the card."}],"snippets":["lead","children","footer"],"stories":["signIn","message"],"classes":["rl-auth-shell","rl-auth-shell--","rl-auth-shell--lg","rl-auth-shell__brand","rl-auth-shell__card","rl-auth-shell__footer","rl-auth-shell__lead","rl-auth-shell__title","rl-auth-shell__top","rl-border","rl-ink-soft","rl-line","rl-radius","rl-space-12","rl-space-2","rl-space-3","rl-space-4","rl-space-6","rl-space-8","rl-surface","rl-text-sm","rl-text-xs"],"criteria":[{"code":"12.7","why":"The shell is the main landmark with the id the skip link targets and tabindex -1, so the link's target really takes the focus."},{"code":"9.1","why":"The title is the page's h1, at the top of the column, before the form."},{"code":"12.8","why":"The mark, the title, the lead's links, the form and the way back follow in the DOM in the order the eye reads them."}],"replaces":["apps/web: the centred column written by hand on the six auth and pairing pages"],"patterns":["form-with-error-summary"],"related":["field","button","alert","link","logo","theme-switch"]},{"name":"PanelShell","slug":"panel-shell","level":"view","status":"stable","surfaces":["extension"],"since":"2026-09","files":{"component":"components/PanelShell/PanelShell.svelte","doc":"components/PanelShell/PanelShell.md","stories":"components/PanelShell/PanelShell.stories.svelte","test":"components/PanelShell/PanelShell.test.ts"},"props":[{"name":"label","type":"string","required":true,"description":"The name of the content landmark: what the panel shows (\"Audit de la page\")."},{"name":"as","type":"'main' | 'div'","required":false,"description":"`main` in the panel, where the shell is the document; `div` inside a page that already has a main, as a named region.","default":"'main'"},{"name":"hidden","type":"boolean","required":false,"description":"Hidden under a wizard: the shell stays mounted and leaves the tab order.","default":"false"},{"name":"id","type":"string | undefined","required":false,"description":"The column's id: the root a consumer's click delegate listens on and reads from."},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"banner","type":"Snippet","required":false,"description":"Above the header: a banner about the page or the parameters."},{"name":"header","type":"Snippet","required":false,"description":"The header: the progress, the title row, the counters, the page switcher. It stays in sight."},{"name":"children","type":"Snippet","required":true,"description":"The content, which scrolls: the themes and their criteria."},{"name":"footer","type":"Snippet","required":false,"description":"The foot: the keys the wizards answer to. It stays in sight."}],"snippets":["banner","header","children","footer"],"stories":["bare","full","underWizard"],"classes":["rl-bg","rl-border","rl-ink","rl-ink-soft","rl-line","rl-panel","rl-panel__footer","rl-panel__header","rl-panel__main","rl-space-2","rl-space-3","rl-space-4","rl-text-xs"],"criteria":[{"code":"12.6","why":"The content is the panel's main landmark, or a named region inside a page that has one; the header and the foot are landmarks of their own."},{"code":"12.8","why":"The header comes first in the DOM, the content next, the foot last: the tab order is the reading order, and a hidden shell leaves it entirely."},{"code":"12.7","why":"As the main landmark it carries the id the skip link targets and a tabindex, so the link really moves the focus."},{"code":"7.3","why":"The scrolling area is a Tab stop in both forms, so a keyboard can scroll a screen that holds only text."}],"replaces":["apps/extension: the column the string renderer wrote in view.ts, with its three scroll containers"],"patterns":["panel-shell"],"related":["wizard-frame","theme-list","live-region","banner","status-counters"]},{"name":"PrintPage","slug":"print-page","level":"view","status":"stable","surfaces":["app","design"],"since":"2026-09","files":{"component":"components/PrintPage/PrintPage.svelte","doc":"components/PrintPage/PrintPage.md","stories":"components/PrintPage/PrintPage.stories.svelte","test":"components/PrintPage/PrintPage.test.ts"},"props":[{"name":"as","type":"'section' | 'article' | 'div'","required":false,"description":"The element: a `section` named by its heading, an `article`, or a plain `div`.","default":"'section'"},{"name":"labelledBy","type":"string | undefined","required":false,"description":"The id of the heading that names the section."},{"name":"breakBefore","type":"boolean","required":false,"description":"Starts on a new sheet.","default":"false"},{"name":"keep","type":"boolean","required":false,"description":"Refuses to be split across two sheets: a figure with its caption, a short table.","default":"false"},{"name":"class","type":"string","required":false,"description":"Layout classes only.","default":"''"},{"name":"screen","type":"Snippet","required":false,"description":"What only the screen shows: an action, a notice, a status region."},{"name":"paper","type":"Snippet","required":false,"description":"What only the paper shows: a running title, a note for the reader of the printed copy."},{"name":"children","type":"Snippet","required":true,"description":"The content, shown on both."}],"snippets":["screen","paper","children"],"stories":["part","kept","plain"],"classes":["rl-print-page","rl-print-page--break","rl-print-page--keep","rl-print-page__paper","rl-print-page__screen"],"criteria":[{"code":"12.6","why":"A section named by its heading through aria-labelledby, so the part is a landmark on screen as it is a sheet on paper."},{"code":"10.1","why":"The page marks what the paper shows and what the screen shows; the presentation is in the sheet, never inline, and the content order is the same on both."}],"replaces":["apps/web: the print rules a page wrote for itself in its style block"],"patterns":["print","report-document"],"related":["report-document","heading","table"]},{"name":"ReportDocument","slug":"report-document","level":"view","status":"stable","surfaces":["app"],"since":"2026-09","files":{"component":"components/ReportDocument/ReportDocument.svelte","doc":"components/ReportDocument/ReportDocument.md","stories":"components/ReportDocument/ReportDocument.stories.svelte","test":"components/ReportDocument/ReportDocument.test.ts"},"props":[{"name":"title","type":"string","required":true,"description":"The document's title on screen, as the h1: \"Rapport d'audit\"."},{"name":"subtitle","type":"string | undefined","required":false,"description":"One line under the title: the audit and the standard."},{"name":"coverEyebrow","type":"string","required":true,"description":"The line above the cover's title on paper: \"Rapport d'audit d'accessibilité\"."},{"name":"coverTitle","type":"string","required":true,"description":"The cover's title on paper: the site's name."},{"name":"coverLine","type":"string | undefined","required":false,"description":"A line under the cover's title: the site's address."},{"name":"coverItems","type":"CoverItem[]","required":true,"description":"The cover's facts: the standard, the date, who audited."},{"name":"coverFootnote","type":"string | undefined","required":false,"description":"A sentence at the foot of the cover."},{"name":"printHeader","type":"string","required":true,"description":"The header repeated on every printed page after the cover."},{"name":"printHeaderDetail","type":"string | undefined","required":false,"description":"A second line of the printed header."},{"name":"plan","type":"PlanEntry[]","required":true,"description":"The plan, served as the summary on screen and the table of contents on paper."},{"name":"summaryExtras","type":"Array<{ href: string; label: string }>","required":false,"description":"Entries of the summary that are not in the document: a distinct artefact shown after it.","default":"[]"},{"name":"summaryLabel","type":"string","required":false,"description":"The summary landmark's name.","default":"'Sommaire du rapport'"},{"name":"contentsLabel","type":"string","required":false,"description":"The table of contents' heading on paper.","default":"'Table des matières'"},{"name":"rights","type":"Snippet","required":false,"description":"A page printed after the cover: the rights of reproduction, as a section with its heading."},{"name":"actions","type":"Snippet","required":false,"description":"The screen's actions beside the title: print, export."},{"name":"notice","type":"Snippet","required":false,"description":"What the screen says under the actions and the paper never shows: a status region, a notice."},{"name":"children","type":"Snippet","required":true,"description":"The document's sections."}],"snippets":["rights","actions","notice","children"],"stories":["screen","withRights"],"classes":["rl-action","rl-action-hover","rl-border","rl-border-strong","rl-ink-soft","rl-line","rl-line-strong","rl-radius","rl-report","rl-report__actions","rl-report__contents","rl-report__contents-entry","rl-report__contents-entry--","rl-report__contents-entry--2","rl-report__cover","rl-report__cover-eyebrow","rl-report__cover-foot","rl-report__cover-items","rl-report__cover-line","rl-report__cover-title","rl-report__document","rl-report__head","rl-report__keep","rl-report__paper","rl-report__part","rl-report__print-header","rl-report__print-header-detail","rl-report__print-header-title","rl-report__screen","rl-report__subtitle","rl-report__summary","rl-report__summary-entry","rl-report__summary-entry--","rl-report__summary-entry--2","rl-report__summary-entry--extra","rl-report__summary-link","rl-report__summary-list","rl-report__summary-title","rl-space-1","rl-space-2","rl-space-20","rl-space-3","rl-space-4","rl-space-8","rl-text-lg","rl-text-sm","rl-text-xs","rl-tracking-eyebrow","rl-weight-bold"],"criteria":[{"code":"12.6","why":"The summary is a navigation landmark named for the document; the table of contents on paper is another, with its heading."},{"code":"9.1","why":"The document's title is the h1 on screen; the plan's levels are an indent, and the outline is the sections' own headings."},{"code":"12.8","why":"The summary comes after the document in the DOM and beside it on a wide screen, so the reading order is the document first."},{"code":"10.11","why":"The document column has no minimum width, so a matrix of 106 columns scrolls in its region instead of widening the page."}],"replaces":["apps/web: the printed cover, rights page, table of contents, paper header and sticky summary of the report page, and the print rules of app.css"],"patterns":["report-document"],"related":["heading","table","card","definition-list","print-page"]}],"patterns":[]}