# @rgaa-dev/design > The design system of RGAA Lab. One foundation for the web app, the browser extension's side panel and the public site. > Read packages/design/README.md first, then this inventory, then the component folder you need: Name.svelte is the implementation, Name.md the contract, Name.stories.svelte one story per state, Name.test.ts what is guaranteed. ## Rules - Tokens are the only source of colour, spacing, type and motion: tokens/*.json, generated by scripts/build.mjs. A literal colour anywhere else fails a test. - Every documented colour pair is measured at generation and the build fails below its floor (tokens/contrast.json). - One implementation per component, in Svelte 5, with scoped CSS on tokens and rl- classes; never a Tailwind utility inside a component. - Colour never carries meaning alone: a status has its word, an icon its label, a disabled control its reason. - English API and docs; French words for what a user reads. Status labels and symbols come from @rgaa-dev/core. - Native elements first; every scrolling region focusable and named; a busy control keeps the focus; a live region exists from the first render. - No em dash, in code, docs or generated files. Comments say why. ## Components - BusyRegion (atom, stable; app): props busy, as?, class?, children; stories idle, busy, asSection. components/BusyRegion/ - Button (atom, stable; site, app, extension): props variant?, size?, fullWidth?, busy?, reason?, children; stories primary, secondary, ghost, destructive, inSentence, small, large, fullWidth, asLink, busy, disabled, withReason. components/Button/ - Checkbox (atom, stable; no surface yet): props id, label, name?, value?, checked?, hint?, error?, required?, disabled?; stories plain, checked, withHint, required, error, disabled. components/Checkbox/ - Chip (atom, stable; no surface yet): props tone?, icon?, class?, children; stories neutral, info, success, warning, error, withIcon. components/Chip/ - CodeBlock (atom, stable; app, site): props inline?, label?, children; stories inline, block, long. components/CodeBlock/ - DisabledReason (atom, stable; app, extension): props reason, layout?, class?, children; stories iconButton, stacked, group. components/DisabledReason/ - Eyebrow (atom, stable; site, app): props children; stories plain, long. components/Eyebrow/ - Figure (atom, stable; app): props src, alt, width?, height?, href?, caption?, code?, size?; stories evidence, opensLarge, natural. components/Figure/ - GlossaryTerm (atom, stable; extension): props term, open?, children; stories closed, open. components/GlossaryTerm/ - Heading (atom, stable; site, app): props level, size?, measure?, id?, class?, children; stories display, level2, levelWithoutSize, measured. components/Heading/ - Icon (atom, stable; site, app, extension): props name, size?, class?; stories small, medium, large, statuses, all. components/Icon/ - IconButton (atom, stable; no surface yet): props label, name, size?, variant?, pressed?; stories ghost, secondary, small, large, toggle, asLink, disabled. components/IconButton/ - InlineMarkup (atom, stable; site, extension): props text; stories plain, code, emphasis, mixed, codeKeepsMarkers. components/InlineMarkup/ - Kbd (atom, stable; no surface yet): props keys?, children?; stories single, combination, inSentence. components/Kbd/ - LevelBadge (atom, stable; no surface yet): props level, class?; stories a, aa, aaa, inRow. components/LevelBadge/ - Link (atom, stable; site, app, extension): props href, class?, children; stories plain, inSentence. components/Link/ - LiveRegion (atom, stable; no surface yet): props message?, tone?, visible?, class?; stories polite, assertive, hidden. components/LiveRegion/ - Logo (atom, stable; site, app, extension): props height?, markOnly?; stories header, small, large, markOnly. components/Logo/ - ProgressBar (atom, stable; extension): props id, label, value?, max?, showValue?, strip?, class?; stories measured, indeterminate, done, strip. components/ProgressBar/ - Prose (atom, stable; site, app): props size?, measure?, class?, children; stories base, small, large, narrow. components/Prose/ - ScrollRegion (atom, stable; site, app): props label?, labelledBy?, class?, framed?, children; stories labelled, framed, labelledBy. components/ScrollRegion/ - Select (atom, stable; no surface yet): props id, label, name?, value?, options?, groups?, placeholder?, hint?, error?, required?, disabled?; stories plain, chosen, grouped, required, error, disabled. components/Select/ - SkipLink (atom, stable; design, site, app): props href?, children?; stories plain, custom. components/SkipLink/ - StatusChip (atom, stable; site, app): props status, short?; stories c, nc, na, nt, short. components/StatusChip/ - Switch (atom, stable; no surface yet): props id, label, checked?, onchange?, hint?, disabled?; stories plain, on, withHint, disabled. components/Switch/ - Text (atom, stable; app, extension, site): props as?, size?, tone?, bold?, mono?, class?, children; stories plain, soft, tones, inline. components/Text/ - Tooltip (atom, stable; no surface yet): props text, children; stories inSentence, short. components/Tooltip/ - Accordion (molecule, stable; no surface yet): props items, level?, open?, exclusive?, class?, panel; stories independent, exclusive, preset. components/Accordion/ - Alert (molecule, stable; site, app): props tone?, title?, live?, class?, children; stories info, success, warning, error, titled, liveError, liveSuccess. components/Alert/ - AnswerButtons (molecule, stable; extension): props answers, value?, onanswer, labelledBy, class?; stories none, retained, three. components/AnswerButtons/ - AutoVerdict (molecule, stable; extension): props status, source?, findings?, onshow?, class?, children?; stories fromPage, fromEngine, ownSentence, fromAuditor. components/AutoVerdict/ - Banner (molecule, stable; no surface yet): props label, dismissKey, dismissLabel?, children; stories beta, maintenance. components/Banner/ - Breadcrumb (molecule, stable; no surface yet): props items, label?, class?; stories plain, deep. components/Breadcrumb/ - Callout (molecule, stable; site): props rule?, status?, aside?, class?, children; stories strong, subtle, aside, nc, c. components/Callout/ - Card (molecule, stable; site): props href?, listItem?, children; stories plain, link, inList. components/Card/ - CardGrid (molecule, stable; site): props columns?, list?, children; stories three, two, four, list. components/CardGrid/ - CommentField (molecule, stable; extension): props id, label, value?, placeholder?, optional?, hint?, rows?, class?; stories empty, filled, withHint. components/CommentField/ - ConfirmDialog (molecule, stable; no surface yet): props open?, title, confirmLabel, cancelLabel?, destructive?, onconfirm?, oncancel?, children; stories plain, destructive. components/ConfirmDialog/ - ConformityRate (molecule, stable; app): props rate, unavailableReason?, suffix?, decimals?; stories high, middle, low, average, unavailable, unavailableBare. components/ConformityRate/ - CriteriaRecap (molecule, stable; extension): props criteria, current?, reachable?, onjump?, label?, class?; stories plain, reachable. components/CriteriaRecap/ - DataTable (molecule, stable; site): props caption, captionHidden?, columns, rows, rowKey, cell?, empty?; stories plain, withCell, empty, captionHidden. components/DataTable/ - DefinitionList (molecule, stable; no surface yet): props items, columns?, class?, value?; stories plain, rendered, single. components/DefinitionList/ - Dialog (molecule, stable; no surface yet): props open?, title, size?, onclose?, closeLabel?, class?, children, actions?; stories question, form. components/Dialog/ - Disclosure (molecule, stable; no surface yet): props title, level?, open?, ontoggle?, class?, children; stories closed, open, level. components/Disclosure/ - EmptyState (molecule, stable; no surface yet): props title, level?, icon?, class?, children?, action?; stories withAction, search, withIcon. components/EmptyState/ - ErrorSummary (molecule, stable; no surface yet): props errors, title?, level?, class?; stories form. components/ErrorSummary/ - Field (molecule, stable; site, app): props id, label, type?, value?, name?, required?, hint?, error?, autocomplete?, placeholder?, multiline?, rows?, minlength?, maxlength?, inputmode?, autofocus?, code?, children?; stories text, email, required, withHint, error, multiline, code. components/Field/ - FilterGroup (molecule, stable; app): props label, filters, selected?, exclusive?, onchange?, class?; stories exclusive, several. components/FilterGroup/ - JumpList (molecule, stable; extension): props items, current, onjump, summary, open?, class?; stories closed, open. components/JumpList/ - Menu (molecule, stable; no surface yet): props label, items, onselect, children?, align?, class?; stories dots, labelled, withDisabled. components/Menu/ - OTPField (molecule, stable; app): props id, label?, value?, name?, length?, required?, hint?, error?, autofocus?; stories code, seven, error. components/OTPField/ - PageHeader (molecule, stable; no surface yet): props title, eyebrow?, lead?, level?, class?, actions?; stories plain, withAction, section. components/PageHeader/ - Pagination (molecule, stable; no surface yet): props page, pages, label, href, onnavigate?, total?, perPage?, rowsName?, class?; stories short, long, counted. components/Pagination/ - PasswordField (molecule, stable; app): props id, label, value?, name?, required?, hint?, error?, autocomplete?, minlength?, showLabel?; stories signIn, newPassword, error. components/PasswordField/ - Popover (molecule, stable; app, extension): props label, title?, open?, align?, variant?, class?, children; stories help, controls, end. components/Popover/ - PrevNext (molecule, stable; site, app, extension): props prev?, next?, label?, prevWord?, nextWord?, class?; stories both, first, last, buttons. components/PrevNext/ - PropositionList (molecule, stable; extension): props propositions, onaccept, ondismiss, onannounce?; stories mixed, single, none, hooked. components/PropositionList/ - Question (molecule, stable; extension): props criterion, id?, class?, children, help?; stories single, twoCriteria, withHelp. components/Question/ - RadioGroup (molecule, stable; no surface yet): props name, legend, options, value?, hint?, error?, required?, inline?; stories plain, chosen, inline, required, error. components/RadioGroup/ - ResumeBanner (molecule, stable; extension): props summary, alert?, note?, class?; stories resumed, alert. components/ResumeBanner/ - ReviewFlag (molecule, stable; extension): props label, answer?, differences?, onconfirm, class?; stories withDifferences, bare. components/ReviewFlag/ - SearchField (molecule, stable; no surface yet): props id, label, placeholder?, value?, name?, action?, buttonLabel?, onsearch?, clearHref?, clearLabel?; stories inPlace, withClear. components/SearchField/ - Stat (molecule, stable; site): props value, secondaryValue?, label, tone?, children?; stories plain, fraction, withSentence, toned. components/Stat/ - StatGroup (molecule, stable; no surface yet): props items, label, class?; stories audit, two. components/StatGroup/ - StatusBreakdown (molecule, stable; app): props counts, total, label, bar?; stories finished, inProgress, countsOnly. components/StatusBreakdown/ - StatusCounters (molecule, stable; extension): props counts, label?, class?; stories inProgress, finished. components/StatusCounters/ - StatusToggle (molecule, stable; extension): props criterionId, value?, onchange, statuses?, size?, hooks?, class?; stories untested, pressed, twoOnly, medium, hooked. components/StatusToggle/ - Table (molecule, stable; no surface yet): props caption, captionHidden?, variant?, fixed?, class?, children; stories plain, report, captionHidden. components/Table/ - Tabs (molecule, stable; no surface yet): props tabs, selected?, label, class?, panel; stories plain, preset. components/Tabs/ - ThemeSwitch (molecule, stable; site, app, extension): props name?, onchange?; stories plain, named. components/ThemeSwitch/ - Toast (molecule, stable; no surface yet): props messages, ondismiss?, fixed?, passthrough?, dismissLabel?; stories flow, tones, passthrough. components/Toast/ - WizardFooter (molecule, stable; extension): props last?, onprev?, onnext?, onfinish?, prevDisabled?, finishReason?, prevLabel?, nextLabel?, finishLabel?, class?, counters?; stories middle, first, last, blocked. components/WizardFooter/ - CriterionRow (organism, stable; extension): props id, title, status?, onstatus, comment?, oncomment?, engine?, action?, hooks?, class?, children?; stories untested, judged, engine, hooked. components/CriterionRow/ - DangerZone (organism, stable; app): props title, level?, actions, class?, children?; stories single, several, busy. components/DangerZone/ - Derogation (organism, stable; extension): props label, justification, id, onderogate, onremove, class?; stories closed, posed. components/Derogation/ - EvidenceBlock (organism, stable; extension): props criterionId, items, picking?, oncapture, onshow?, onpick?, oncancel?, hooks?, class?, shot?, show?; stories detected, complete, empty, picking, kept. components/EvidenceBlock/ - Onboarding (organism, stable; app): props title, level?, steps, doneWord?, dismissLabel?, ondismiss?, class?, children?, actions?, footer?; stories welcome, underWay, dismissible. components/Onboarding/ - PageSwitcher (organism, stable; extension): props open?, pages, currentId?, onswitch, ondelete?, addPath?, addNote?, onadd?, onclose?, title?; stories pages, single. components/PageSwitcher/ - Section (organism, stable; site): props rhythm?, rule?, class?, children; stories lead, plain, tight, ruled. components/Section/ - ThemeList (organism, stable; extension): props themes, open?, ontoggle?, onstatus, oncomment?, onwizard?, wizardReason?, busyTheme?, criterionAction?, hooks?, level?, class?, detail?; stories themes, offPage, collecting, hooked, partiel. components/ThemeList/ - WizardFrame (organism, stable; extension): props title, kind?, progress?, onclose?, closeLabel?, class?, tools?, above?, children, footer?; stories bare, withProgress, withFooter. components/WizardFrame/ - AppShell (view, stable; app): props home, sections, currentPath, collapsed?, oncollapse?, busy?, announcement?, email, logoutAction, logoutEnhance?, siteHref?, siteLabel?, navLabel?, themeName?, banner?, as?, children; stories open, rail, closed, busy, withBanner. components/AppShell/ - AuthShell (view, stable; app): props title, logoHref?, themeName?, width?, as?, headingLevel?, lead?, children, footer?; stories signIn, message. components/AuthShell/ - PanelShell (view, stable; extension): props label, as?, hidden?, id?, class?, banner?, header?, children, footer?; stories bare, full, underWizard. components/PanelShell/ - PrintPage (view, stable; app, design): props as?, labelledBy?, breakBefore?, keep?, class?, screen?, paper?, children; stories part, kept, plain. components/PrintPage/ - ReportDocument (view, stable; app): props title, subtitle?, coverEyebrow, coverTitle, coverLine?, coverItems, coverFootnote?, printHeader, printHeaderDetail?, plan, summaryExtras?, summaryLabel?, contentsLabel?, rights?, actions?, notice?, children; stories screen, withRights. components/ReportDocument/ ## Adding one Create components/Name/ with the four files, declare an interface Props with a JSDoc line per prop, one exported snippet per state in the stories file, a test that mounts every story, and the doc on the template of an existing one. Then: pnpm --filter @rgaa-dev/design build:tokens (regenerates this file and manifest.json), test. # The package README # @rgaa-dev/design The design system of RGAA Lab. One foundation, two application levels: the web app (`apps/web`) and the extension's side panel (`apps/extension`), plus the public site (`apps/site`) as a third consumer. The reference website is `apps/design`, published at design.rgaa.dev. This file states the contract. The plan that introduced it is dated 2026-09-24; the sections below say what already holds and what is still being built. ## The five rules 1. **Tokens are the only source of colour, spacing, type and motion.** They live in `tokens/*.json` and are generated into CSS, TypeScript and the Tailwind preset by `scripts/build.mjs`. A literal colour anywhere else fails a test. 2. **Every colour pair is measured, not chosen.** `tokens/contrast.json` lists the pairs and their thresholds; the generator computes the ratios, writes them into the CSS comments and fails the build when one drops below its floor, in light, dark and print. 3. **One implementation per component, in Svelte 5, with scoped CSS.** A component references tokens only, writes `rl-` classes only, and never a Tailwind utility, so the same file renders the same in the app, the panel and the site. 4. **Colour never carries meaning alone.** A status has its word, an icon has its label, a disabled control has its reason. This is criterion 3.1 of the RGAA, and the product audits other sites against it. 5. **English API, French words.** Component names, props, classes and docs are English. Everything a user reads is French. Labels and symbols for audit statuses come from `@rgaa-dev/core`, never from here. ## Layout ``` tokens/ primitives.json, semantic.json, contrast.json the source scripts/ build.mjs (every generator, --check), lib/ (one module per output) styles/ tokens.css, tokens.scoped.css (generated), base.css, print.css fonts/ fonts.css, LICENSE-OFL.txt theme/ theme.ts, boot.js icons/ svg/ (the source drawings), paths.ts (generated), index.ts (svgIcon and statusIcon for markup written as a string) components/ one folder per component: Name.svelte, Name.md, Name.stories.svelte, Name.test.ts tooling/ rehype-tables.js, the mdsvex plugin for Markdown tables (scope and caption) manifest.json generated inventory, schema in manifest.schema.json llms.txt generated summary for LLM contributors ``` Today (0.2.0) the tokens, the theme, the fonts, the base layer, the component folders and the generated inventory are in place, and the 85 components (the 18 original ones rewritten, plus 67 written on the target shape: Icon, Link, BusyRegion, Dialog, ConfirmDialog, LiveRegion, SkipLink, Kbd, Disclosure, Accordion, Tabs, ErrorSummary, Checkbox, RadioGroup, Switch, Select, IconButton, Breadcrumb, Chip, LevelBadge, EmptyState, Table, Menu, Toast, Pagination, ProgressBar, Tooltip, SearchField, Banner, StatGroup, PageHeader, DefinitionList, AuthShell, AppShell, StatusBreakdown, CodeBlock, Figure, ReportDocument, DisabledReason, Text, PasswordField, OTPField, FilterGroup, Popover, PrevNext, PrintPage, Onboarding, DangerZone, the wizard skeleton of the extension's panel: WizardFrame, Question, AnswerButtons, CommentField, WizardFooter, CriteriaRecap, StatusCounters, AutoVerdict, Derogation, JumpList, ResumeBanner, ReviewFlag, GlossaryTerm, and the panel's own screens: PanelShell, ThemeList, CriterionRow, StatusToggle, EvidenceBlock, PageSwitcher) are on the English API with scoped CSS on the tokens, `status: stable`. The base layer's `btn-*`, `card`, `chip`, `tableau*`, `h-display` and `link-underline` rules left with their last raw use; it keeps the document defaults, the focus ring, `rl-sr-only`, the page column `rl-container` (and `--wide`), `rl-prose` and the motion switch. The changelog says where things stand. ## Consuming it - App and site: in `app.css`, import in this order `@rgaa-dev/design/fonts.css`, `@rgaa-dev/design/tokens.css`, `tailwindcss/base`, `@rgaa-dev/design/base.css`, `tailwindcss/components`, `tailwindcss/utilities` (a test holds the order); use the Tailwind preset for page layout; import components from `@rgaa-dev/design/components`. Apply the theme before first paint with the code of `theme/boot.js` inline in `app.html`, and read or change it through `@rgaa-dev/design/theme`. - Extension panel: the same fonts, tokens and base, bundled by esbuild from `sidepanel.css`; `theme/boot.js` built to a file and loaded before the stylesheets; the same components, compiled by the in-repo plugin. - Extension overlays injected into audited pages: `tokens.scoped.css` on a host element carrying `class="rl-scope"` and `data-theme`, never on the page's `:root`. - Documents the product exports (reports, declarations, emails): the generated token modules, never a pasted copy. - A surface that prints a document (the app for its report, the design site for its print pattern) imports `@rgaa-dev/design/print.css` after `base.css`: the page's size and margins, the breaks, the headings kept with their text, the address after a link. `ReportDocument` and `PrintPage` rely on it; the colours on paper are the token sheet's print set, loaded with the tokens. - The site's Markdown pipeline takes `@rgaa-dev/design/tooling/rehype-tables.js` in its mdsvex `rehypePlugins`, for `scope="col"` and the `Tableau :` caption on every table. ## Adding or changing a component 1. Pick the level (atom, molecule, organism, view) and an English, singular, PascalCase name. 2. Create `components/Name/` with the four files. Nothing else goes in the folder. 3. `Name.svelte`: a header comment that says why the component exists, an `interface Props` with a JSDoc line on every prop, `rl-` classes, tokens only. 4. `Name.stories.svelte`: one exported snippet per state that changes markup or ARIA. A story that writes an id or a group name takes a `scope` argument and suffixes it (`id="story-nom{scope}"`): the design site shows every story several times on one page, and the test harness passes none. 5. `Name.md`: the documented sections, in order, with the RGAA criteria the component satisfies and why. 6. `Name.test.ts`: mount every story and assert the accessibility contract (name, role, keyboard, focus). 7. `pnpm --filter @rgaa-dev/design build:tokens` (it regenerates `manifest.json` and `llms.txt` too), then `test`, then look at the page on the design site in all four conditions. 8. `pnpm --filter @rgaa-dev/design-site build`, then `captures`: the forced-colours picture of every story is committed under `apps/design/static/captures/`, and a test of the site refuses a capture older than its sources. `run audit` (the bare `audit` is pnpm's own command) and `keyboard` run the engine and a Tab walk over every page of the site. 9. A changelog line. If the component retires a hand-built pattern somewhere, list it under `replaces` and remove the copy in the same branch. ## Commands ``` pnpm --filter @rgaa-dev/design build:tokens regenerate from tokens/*.json and the component folders (fails on a contrast breach) pnpm --filter @rgaa-dev/design check:generated what CI runs: the committed files match the source pnpm --filter @rgaa-dev/design test pnpm --filter @rgaa-dev/design typecheck svelte-check, so the stories' module exports are typed ``` ## Reading it as a machine `manifest.json` lists every component with its level, status, surfaces, props (type, default, description read from the `Props` interface), snippets, stories, `rl-` classes and criteria; `manifest.schema.json` is its schema. `llms.txt` is the same inventory as a page of text, rules first. Both are generated; a doc that breaks the section order, claims a criterion the referential does not have, or annotates a story that does not exist fails `docs.test.ts` before the generator runs. # BusyRegion --- name: BusyRegion slug: busy-region level: atom status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the zone-occupee class and aria-busy written by hand on the lab layout's main" 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." patterns: [app-shell] related: [button] --- ## Purpose The busy state at the scale of a page: while a navigation loads, the content area drops to the hollow, takes a rule at the top and a fixed ring at the corner, and says so with `aria-busy`. The class and the attribute go together, so the rule cannot repaint everything that already carries `aria-busy`. ## When to use, when not Use it on the content area of an app shell, driven by the navigation state, or on a part of a page that reloads on its own. Do not use it for a button (Button has `busy`) nor for a wait shorter than a frame. ## Anatomy 1. A `main`, `section` or `div`, with `aria-busy` when busy. 2. Under motion, a ring fixed at the top right of the viewport, in the soft ink. ## States Idle and busy. ## Accessibility contract - Role and name: those of the element chosen with `as`; the component adds none. - Keyboard, focus: none. Busy never moves the focus. - Live regions: none; `aria-busy` is the state, and the page announces the outcome when the wait ends. - Reduced motion: no ring; the hollow and the rule carry the wait. ## Content rules None of its own. ## Do and don't - Do drive `busy` from the navigation state, so it clears by itself. - Do not set `aria-busy` by hand next to it. ## Examples ```svelte {@render children()} ``` ## Surfaces - App: the lab layout's content area, through the AppShell. ## Related Button. ## Decisions - 2026-09-24: created from the base layer's `zone-occupee`, which stayed for the lab layout until the shell moved to the atoms on 2026-09-25. # Button --- name: Button slug: button level: atom status: stable surfaces: [site, app, extension] since: 2026-08 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" 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)." patterns: [destructive-confirmation, disabled-with-reason, form-with-error-summary] related: [alert, icon] --- ## Purpose An action or a link, in one component that renders `a` when there is a destination and `button` when there is an action. The distinction is what matters for the keyboard and for assistive technology, and it is the one lost by styling a `div`. ## When to use, when not Use it for everything a user activates. Give `href` for a destination, nothing for an action. Do not use it for a toggle that has a pressed state (that needs `aria-pressed`, a different component), and do not put an icon alone in it without a visible or hidden word. ## Anatomy 1. The element: `a` or `button`, `type="button"` by default so it never submits a form by accident. 2. The label, always a verb. 3. The busy ring, added beside the label when `busy` is set, under motion only. 4. With `reason`, the sentence next to the unavailable button, tied by `aria-describedby`. ## States Five variants, three sizes, full width, a link form, unavailable, unavailable with a reason, busy. The `link` variant is an action written in a sentence: it keeps the button element and takes the underline. ## Accessibility contract - Role and name: button or link; the name is the label. - Keyboard: Tab, Enter, and Space for a button. - Focus: the ring of the base layer, two pixels of the action colour with an offset. - Unavailable (`disabled`) and busy (`aria-busy` with `aria-disabled`) are different states. Busy keeps the focus on the button, because the user just pressed it; the caller refuses the second submit at the source. - Unavailable is never shown with opacity: a translucent wash lowers the contrast of the label. The soft ink and the light line say it, and `reason` says why. ## Content rules The label is a verb in the infinitive ("Enregistrer", "Supprimer l'audit"), never "OK", never an ellipsis. A destructive action says the verb and the object. A reason is a sentence that says what to do first ("Terminez l'audit pour publier le taux."). ## Do and don't - Do give the reason on an unavailable button rather than a bare `disabled`. - Do not combine `busy` with the app's form busy helper: they would fight over the same attribute. - Do not use the destructive variant as an accent; it borrows the red of non-conformity because it shares the meaning. - Do use the `link` variant for an action that sits in a sentence or beside a title ("Renommer", "Modifier la description"); do not use it for a destination, which is the Link component: the underline is shared, the element is not. ## Examples ```svelte ``` ## Surfaces - Site: 16 files, plus 37 files still writing the classes by hand. - App: the auth pages, the settings, the admin screens. - Extension: to come with the panel's migration. ## Related Alert, Icon. ## Decisions - 2026-08: `...rest` passes the attributes the component does not decide, because a component one works around protects nothing. - 2026-08: `aria-busy` absent rather than `"false"`: an idle button says nothing of its rest. - 2026-09-24: props `variant`, `size`, `fullWidth`, `busy` and the new `reason`; the four variants and the busy ring are `rl-button` rules on the tokens, the base layer keeps `btn-*` for the pages that still write them by hand. - 2026-09-26: the `link` variant, for the app's five buttons that wore the base layer's underline class inside a sentence (rename, edit a description or a comment); the app writes no base class any more. - 2026-09-27: the base layer's `btn-*` rules leave with their last raw use, the site's call to action under a sheet's criteria, which is this component now; `rl-prose` excludes `.rl-button` from its link rule instead of matching a `btn-` name. # Checkbox --- name: Checkbox slug: checkbox level: atom status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: the hand-built checkboxes of the settings, the sharing and the admin forms, once the app is ported" 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." patterns: [form-with-error-summary] related: [radio-group, switch, field] --- ## Purpose A checkbox with its label, native. The browser gives the focus, the Space key, the state to assistive technology and a mark that says it without colour; the component ties the label, the hint and the error so none can be left aside. ## When to use, when not Use it for a yes-or-no choice inside a form, submitted with it, and for a list of independent options. Not for a setting that applies at once (Switch), not for an exclusive choice (RadioGroup). ## Anatomy 1. The control, native, sized and tuned by `accent-color`. 2. The label, with the required mark when needed. 3. The hint and the error, under, tied by `aria-describedby`. ## States Plain, checked, with a hint, required, in error, and unavailable. ## Accessibility contract - Role and name: a checkbox, named by its label; "(obligatoire)" in the name when required. - Keyboard, focus: Tab reaches it, Space toggles; the ring is the system's. - Live regions: the error is `role="alert"`, so a correction is announced without moving the focus. ## Content rules French. The label says what checking means, as a statement ("J'accepte que le rapport soit publié") or a noun ("Formulaires"). The error says what to do ("Cochez la case pour publier le rapport."). ## Do and don't - Do keep several checkboxes of one question in a `fieldset` with a `legend`; RadioGroup shows the shape. - Do not redraw the control with `display: none` on the input. - Do not use a checkbox for a setting that applies at once; that is Switch. ## Examples ```svelte ``` ## Surfaces - App: the settings, the sharing options, the admin forms; not ported yet. ## Related RadioGroup, Switch, Field. ## Decisions - 2026-09-24: native control, `accent-color`, as the site's cases-à-cocher demo argues; redrawing would cost the mark and the system's focus ring for nothing. # Chip --- name: Chip slug: chip level: atom status: stable surfaces: [] since: 2026-09 replaces: - "packages/design base.css: the chip class, and its raw uses in the app, once the app is ported" 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." related: [status-chip, level-badge] --- ## Purpose A short word in a frame: a state, a category, a count. The word carries the information and the tone only confirms it. Audit statuses are not chips: StatusChip has its own drawing and its words from core. ## When to use, when not Use it next to a title or in a table cell for a state ("Brouillon", "Terminé"), a category or a small count. Not for an audit status (StatusChip), not for a criterion level (LevelBadge), not as a button or a link. ## Anatomy 1. A `span` with the line and, for a tone, the ink. 2. An optional drawing, hidden. 3. The word. ## States Neutral, the four tones, and with a drawing. ## Accessibility contract - Role and name: none; it is text in the flow, read as its word. - Keyboard, focus: none. - Live regions: none. ## Content rules French. One or two words, a noun or a participle ("Terminé", "En attente"); never a sentence. ## Do and don't - Do choose the tone from the meaning: a warning tone for a decoration is a lie. - Do not put a chip inside a button's label; the button's text is its name. ## Examples ```svelte Terminé Brouillon ``` ## Surfaces - App: the audit's state and closure, the site's audit list, the members' roles, the users' state and second factor, an expired token. ## Related StatusChip, LevelBadge. ## Decisions - 2026-09-24: the same tones as Alert, so a state has one ink whether it is a message or a chip. # CodeBlock --- name: CodeBlock slug: code-block level: atom status: stable surfaces: [app, site] since: 2026-09 replaces: - "apps/web: the code elements and mono spans written by hand for selectors, secrets and tokens" 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." patterns: [] related: [kbd, figure] --- ## Purpose Text the reader copies rather than reads: a selector, a secret key, a token, a line of code. In the mono face, on the sunken surface, inline in the running text or as a block. ## When to use, when not Use it for a value that is code or behaves like it: a CSS selector under a comment, a secret to type into an authenticator, a masked token. Not for a key of the keyboard (Kbd), not for an identifier that reads as a word, such as a criterion's number. ## Anatomy 1. Inline: a `code` element. 2. Block: a `pre` holding a `code`; with `label`, the `pre` is a `group` named by it, since a bare `pre` is generic and cannot take a name. ## States Inline in a sentence, a block, and a long value that wraps. ## Accessibility contract - Role and name: `code` inline, read as code by the readers that say so; a block is a `pre`, which becomes a `group` named by `label` when one is given, and stays generic and nameless otherwise. - Keyboard, focus: none; a block that overflows does not scroll, it wraps. - Live regions: none. ## Content rules The text is the value, verbatim, nothing added. The label, when there is one, says what the value is ("Clé de secours"), in French. ## Do and don't - Do give a block a label when its content is not obvious from the sentence before it. - Do not put a sentence in it: it is for values, and its face says so. ## Examples ```svelte

Le sélecteur footer .legal-links désigne l'élément.

JBSW Y3DP EHPK 3PXP ``` ## Surfaces - App: the selectors of the audited page and the report, the second factor's key, the temporary password, the masked tokens. - Site: the sheets' code samples, when they leave the hand-written `pre`. ## Related Kbd, Figure. ## Decisions - 2026-09-26: no copy button: the product's values are short and selected in one gesture, and a button per value is a stop per value at the keyboard. # DisabledReason --- name: DisabledReason slug: disabled-reason level: atom status: stable surfaces: [app, extension] since: 2026-09 replaces: - "apps/extension: the title attribute on the theme rows' disabled wizard button ('Revenez à la page auditée')" 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." patterns: [disabled-with-reason] related: [button, icon-button] --- ## Purpose A control that refuses, and says why next to it. The component writes the reason as a visible sentence and hands the control the two attributes that make the refusal honest: `disabled`, and `aria-describedby` pointing at the sentence. `Button` carries the same contract as its own `reason` prop; this component is for every other control, and for a group that shares one reason. ## When to use, when not Use it around an icon button, a switch, a field or a group of controls that cannot be used until a condition is met: off the audited page, a report already published, an audit not finished. Use `Button`'s `reason` prop for a lone button. Do not use it for a control that is busy (that is `busy`, which keeps the focus), and do not use it to hide a control the user can never use: remove that one. ## Anatomy 1. The wrapper, the reason beside or under the control. 2. The control, rendered by the snippet with the marks spread on it. 3. The reason, a sentence in the soft ink, with the id the control describes itself by. ## States An icon button, a full-width control with the reason under it, and a group sharing one reason. ## Accessibility contract - Role and name: the control's own; the sentence is its description. - Keyboard, focus: a disabled control leaves the tab order, as the platform does; the reason stays readable by the virtual cursor and in the description. - Live regions: none. A reason that changes is read the next time the control is reached; a page that wants it announced at once writes it in a LiveRegion too. ## Content rules French. The reason says what to do first, in the imperative: "Terminez l'audit pour publier.", "Revenez à la page auditée pour capturer.". Never "Indisponible" alone, never the negative fact ("Rapport non relu"). ## Do and don't - Do spread the marks on every control of a group; the sentence is written once. - Do not write the reason in a `title`: a disabled element gets no hover, and a tooltip needs a pointer. - Do not grey the control with opacity; the marks and the tokens' disabled rules do the drawing. ## Examples ```svelte {#snippet children(marks)}{/snippet} ``` ## Surfaces - App: the audit settings once the report is published, the export buttons of an unfinished audit. - Extension: the wizard buttons of the theme rows while the tab is off the audited page. ## Related Button, IconButton. ## Decisions - 2026-09-26: the snippet receives the marks rather than the component wrapping the control in a fieldset: a `fieldset disabled` would work for native controls only, and would put a group role around one button. - 2026-09-26: `Button` keeps its own `reason` prop rather than composing this component: nine of ten reasons in the product sit on a button, and one prop reads better than a snippet. # Eyebrow --- name: Eyebrow slug: eyebrow level: atom status: stable surfaces: [site, app] since: 2026-08 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] --- ## Purpose The small line above a title that situates the page at a glance: the version of the standard, the scope, the browsers. It is never decorative, so what it says has to be true and useful. ## When to use, when not Use it above the first heading of a page or a section, for one fact the reader needs before the title. Do not use it as a label, a badge or a second title, and never for text that carries the argument: that is the heading's job. ## Anatomy 1. A paragraph in small capitals, soft ink, wide tracking. ## States One state: the eyebrow has no interaction and no variant. ## Accessibility contract - Role and name: a paragraph. It is read in the flow, before the heading it introduces. - Keyboard: none. - Focus: none. - Live regions: none. ## Content rules Short, factual, French. Middle dots separate items ("RGAA 4.1.2 · Chrome et Firefox · Gratuit"). No sentence, no verb. ## Do and don't - Do put one fact per item, and only facts the reader can verify. - Do not turn it into a heading because it looks like one: the outline is the heading's, criterion 9.1. ## Examples ```svelte RGAA 4.1.2 · Chrome et Firefox · Gratuit Les 106 critères. ``` ## Surfaces - Site: 34 pages open with it. - App: the error page and the contents heading of the report. ## Related Heading, Prose, Section. ## Decisions - 2026-08: a paragraph rather than a heading, so it never enters the outline. - 2026-09-24: an `rl-eyebrow` rule on the tokens, the eyebrow tracking the tokens already defined for it. # Figure --- name: Figure slug: figure level: atom status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the figure written by hand on the audited page and in the report for the screenshots of a non-conformity" 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." patterns: [] related: [code-block] --- ## Purpose A picture with its caption. In the product it is evidence: the element found non-compliant, photographed by the extension at the time of the audit, with the selector under it. ## When to use, when not Use it for a picture that proves or shows something the text cannot: a screenshot of an element, a diagram. Not for decoration, which the direction refuses, and not for the mark, which is Logo. ## Anatomy 1. The picture, with its intrinsic size, lazy. 2. A link around it when it opens large, named for it. 3. The caption, plain or in the mono face. ## States Evidence with its selector, a picture that opens large, and a picture at its natural size. ## Accessibility contract - Role and name: `figure` with a `figcaption`; the `img` has the alt given, extended with "(ouvrir en grand, nouvelle fenêtre)" when it is a link. - Keyboard, focus: the link, when there is one, with the base layer's ring. - Live regions: none. ## Content rules French. The alt is a sentence that names the element and the criterion ("Élément footer .legal-links, en défaut sur le critère 3.2"). The caption is the selector, or the source of a diagram. ## Do and don't - Do give the intrinsic width and height, so the page does not jump. - Do write the alt as what the picture proves, not "capture d'écran". - Do not use it for a picture that says nothing the text does not: the direction forbids illustration. ## Examples ```svelte
``` ## Surfaces - App: the screenshots of a non-conformity on the audited page and in the report. ## Related CodeBlock. ## Decisions - 2026-09-26: the whole picture is the link rather than a "voir en grand" link under it, since the picture is what one wants larger, and one target beats two. # GlossaryTerm --- name: GlossaryTerm slug: glossary-term level: atom status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: the .a11y-terme buttons the wizards wrote and attacherLesTermes wired" 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." related: [disclosure, question, tooltip] --- ## Purpose A term of the referential inside a wizard's sentence, with its definition a press away, in the flow. ## When to use, when not Use it for a word of the referential the auditor may not know ("alternative textuelle", "contexte du lien"). Not for a help sentence about the whole question, which is the Question's help line, and not for a tooltip on a control. ## Anatomy 1. The term, a button with a dotted line under it. 2. The definition, a block under the sentence, hidden until pressed. ## States Closed, and open. ## Accessibility contract - Role and name: a `button` named by the term with "(définition)" hidden after it; `aria-expanded` says the state and `aria-controls` names the block. - Keyboard, focus: Tab reaches it, Enter or Space toggles; the focus stays on the button. - Live regions: none; the block opens in the flow, under the sentence. ## Content rules French; the term as the referential's glossary writes it, the definition quoted from it or one sentence of the product's. ## Do and don't - Do keep the definition short; the glossary entry on the site is the long version. - Do not turn the term into a link: leaving the panel loses the audit in progress. ## Examples ```svelte

L'Le texte associé à une image, restitué à sa place par un lecteur d'écran. décrit-elle l'image ?

``` ## Surfaces - Extension: the wizard questions. ## Related Disclosure, Question, Tooltip. ## Decisions - 2026-09-26: a button and a block in the flow rather than a tooltip: the tool cannot commit the fault it audits, and a block that stays is read at the reader's pace. # Heading --- name: Heading slug: heading level: atom status: stable surfaces: [site, app] since: 2026-08 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] --- ## Purpose A heading whose level and size are two different things. Choosing a tag "for the size that looks right" is exactly the fault criterion 9.1 sanctions; here the level is required and the size is a separate parameter on the type scale. ## When to use, when not Use it for every heading of a page: the thesis (display), the sections, the sub-sections. Do not use it for a bold line that is not a heading, and do not skip a level to get a smaller size: change `size` instead. ## Anatomy 1. An `h1` to `h4`, in the display typography (weight 700, tight tracking, balanced wrapping). 2. Optionally a measure in characters. ## States Four levels, six sizes, an optional measure. No interaction. ## Accessibility contract - Role and name: the heading of its level; the name is its text. - Keyboard, focus, live regions: none. Screen readers navigate by heading level, which is why the level must be true. - One `h1` per page; levels never skip. Site and app tests check both. ## Content rules French, no trailing period on a short heading, a period when the heading is a sentence ("Les 106 critères."). Say the thing, not the section's name. ## Do and don't - Do pick the level from the outline and then the size from the place. - Do not use `size` to fake a hierarchy the outline does not have. - Do not render two `h1`. ## Examples ```svelte Les composants, dans tous leurs états. Actions Paramètres du rapport ``` ## Surfaces - Site: every page (38 files). - App: the auth pages, the settings, the admin screens, the audit pages. ## Related Eyebrow, Prose, Section. ## Decisions - 2026-08: `level` is required and has no default, so a heading cannot be added without deciding where it sits. - 2026-09-24: the display weight is written as 700; the font has no 750 and every browser rendered 700. - 2026-09-24: `rl-heading` rules on the display tokens and the type scale; the base layer keeps `h-display` for the article layout until it moves to the atoms. - 2026-09-27: `h-display` leaves the base layer, with no raw use left; `rl-prose` carries the display face for a Markdown body's headings. - 2026-09-26: an `id` prop, since a section that names itself by its heading needs one, and a `base` size for the fourth level of the report, a criterion inside a theme inside a part. # Icon --- name: Icon slug: icon level: atom status: stable surfaces: [site, app, extension] since: 2026-09 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" 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." patterns: [] related: [status-chip, alert, button] --- ## Purpose A drawing that goes with a word. Twenty-three stroke icons on a 24-unit grid, drawn in the current colour, so they follow the ink in both themes and under forced colours. The icon is never the message: the word is, and the icon is hidden from assistive technology whatever the caller does. ## When to use, when not Use it next to a label in a control, a chip, a heading or a navigation entry, when the shape helps a sighted reader find it faster. Do not use it alone, and do not use it where a character does the job: an audit status in running text or a document takes the symbol from `@rgaa-dev/core`. ## Anatomy 1. An `svg` with `aria-hidden="true"` and `focusable="false"`. 2. One or more `path` elements from `icons/paths.ts`, generated from `icons/svg`. ## States The three sizes, the five status drawings with their words, and the whole set. ## Accessibility contract - Role and name: none. The svg is hidden; the parent's text is the name. - Keyboard and focus: none; `focusable="false"` keeps old Edge from tabbing to it. - Colour: `currentColor`, so the icon has exactly the contrast of its text. - Forced colours: a stroke in `currentColor` is repainted by the system palette like text. ## Content rules The word next to the icon says what the icon means, in French, visible or in `rl-sr-only`. A status icon comes with the status label from `@rgaa-dev/core`. ## Do and don't - Do pick a status drawing through `statusIcon`, never by shape. - Do not give the svg a title or a label: two names for one thing is one too many. ## Examples ```svelte Conforme Non conforme ``` For an overlay written without Svelte, `svgIcon('check', { size: 'sm' })` from `@rgaa-dev/design/icons` returns the same markup as a string. ## Surfaces - Site: the header and the sheet navigation, as they move to the atoms. - App: the lab layout, as it moves to AppShell. - Extension: the panel and the page furniture, through `svgIcon`. ## Related StatusChip, Alert, Button. ## Decisions - 2026-09-24: `cross` and `close` are the same drawing under two names, because they are two meanings: an audit verdict and a control that dismisses. A reader of the call site should know which one is meant. - 2026-09-24: the eight navigation drawings (menu, chevron-left, home, document, building, settings, shield-check, logout) are the lab layout's, moved into the set as they were, so the rail does not change shape. - 2026-09-24: no fill, no colour, only paths: a test on the source files refuses anything else, so an icon can be pasted from any set as long as it is redrawn on the grid. # IconButton --- name: IconButton slug: icon-button level: atom status: stable surfaces: [] since: 2026-09 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" 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." patterns: [app-shell] related: [button, icon] --- ## Purpose A button that shows a drawing and says a word. The product's toolbars are full of them; an icon alone is a button with no name, the fault the product finds every day on other sites. Here the name is required, the drawing is hidden, and a toggle says its state. ## When to use, when not Use it in a toolbar, a table row or a header where the space is a square: close, menu, previous, next, settings. Do not use it for a page's action (Button, with a verb), and never with an icon whose meaning is not universal: "Enregistrer" is a word, not a floppy disk. ## Anatomy 1. A square button or link, the size of a control. 2. The drawing, from the icon set, hidden. 3. The name, in `aria-label`. ## States Ghost, secondary, small, large, a toggle, a link, and unavailable. ## Accessibility contract - Role and name: a button or a link, named by `label`. A toggle carries `aria-pressed`. - Keyboard, focus: the browser's; the ring is the system's. - Live regions: none; a toggle announces its own state. - Pointer: the whole square is the target, at least the control size, above the pointer target floor. ## Content rules French. The name is what the button does, a verb or a noun the user knows ("Fermer", "Menu", "Page suivante", "Paramètres"), never the icon's name. ## Do and don't - Do write the name as the user would say it, not as the drawing is called. - Do use `pressed` only for a toggle, and leave it absent otherwise. - Do not rely on a `title` tooltip for the name. ## Examples ```svelte ``` ## Surfaces - App: the lab layout's rail and drawer, the row menus; not ported yet. - Extension: the panel header and the wizard toolbar, when it is on Svelte. ## Related Button, Icon. ## Decisions - 2026-09-24: `label` is required and not defaulted from the icon's name: the icon's name is not what the user needs to hear. # InlineMarkup --- name: InlineMarkup slug: inline-markup level: atom status: stable surfaces: [site, extension] since: 2026-08 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] --- ## Purpose Text with two enrichments and two only: backticks give `code`, double asterisks give `strong`, single ones give `em`. The criteria sheets are typed data, not Markdown, and the extension must render them offline without a parser; but accessibility documentation without code fragments is unreadable, since `alt=""` and no `alt` do not mean the same thing. ## When to use, when not Use it for a sentence from a criterion sheet or a glossary entry. Do not use it for a paragraph that needs links, headings or lists: that text should be split into fields. ## Anatomy 1. Segments of plain text. 2. `code` spans on the hollow background. 3. `strong` and `em` spans. ## States Plain text, with code, with emphasis, mixed. ## Accessibility contract - Role and name: inline text; `code`, `strong` and `em` are semantic elements a screen reader can announce. - No HTML string is ever built: the text is split into segments and rendered by real elements. ## Content rules The two markers do not nest: inside a code span a double asterisk stays literal, which is what a code fragment needs. ## Do and don't - Do keep a sheet's sentence to these two enrichments. - Do not add links or lists here; split the sheet instead. ## Examples ```svelte ``` ## Surfaces - Site: the criteria sheets. - Extension: the criterion documentation in the panel, once on Svelte. ## Related Prose. ## Decisions - 2026-08: italic was first omitted and eight passages showed bare asterisks; a format four authors get wrong is not an authors' problem, so it is accepted and a test flags any marker left unrendered. - 2026-09-24: renamed from Riche, prop `text`; the code span is a scoped rule on the tokens. # Kbd --- name: Kbd slug: kbd level: atom status: stable surfaces: [] since: 2026-09 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] --- ## Purpose A key, or a combination of keys, as the user presses them. The product explains keyboard use everywhere: the extension's wizard shortcuts, the site's sheets, the app's help. A key drawn as a key is read as one, and the `kbd` element says so to assistive technology. ## When to use, when not Use it for a key or a shortcut in running text or in a help table. Not for a button's label, not for code (that is InlineMarkup's code span). ## Anatomy 1. A `kbd` for one key; for a combination, an outer `kbd` holding one `kbd` per key, joined by a plus. ## States One key, a combination, and inside a sentence. ## Accessibility contract - Role and name: the `kbd` element; a screen reader reads the key's name. The plus between keys is read too, "Ctrl plus K", which is how the combination is said aloud. - Keyboard, focus: none, it is text. - Live regions: none. ## Content rules French key names as the keyboards of the product's users write them: Échap, Entrée, Maj, Ctrl, Tab, Espace; arrows as "Flèche droite". Not the English names. ## Do and don't - Do write the key as it is printed on the key cap. - Do not use it to style a button or a code sample. ## Examples ```svelte

Appuyez sur Tab pour passer au champ suivant, pour revenir.

``` ## Surfaces - Extension: the wizard shortcut help, when the panel is on Svelte. - Site: the sheets that name keys. ## Related InlineMarkup. ## Decisions - 2026-09-24: nested `kbd` for a combination, as the HTML standard describes, rather than one element with a plus in its text. # LevelBadge --- name: LevelBadge slug: level-badge level: atom status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: the level letters written raw next to the criteria of the audit and the report, once the app is ported" criteria: - code: "3.1" why: "The level is a code, not a colour: the three tiers share one drawing and the word says which." related: [chip, status-chip] --- ## Purpose The level of a criterion, A, AA or AAA, as three letters in a mono frame, read as "niveau AA". The word is hidden before the code so a list of criteria read aloud says what the badge means. ## When to use, when not Use it next to a criterion wherever the level matters: the audit page, the report, the criteria list. Not for anything that is not a level of the referential. ## Anatomy 1. A `span` in the mono face with the strong line. 2. The hidden word "Niveau". 3. The level. ## States A, AA, AAA, and in a criterion row. ## Accessibility contract - Role and name: none; text in the flow, read "Niveau AA". - Keyboard, focus: none. - Live regions: none. ## Content rules The level as the referential writes it, nothing else. ## Do and don't - Do not colour the levels: they are tiers of the standard, not degrees of gravity. ## Examples ```svelte ``` ## Surfaces - App: the audit page and the report; not ported yet. - Extension: the criterion rows, when the panel is on Svelte. ## Related Chip, StatusChip. ## Decisions - 2026-09-24: the hidden word is "Niveau", as the referential names it; a badge reading "A A" aloud was the fault on the audit page. # Link --- name: Link slug: link level: atom status: stable surfaces: [site, app, extension] since: 2026-09 replaces: - "apps/site and apps/web: the link-underline class written by hand in fifty files" 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." patterns: [] related: [button] --- ## Purpose A link in running text, underlined. The underline carries the link and the colour only supports it: a link told from its sentence by colour alone is the most common failure of criterion 3.1 on the web, and the one the product audits other sites for. ## When to use, when not Use it for a link inside a sentence or a list. For a link that acts as a call to action, use Button with `href`; for the site's navigation, the header's own links. ## Anatomy 1. An `a`, bold, in the action colour, underlined four pixels under the baseline. ## States Plain, and inside a sentence. ## Accessibility contract - Role and name: a link named by its text. - Keyboard: Tab and Enter, native. - Focus: the ring of the base layer. - Hover: the line thickens; the colour shift is secondary. ## Content rules The text says the destination ("la méthode", "le mode d'emploi"), never "ici" or "cliquez". A link that opens a document says its format. ## Do and don't - Do let the underline show; never remove it to make the sentence "cleaner". - Do not put two links with the same text and different destinations on one page. ## Examples ```svelte Le relevé est public, écarts compris. ``` ## Surfaces - Site, app: every link in prose, as the pages move from the raw class. - Extension: the panel's links to the criterion sheets. ## Related Button. ## Decisions - 2026-09-24: created from the base layer's `link-underline`, which stays for the fifty files that still write it by hand and leaves with the last one. - 2026-09-27: `link-underline` leaves the base layer; the site and the app write this component everywhere. # LiveRegion --- name: LiveRegion slug: live-region level: atom status: stable surfaces: [] since: 2026-09 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" 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." --- ## Purpose A region that announces a message that appeared after an action. What decides whether a screen reader says a status message is the document a second earlier: a region created with its text is not restituted by every reader. This component is the region, present and empty from the first render, whose text the page changes. ## When to use, when not Use it for the outcome of an action that does not move the focus: saved, failed, three results found, copied. Do not use it for a message that takes the focus (an error summary is not a status message), for a message present at load (nothing to announce), or as a second announcement of something a control already says, such as a checked radio. ## Anatomy 1. A `div` with `role="status"` or `role="alert"` and the matching `aria-live`. 2. The message, or nothing. ## States Polite, assertive, and hidden. ## Accessibility contract - Role and name: `status` with `aria-live="polite"` for the ordinary case, `alert` with `assertive` for an error that blocks. No name: a live region announces its content. - Keyboard, focus: none. A live region never moves the focus, that is what makes it one. - Live regions: this is one. It exists from the first render; the page never mounts it at the moment of the announcement. A text identical to the previous one is not a change and announces nothing: a repeated outcome must vary, by a count or a time. ## Content rules French. A message is one sentence that says what happened: "Audit enregistré.", "Trois pages correspondent.", "Échec de l'enregistrement : la connexion a été perdue." An error says what to do next when there is something to do. ## Do and don't - Do mount the region with the page and change `message`; never `{#if message}` around it. - Do use `assertive` for an error that blocks the user, and for nothing else; an assertive success harasses. - Do not announce what a control already says: a checked radio, a pressed toggle. ## Examples ```svelte ``` ## Surfaces - App: the dashboard's filter count, the audited page's saved comment, the report's export; the form outcomes are Alerts that say they are live. - Extension: one region per screen, mounted at boot, for the announcements the panel makes today by redrawing an `aria-live` main. ## Related Alert, Field. ## Decisions - 2026-09-24: a region, not a function. An `announce()` helper that mounts a region on demand is the anti-pattern this component exists to prevent; the page owns a region and writes to it. - 2026-09-24: hidden by default. The page usually shows the outcome its own way; a visible region is for the cases where the region is the text, as in the stories. # Logo --- name: Logo slug: logo level: atom status: stable surfaces: [site, app, extension] since: 2026-08 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: [] --- ## Purpose The mark of RGAA Lab, "la grille", drawn inline so it follows the ink of the theme. A SVG loaded as an image is an isolated document that never inherits `currentColor`: the previous logo stayed black on the dark footer, was whitened with a filter, and vanished the day the footer went light. ## When to use, when not Use it once in a header or a footer, and as the mark alone only where the word cannot fit (the collapsed rail of the app). Do not use it as decoration inside content. ## Anatomy 1. The mark: a square divided in four, one quarter filled, in `currentColor`, `aria-hidden`. 2. The word "RGAA Lab", which is the accessible name; visually hidden when `markOnly` is set. ## States Three sizes and the mark-only form. ## Accessibility contract - Role and name: an inline group whose text is "RGAA Lab". The drawing is hidden from assistive technology. - Keyboard, focus: none of its own; wrap it in a link when it navigates. - Contrast: inherits the ink, which is measured on every background. ## Content rules The word is always "RGAA Lab", with "Lab" in the soft ink. Never translated, never abbreviated. ## Do and don't - Do keep the word visible wherever there is room: it is the name. - Do not load the mark as an image. ## Examples ```svelte ``` ## Surfaces - Site: header and footer. - App: the lab sidebar, the auth pages. - Extension: the store icons are generated from the same geometry by `scripts/icones.mjs`. ## Related None. ## Decisions - 2026-08: inline SVG rather than an image, for the theme. - 2026-09-24: props `height` and `markOnly`; the word's size derives from the height through one custom property, the only dynamic style the component writes. # ProgressBar --- name: ProgressBar slug: progress-bar level: atom status: stable surfaces: [extension] since: 2026-09 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" 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." related: [busy-region, live-region] --- ## Purpose A progression that can be measured, on the native element: the browser says "barre de progression, 40 %" without an ARIA attribute, and a label names it. Without a value the bar is indeterminate, the honest state for a wait whose length is unknown. ## When to use, when not Use it for a task with a measurable advance: an export, an analysis over pages, criteria decided out of 106. For a wait with no measure inside a zone, BusyRegion; for a button waiting on the server, Button with `busy`. ## Anatomy 1. The label. 2. The bar, hollow, filled with the action ink. 3. The percentage, written. ## States Measured and running, indeterminate, done, and the strip across a shell's edge. ## Accessibility contract - Role and name: `progressbar`, named by the label, the value the element's own. - Keyboard, focus: none. - Live regions: none here. The bar carries the value continuously; announcing every percent would be chatter. Milestones are the page's through LiveRegion: started, half, done, and done matters most, since without it the user who does not see the screen waits for a result already there. ## Content rules French. The label is what progresses, as a noun phrase: "Export du rapport". ## Do and don't - Do announce the end through a live region. - Do not draw the bar with a div and `aria-valuenow`; the element exists. - Do not hide the percentage unless the label already says it. - Do use `strip` only where the screen says the progress in words next to it, as the panel's sentence "38 % des critères évalués" does; the strip alone would leave the figure to colour. ## Examples ```svelte ``` ## Surfaces - Extension: the strip of the audit's progress over the panel's header; the page analysis, when the panel is on Svelte. - App: the bulk export; not ported yet. ## Related BusyRegion, LiveRegion. ## Decisions - 2026-09-24: the native element, styled through its pseudo-elements, since a remade bar would need three ARIA attributes for what the browser already says. - 2026-09-27: `strip` draws the same element as the thin line across the extension panel's top, which was a `div` with `role="progressbar"` and three ARIA attributes; the label is hidden rather than dropped, so the bar keeps its name. # Prose --- name: Prose slug: prose level: atom status: stable surfaces: [site, app] since: 2026-08 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] --- ## Purpose A paragraph of running text, in the soft ink, with its line length bounded. Beyond about 58 characters the eye loses the next line coming back to the margin; the component sets that bound so that no page has to remember it. ## When to use, when not Use it for the sentences under a heading, an introduction, a caption of more than one line. Do not use it for interface labels, which belong to their control, nor for lists or tables. ## Anatomy 1. A paragraph, soft ink, with a maximum width in characters. ## States Three sizes and a measure; no interaction. ## Accessibility contract - Role and name: a paragraph. - Keyboard, focus, live regions: none. - The soft ink measures 7.32:1 on the page and 7.64:1 on a surface (tokens/contrast.json). ## Content rules French, full sentences, one idea per sentence. No "il est important de". ## Do and don't - Do let it wrap; a measure is a maximum, never a fixed width. - Do not put a heading's job in it: a bold first sentence is not a heading. ## Examples ```svelte RGAA Lab est une extension Chrome et Firefox et une application web qui guident l'audit des 106 critères du RGAA 4.1.2. ``` ## Surfaces - Site: 38 files. - App: the error page. ## Related Heading, Eyebrow, InlineMarkup. ## Decisions - 2026-08: 58 characters by default, the site's article measure being 66 and the page rule 70; a paragraph under a heading reads best shorter than a column of prose. - 2026-09-24: `rl-paragraph` rules on the tokens; the class is not `rl-prose`, which the base layer reserves for article typography. The measure stays the one dynamic style. # ScrollRegion --- name: ScrollRegion slug: scroll-region level: atom status: stable surfaces: [site, app] since: 2026-08 replaces: - "apps/web: nine hand-written scroll wrappers on the dashboard, the site page and the report" 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." patterns: [data-table-page] related: [data-table] --- ## Purpose A zone that scrolls and can be reached with the keyboard. A `div` in `overflow: auto` never takes the focus: a table wider than its column scrolls with the mouse, the wheel, a finger, and not with the keyboard, so its off-screen columns are out of reach. The defect was found on the report, five zones, and again on the site and the app. ## When to use, when not Wrap anything that may overflow sideways: a wide table, a code block that must not wrap, a matrix. Do not wrap content that never overflows; a focusable region with nothing to scroll is a stop for nothing. ## Anatomy 1. A `div` with `tabindex="0"`, `role="region"` and a name. 2. Optionally a hairline frame. ## States Named by a label, named by an existing heading, framed or not. ## Accessibility contract - Role and name: a region named "X (zone défilante)", or named by the heading given in `labelledBy`. One of the two is required; without a name the stop is silent. - Keyboard: Tab reaches it, the arrow keys scroll it. - Focus: the ring of the base layer. - Live regions: none. ## Content rules The label says what one enters ("Tableau des critères"), not the fact that it scrolls: the component adds that word. ## Do and don't - Do give the label the table's caption or the section's heading. - Do not put interactive content only reachable by scrolling without a name on the region. ## Examples ```svelte ...
``` ## Surfaces - App: the dashboard, the project page, the admin lists, the comparison, the report. - Site: the coverage tables, the pricing grid. ## Related DataTable. ## Decisions - 2026-08: the Svelte warning on a non-interactive tabindex is silenced here, once, rather than in nine templates. - 2026-09-24: renamed from ZoneDefilante, props `label`, `labelledBy`, `framed`; the frame is a modifier class on the tokens. # Select --- name: Select slug: select level: atom status: stable surfaces: [] since: 2026-09 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" 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." patterns: [form-with-error-summary] related: [field, radio-group] --- ## Purpose A choice among options, as the native `select` and nothing else. The browser gives the focus, the arrows, typing the first letters, the system's picker on a phone; a remade select loses all of it. The component ties the label, the hint and the error, and keeps an empty option so nothing chosen stays distinct from a choice. ## When to use, when not Use it for a choice among many options, or among a few that need no comparing. For two to five options the user should see at once, RadioGroup. For free text, Field. ## Anatomy 1. The label, with the required mark when needed. 2. The hint. 3. The select, with the empty option first, the options or the groups. 4. The error. ## States Plain, with a choice made, grouped, required, in error, and unavailable. ## Accessibility contract - Role and name: a listbox in a combobox, as the browser exposes a select, named by its label; "(obligatoire)" in the name when required. - Keyboard, focus: the browser's. No navigation on change: the arrows walk the options one by one, and a navigation fired there would leave on the first one met. - Live regions: the error is `role="alert"`. ## Content rules French. The label is a noun ("Niveau visé"); the empty option says what to do ("Choisissez", "Choisissez un critère"); option texts are short and start with what distinguishes them. ## Do and don't - Do keep the empty option unless a value is always chosen, as with a single supported version. - Do not navigate or submit on change. - Do not remake the control with a list of divs. ## Examples ```svelte ``` ## Surfaces - App: the audit's method and version, the new account's role. - Extension: the page switcher's list, when the panel is on Svelte. ## Related Field, RadioGroup. ## Decisions - 2026-09-24: `placeholder` defaults to "Choisissez" and an empty string removes it, so the ordinary case asks and the exception is written. # SkipLink --- name: SkipLink slug: skip-link level: atom status: stable surfaces: [design, site, app] since: 2026-09 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" 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." patterns: [app-shell] related: [link] --- ## Purpose The first stop of the page, hidden until focused: one link that lets a keyboard user jump over the header and the navigation to the main landmark. Hidden off-screen rather than removed, since `display: none` would take it out of the tab order, which is precisely what it exists to repair. ## When to use, when not Once per page, as the first element of the body, before the header. Not for jumping inside the content; that is a table of contents. ## Anatomy 1. An `a` with the target's id as its `href`, in the action colour on its own fill, positioned above the page until focused. ## States Hidden, and focused; with the default text, and with another. ## Accessibility contract - Role and name: a link, named by its text. - Keyboard, focus: the first Tab of the page reaches it; Enter moves the focus to the target. The target must carry `tabindex="-1"`: without it the page scrolls and nothing takes the focus, and the next Tab starts from wherever the browser decides. The site's sheet on the subject measures the difference. - Live regions: none. - Print: the link leaves the paper; a printed page has no focus to move. ## Content rules French. "Aller au contenu" for the main landmark; for another target, "Aller à" followed by what it is ("Aller aux résultats"). ## Do and don't - Do give the target `tabindex="-1"`, and only that: a `-1` is never a Tab stop. - Do put the link first in the body, before the header, so it is the first stop. - Do not hide it with `display: none` or `visibility: hidden` when idle. ## Examples ```svelte
…
…
``` ## Surfaces - Design site, public site and the lab layout: the one link at the top of each shell. ## Related Link. ## Decisions - 2026-09-24: written as a component and the base-layer rule retired with its last raw use, so the three shells stop copying a class and a text. # StatusChip --- name: StatusChip slug: status-chip level: atom status: stable surfaces: [site, app] since: 2026-08 replaces: - "apps/web: status badges written by hand in the report and the audit pages" 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)." patterns: [] related: [conformity-rate, icon] --- ## Purpose The status of a criterion: conforme, non conforme, non applicable, non testé. It exists to guarantee that the word is always there. Criterion 3.1 forbids information by colour alone, and that is the rule one forgets first when writing a badge by hand in a corner of a page. ## When to use, when not Use it wherever a criterion's status is shown: a grid, a report, a column header. Do not use it for anything that is not one of the four audit statuses; a derogation, a "to recheck" mark or a level badge are other components. ## Anatomy 1. The status drawing from the icon set, `aria-hidden`. 2. The abbreviation, in the mono face. 3. The word, visible or visually hidden when `short` is set. 4. A line around, solid; dashed for "non testé", which is not a verdict. ## States The four statuses, and the short form. ## Accessibility contract - Role and name: an inline span; the name is "C Conforme" and so on, the drawing excluded. With `short`, the word stays in the name. - Keyboard, focus, live regions: none. - The chip never wraps: a status reads in one piece or not at all. - Labels come from `@rgaa-dev/core`, the same table as the extension and the reports; drawings from `statusIcon`. ## Content rules The four French words of the standard, never a synonym. The abbreviation is the standard's too. ## Do and don't - Do use `short` in a column header, where the word is in the header's context anyway. - Do not give "non testé" a colour of its own: it is an absence of verdict. ## Examples ```svelte ``` ## Surfaces - App: the dashboard headers, the audit page, the page view, the comparison, the report. - Site: the method page and the gallery. ## Related ConformityRate, Icon. ## Decisions - 2026-08: the symbol for non conforme is ✕ (U+2715), from core; the extension had drifted to ✗. - 2026-09-24: props `status` and `short`; the symbol character gives way to the icon drawing, the same shape a control shows for that status, while documents and text keep the character from core. # Switch --- name: Switch slug: switch level: atom status: stable surfaces: [] since: 2026-09 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" 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." related: [checkbox] --- ## Purpose A setting that applies at once, as a button with `role="switch"` and `aria-checked` written from the state. Its name is its label, inside it. No submit button: the caller acts on `onchange`. ## When to use, when not Use it for a preference that takes effect the moment it is flipped: show the criterion numbers, sync automatically. Not for a choice submitted with a form (Checkbox), nor for anything that needs a confirmation. ## Anatomy 1. The button, holding the label and the drawing. 2. The label, first, as the setting's name. 3. The track and the thumb, hidden from assistive technology. 4. The hint, under, tied by `aria-describedby`. ## States Off, on, with a hint, and unavailable. ## Accessibility contract - Role and name: `switch`, named by its label text; `aria-checked` says on or off. - Keyboard, focus: Tab reaches it, Enter and Space flip it; the ring is the system's. - Live regions: none; the switch announces its own state. The page says the consequence its own way when it matters. - Forced colours: the track and the thumb take ButtonText, Highlight and HighlightText. ## Content rules French. The label is the setting as a noun phrase ("Numéros de critère"), never a verb that would change with the state. ## Do and don't - Do act in `onchange`; a switch that waits for a button is a checkbox. - Do not write "Activer" on a switch: the state is already said. - Do not announce the flip in a live region; the control already does. ## Examples ```svelte ``` ## Surfaces - Extension: the panel's display settings, when it is on Svelte. - App: the notification preferences; not ported yet. ## Related Checkbox. ## Decisions - 2026-09-24: first written as a native checkbox with the switch role, the other implementation the site's demo shows. The product's own engine refused it on the design site: ACT rule 4e8ab6 requires `aria-checked` on any element with the switch role, and ARIA in HTML forbids writing it on a native checkbox. The button carries the attribute without conflict, and the state cannot go stale since it is written at every render. # Text --- name: Text slug: text level: atom status: stable surfaces: [app, extension, site] since: 2026-09 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" 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." patterns: [] related: [prose, heading, eyebrow] --- ## Purpose A run of text with a size and a tone: a caption, a count, a help line, a sentence that says a state. One element, a size from the scale, an ink from the tokens. ## When to use, when not Use it for the short text a screen is full of. Not for a paragraph of running text, which is Prose and takes a measure; not for a heading; not for a label above small capitals, which is Eyebrow. ## Anatomy 1. One element, `p`, `span` or `div`, with the size, the tone, the weight and the face as modifiers. ## States Plain, soft and small, the three state tones, and inline with a bold word and a mono value. ## Accessibility contract - Role and name: none; text. - Keyboard, focus: none. - Live regions: none. A sentence that must be announced goes through LiveRegion, which can hold a Text. - A tone never carries the meaning: "Erreur, la capture a échoué." says it before the ink does. ## Content rules French. A sentence ends with a period; a caption or a count does not. A state tone starts with the word of the state when the sentence is short ("Attention, ..."). ## Do and don't - Do use `soft` for what supports the main text, never for the main text. - Do use `mono` for a value the reader may copy: a selector, an address. - Do not use a state tone to decorate; the ink says a state, and only when the words say it too. ## Examples ```svelte Visible dans le rapport.

Le sélecteur footer .legal-links désigne trois éléments.

``` ## Surfaces - App: help lines, captions, counts. - Extension: the panel's hints and captions. - Site: captions under the demos. ## Related Prose, Heading, Eyebrow. ## Decisions - 2026-09-26: no measure prop: a run of text that needs one is a paragraph of running text, which is Prose. # Tooltip --- name: Tooltip slug: tooltip level: atom status: stable surfaces: [] since: 2026-09 replaces: - "apps/site: the infobulle demo, kept as teaching material" 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." related: [kbd, inline-markup] --- ## Purpose A description for a term that already has its name. The trigger keeps its text as its name and the bubble is its description, tied by `aria-describedby`, read at focus whether shown or not; the `title` attribute gives no such guarantee. ## When to use, when not Use it for a definition or a precision on a term in running text: a rate, a status, an abbreviation. Not for the name of an icon-only control (that is IconButton's `label`), not for anything the reader must not miss (write it in the text), never with something interactive inside. ## Anatomy 1. The group, which holds the trigger and the bubble so the pointer never leaves. 2. The trigger, a button with the term as its text, underlined with dots. 3. The bubble, `role="tooltip"`, hidden at rest. ## States In a sentence, and a short one. ## Accessibility contract - Role and name: the trigger is a button named by its text; the bubble is its description. `role="tooltip"` documents the intent. - Keyboard, focus: the bubble shows on focus and hides when the focus leaves; Escape hides it and moves nothing. A click toggles it. - Pointer: the bubble shows on hover of the trigger and stays while the pointer is on the trigger or in the bubble. - Live regions: none; a bubble that spoke at every mouse move would make the page unlistenable. ## Content rules French. One or two sentences that define or precise; no link, no button inside. ## Do and don't - Do put the essential in the text and the precision in the bubble. - Do not use it to name a control. - Do not open it on a delay or close it on a timer. ## Examples ```svelte

Son taux de conformité se lit en pourcentage.

``` ## Surfaces - App: the report's terms; not ported yet. - Site: the sheets' definitions, once the site is ported. ## Related Kbd, InlineMarkup. ## Decisions - 2026-09-24: the refusal by Escape is forgotten once the pointer and the focus have both left, as the site's demo does; without it a bubble closed once never came back. # Accordion --- name: Accordion slug: accordion level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/site: the accordéon demo, kept as teaching material" 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." patterns: [] related: [disclosure, tabs] --- ## Purpose A list of disclosures under their headings, each independent by default: opening one closes no other, because closing what the reader had just opened surprises them. The items are data and the panel is a snippet, so the caller writes the content once and the component holds which are open. ## When to use, when not Use it for a list of questions and answers, a list of criteria with their explanation, any set of details the reader may skip. Do not use it for views of equal weight the user switches between (Tabs), nor for one panel alone (Disclosure). ## Anatomy 1. One Disclosure per item: a heading with the button, and the panel. ## States Independent, exclusive, and with a panel open at first. ## Accessibility contract - Role and name: Disclosure's, per panel. - Keyboard, focus: Tab moves from button to button and into an open panel's content; Enter and Space toggle. No arrow-key navigation: the panels are headings and buttons, and both already have their keys. - Live regions: none. ## Content rules French. Titles are questions or noun phrases, each different from the others so the list of headings reads on its own. ## Do and don't - Do give `level` the level the titles hold in the page. - Do not set `exclusive` unless the panels really are alternatives; it costs a screen reader user the panel they opened to compare. ## Examples ```svelte {#snippet panel(item)}

{answers[item.id]}

{/snippet}
``` ## Surfaces - App: the questions of the help page and the criteria of the report; not ported yet. - Site: the questions of the pricing page, once the site is ported. ## Related Disclosure, Tabs. ## Decisions - 2026-09-24: independent by default, exclusive on request, as the site's accordéon demo argues. # Alert --- name: Alert slug: alert level: molecule status: stable surfaces: [site, app] since: 2026-08 replaces: - "apps/web: three hand-built inline notices on the audit page, the report and the page view" 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." patterns: [form-with-error-summary] related: [callout, icon] --- ## Purpose A message of information, success, warning or failure. It exists for three reasons, two of which are accessibility traps a hand-written box almost always misses: the word before the colour, the right ARIA role, and a decided `live`. ## When to use, when not Use it for a message the page shows about an action or a state: a form error at the top of the form, a confirmation after a save, a warning before a destructive step. Do not use it for a transient toast, for content that is merely framed (that is Callout), or for a field's own error (Field carries it). ## Anatomy 1. The tone's drawing from the icon set, `aria-hidden`. 2. The tone's word, visible, or visually hidden before a free title. 3. The message. 4. A line around, in the tone's ink, never a fill. ## States Four tones, with or without a title, live or not. ## Accessibility contract - Role and name: no role when the message is present at load (it is read in the flow). With `live`, `alert` for an error and `status` for everything else. The tone's word is always in the accessible name. - Keyboard, focus: none. A live message never moves the focus. - Live regions: a message that appears after an action is announced; a message present at load is not. That is what `live` decides, and it must be decided. ## Content rules French. The message says what happened and, for an error, what to do. The tone's word is the standard's (Erreur, Attention, Fait, Information); a title, when there is one, is specific. ## Do and don't - Do set `live` on a message that appears in response to an action. - Do not set `live` on a message rendered with the page: it would be announced twice. - Do not use the warning tone for a decoration; the tone is the meaning. ## Examples ```svelte Le mot de passe ne correspond pas. Les 106 critères sont à jour. ``` ## Surfaces - App: the auth pages, the settings, the admin lists, the audit page. - Site: the beta page and the gallery. ## Related Callout, Icon. ## Decisions - 2026-08: `alert` only for the error tone; a success announced as an alert harasses. - 2026-09-24: props `tone` (`info`, `success`, `warning`, `error`) and `title`; the drawing comes from the icon set, and the warning tone takes the attention ink the tokens define for it, where it used to borrow the plain ink. # AnswerButtons --- name: AnswerButtons slug: answer-buttons level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: the five families of answer buttons (verdict, answer, final answer, inline answer, question 31) the wizards wrote" 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." related: [question, kbd, wizard-footer] --- ## Purpose The buttons a wizard's question is answered with: Oui, Non, and sometimes Non applicable, one group named by the question, the retained one said by its state and drawn by its fill. ## When to use, when not Use it for every answer a wizard takes with a click. Not for a verdict the tool concluded itself (AutoVerdict), not for a form's radio group, which is RadioGroup. ## Anatomy 1. A group, named by the question. 2. One button per answer, with its label and its shortcut key. ## States Nothing retained, Oui retained, and three answers with Non applicable. ## Accessibility contract - Role and name: `role="group"` named by the question through `aria-labelledby`; each answer is a `button` with `aria-pressed`. - Keyboard, focus: Tab reaches each button, Enter or Space presses it; the letter shortcuts are the page's listener, not the buttons'. - Live regions: none; the wizard says what the answer did, if anything. ## Content rules French. Oui, Non, Non applicable; never Valider or OK. The key is the capital letter printed on the key cap. ## Do and don't - Do name the group by the question's id. - Do not add a check character to the retained label: the state is the attribute, and a reader spells characters out. ## Examples ```svelte (answer = v)} answers={[{ value: 'C', label: 'Oui', key: 'O' }, { value: 'NC', label: 'Non', key: 'N' }]} /> ``` ## Surfaces - Extension: every wizard question. ## Related Question, Kbd, WizardFooter. ## Decisions - 2026-09-26: one component for the five families of answer buttons the wizards had grown, so the keyboard shortcuts and the retained state have one shape. # AutoVerdict --- name: AutoVerdict slug: auto-verdict level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: insererOrigineDuVerdict and the auto-verdict blocks each wizard wrote" 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." related: [question, status-chip, answer-buttons] --- ## Purpose A verdict the tool concluded itself: the status, who said it, what it saw, and the reminder that the auditor's answer wins. ## When to use, when not Use it above the answer buttons when the engine or the page's markup already settled a criterion, or in place of the buttons when the criterion is the sum of answers the auditor gave elsewhere, element by element on a walk. Not for the auditor's answer to the question on screen, which the buttons show. ## Anatomy 1. The verdict line: the drawing, the status in bold, the source. 2. The findings, one per line, with a "Montrer" button when the element can be shown. 3. The closing sentence, or what the wizard says instead. ## States Conforming from the page, non-conforming from the engine with findings, not applicable with a sentence of the wizard's, and non-conforming from the auditor's own answers with the sentence that says where to change them. ## Accessibility contract - Role and name: a block of text; "Montrer" buttons named by their word, with the selector as data. - Keyboard, focus: the buttons only. - Live regions: none; the block is part of the screen. ## Content rules French. The status word from `@rgaa-dev/core`; the source is "d'après le moteur de détection", "d'après la page" or "d'après vos réponses"; a finding is a reason in words, the selector never printed. With the auditor as source, the closing sentence must say where the answers are changed, since the reminder that the answer wins would point at buttons that are not there. ## Do and don't - Do keep the selector in the button: a ten-level path printed drowned the reason. - Do not present the verdict as final; the closing sentence says the auditor's answer wins. ## Examples ```svelte ``` ## Surfaces - Extension: the wizards, when a criterion was settled before the auditor. ## Related Question, StatusChip, AnswerButtons. ## Decisions - 2026-09-26: the findings are the engine's own text, split into reason and selector by the wizard; the component prints the reason and keeps the selector for the button. - 2026-09-26: a third source, the auditor. The script wizard concludes 7.1 and 7.3 from the checks answered component by component, and its questions screen shows them read-only; "d'après la page" was false there, and a local block would have been a second implementation of this one. # Banner --- name: Banner slug: banner level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: lib/components/BandeauBeta.svelte, gone with the lab layout's port" 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." patterns: [app-shell] related: [alert, icon-button] --- ## Purpose An announcement at the top of a shell, dismissable and remembered: the beta, a maintenance, a change of terms. A named aside with a word, a sentence, a link when there is more, and a close button whose choice is kept under a key. ## When to use, when not Use it for an information that concerns every page and can wait: a date, a maintenance. Not for the outcome of an action (LiveRegion or Toast), not for an error (Alert), not for anything the user must answer (Dialog). ## Anatomy 1. The aside, with the line on the left. 2. The word, which names it. 3. The text, with its link. 4. The close button. ## States The beta, and a maintenance. ## Accessibility contract - Role and name: `complementary`, named by the word through `aria-labelledby`. No `role="alert"`, no `aria-live`. - Keyboard, focus: the close button is a Tab stop; closing moves the focus nowhere in particular, the banner was not focused. - Persistence: the dismissal is stored under `dismissKey`; a new key brings the banner back once. ## Content rules French. The word is one noun ("Bêta"). The text says the fact and the date; no countdown, no red, no "plus que N jours". ## Do and don't - Do put the date in the key when the banner carries one, so an extension of the date shows the banner again. - Do not show more than one banner at a time. - Do not animate it in. ## Examples ```svelte

L'application est gratuite jusqu'au {date}. {promise} Voir les tarifs

``` ## Surfaces - App: the beta banner of the lab, in the AppShell's banner slot. ## Related Alert, IconButton. ## Decisions - 2026-09-24: closed at the first render and opened once storage is read, as the app's banner does, so whoever closed it never sees it flash. # Breadcrumb --- name: Breadcrumb slug: breadcrumb level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: lib/components/Breadcrumb.svelte, gone" - "apps/site: the fil-d-ariane demo, kept as teaching material" 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." patterns: [app-shell] related: [link] --- ## Purpose The path to the current page, as a named navigation landmark holding a list, the separators hidden and the last step marked current. It looks like a row of links with slashes; what tells them apart is what assistive technology receives. ## When to use, when not Use it on any page more than one level deep in the app. Not on the first level, where it would hold one step, and not as a substitute for a page title. ## Anatomy 1. A `nav` named "Fil d'Ariane". 2. An ordered list, one item per step. 3. Links for every step but the last; the last step with `aria-current="page"`. 4. Hidden separators. ## States A short path, and a deep one that wraps. ## Accessibility contract - Role and name: `navigation`, named by `label`; a list inside, with `role="list"` restored. - Keyboard, focus: the links are ordinary links. - Live regions: none. - Print: the trail leaves the paper; it is a way through the screen. ## Content rules French. Each step is the title of the page it leads to, as short as the page allows; the last step is the current page's title. ## Do and don't - Do give the last item no `href`: it is where the user is. - Do not repeat the breadcrumb in the footer. ## Examples ```svelte ``` ## Surfaces - App: every page under an organisation, a site or an audit, the settings, and the administration, which had none. ## Related Link. ## Decisions - 2026-09-24: the separator is a hidden slash, drawn the same as the hand-made version the site's demo compares it with, so the difference is only what is restituted. # Callout --- name: Callout slug: callout level: molecule status: stable surfaces: [site] since: 2026-08 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] --- ## Purpose A block framed by a structural line. It was rewritten by hand about fifteen times during the redesign, each page copying the same border style, and a copied shape diverges. The status changes the colour of the line, never a fill. ## When to use, when not Use it to set a passage apart: a definition, an aside, a quoted rule, a block that borrows a status colour with its word inside. Do not use it for a message about an action (that is Alert) nor as a card in a grid. ## Anatomy 1. A `div`, or an `aside` when the content is beside the point. 2. A line around: strong or subtle, neutral or in a status colour. ## States Strong or subtle line, neutral or in the NC or C colour, as an aside or not. ## Accessibility contract - Role and name: none for a `div`; a complementary landmark for an `aside`, so use `aside` only for content that is truly aside. - Keyboard, focus, live regions: none. ## Content rules A block in a status colour says the status in words inside it. ## Do and don't - Do use the subtle line for an aside and the strong one for an argument. - Do not fill it: the direction refuses colour slabs. ## Examples ```svelte Une définition qui mérite un cadre. Un aparté, vraiment à côté du propos. Non conforme : le lien n'a pas d'intitulé. ``` ## Surfaces - Site: the criteria sheets, the pricing page. ## Related Alert. ## Decisions - 2026-08: the line, never a fill. - 2026-09-24: renamed from Encadre, props `rule`, `status`, `aside`; the line is modifier classes, so a prop that changes after mount redraws, which the style computed once did not. # Card --- name: Card slug: card level: molecule status: stable surfaces: [site] since: 2026-08 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] --- ## Purpose A card laid on the surface, separated from its neighbours by the hairline of the grid. With `href`, the whole card becomes the target. ## When to use, when not Use it inside a CardGrid for a set of comparable items: articles, services, capabilities. Do not use it alone as a framed box (that is Callout) or as a form wrapper. ## Anatomy 1. The item: `li` in a list grid, `article` otherwise. The grid decides. 2. The content, padded; a link covering the whole card when `href` is set. ## States Plain, as a link, inside a list or not. ## Accessibility contract - Role and name: a list item or an article; with `href`, a link whose name is the card's text. - Keyboard: the link is one tab stop for the whole card. - Focus: the ring of the base layer around the link. - Live regions: none. ## Content rules A card that is a link opens with its title, so the link's name starts with what it leads to. ## Do and don't - Do let the grid decide `listItem`; set it by hand only outside a grid. - Do not put a second link or a button inside a card that is a link. ## Examples ```svelte Les outils qui testent La méthode ``` ## Surfaces - Site: 12 files (articles, services, capabilities). - App: every form's card, the empty states, the site and page cards that link, the sessions and tokens as list items; the report keeps the base layer's class until it is a document. ## Related CardGrid. ## Decisions - 2026-08: the grid passes a flag during the construction of its cards rather than a Svelte context, which threw at hydration and blanked the pages (see `context.ts`). - 2026-09-24: prop `listItem`; the surface and the padding are `rl-card` rules on the tokens. - 2026-09-24: the card draws its own hairline as an outline, so a grid needs no background and an incomplete row shows no painted cell. # CardGrid --- name: CardGrid slug: card-grid level: molecule status: stable surfaces: [site] since: 2026-08 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] --- ## Purpose A grid of cards separated by a one-pixel hairline. Each card carries its line as an outline and the outlines meet in the one-pixel gap: the separation is a real stroke, not a border doubled between two neighbours, and an incomplete row shows no painted cell. Structure by the line, not by the shadow. ## When to use, when not Use it for two to four comparable cards side by side. Make it a list as soon as the cards are an enumeration. Do not use it for a single card. ## Anatomy 1. A `ul` with `role="list"` (list mode) or a `div`. 2. A grid with a one-pixel gap where the cards' outlines meet; two, three or four columns from the small breakpoint, four folding to two below the large one. ## States Two to four columns, list or not. ## Accessibility contract - Role and name: a list when `list` is set, announced with its number of items; a plain group otherwise. `role="list"` is explicit because a reset that removes bullets makes some screen readers drop the list semantics. - Keyboard, focus, live regions: none of its own. ## Content rules None. ## Do and don't - Do choose the list form for enumerations: three capabilities, five articles. - Do not put anything but cards inside. ## Examples ```svelte ... ... ... ``` ## Surfaces - Site: 12 files. - App: the organisation's sites, as a list grid. ## Related Card. ## Decisions - 2026-08: the grid announces to its cards whether they are list items, so the two choices cannot disagree. - 2026-09-24: props `columns` and `list`; the grid and its breakpoints are `rl-card-grid` rules. - 2026-09-24: the hairline moved from a background behind the grid to an outline on each card, because the background painted the empty cells of a five-card row in three columns on the public site. # CommentField --- name: CommentField slug: comment-field level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: the comment textarea each wizard wrote with inline styles and the shared id a-11y-wizard-comment" 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." related: [field, answer-buttons, wizard-footer] --- ## Purpose The auditor's note on the element judged: a small textarea with its label, in every wizard. ## When to use, when not Use it for the note that travels with an answer. Not for a form of the app, which is Field with `multiline`. ## Anatomy 1. The label, with "(facultatif)" when it is. 2. The hint line, optional. 3. The textarea, which grows with the text. ## States Empty, filled, and with a hint about the keys. ## Accessibility contract - Role and name: a `textarea` named by its `label`, described by the hint when there is one. - Keyboard, focus: an ordinary text field. The wizard's shortcut listener leaves letters alone inside it and takes Enter to advance, Shift and Enter to break the line; the hint says so. - Live regions: none. ## Content rules French. The label names the element judged ("Commentaire sur ce cadre"); the placeholder is "Ce que vous avez constaté…". ## Do and don't - Do give each field an id of its own: the old shared id let a shortcut read the wrong field. - Do not make the note required: a conforming element needs no note. ## Examples ```svelte ``` ## Surfaces - Extension: every wizard. ## Related Field, AnswerButtons, WizardFooter. ## Decisions - 2026-09-26: optional by default and said so in the label, since a conforming element needs no note and a required field would slow every answer. # ConfirmDialog --- name: ConfirmDialog slug: confirm-dialog level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: the seven window.confirm() calls and the inline confirmation panel of the extension page" 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." patterns: [destructive-confirmation] related: [dialog, button] --- ## Purpose A yes-or-no question before an action, composed from Dialog. The app asks eight of them through `window.confirm()`, which no design reaches, which some browsers let the user silence for a whole site, and whose buttons say "OK" whatever the question. Here the question is the title, the consequence is the body, the button says what it does, and the initial focus is on the least risky answer. ## When to use, when not Use it before an action the user cannot undo, or would regret: deleting, publishing, closing an audit. Do not use it for an action that can be undone in one step (offer the undo instead), nor as a generic "are you sure", which trains the user to press Enter. ## Anatomy 1. The question, as the dialog's heading. 2. The consequence, as the body: what the action does and what it does not undo. 3. The cancel button, secondary, first and focused. 4. The confirm button, primary or destructive, with the verb of the action. ## States Plain, and destructive. ## Accessibility contract - Role and name: Dialog's, named by the question. - Keyboard, focus: the initial focus is on the cancel button, so Enter pressed by reflex does nothing. Tab reaches the confirm button, then the close button. Escape cancels. The focus returns to the opener. - Live regions: none. The result of the action, when it must be said, is the page's, through LiveRegion. ## Content rules French. The title is the question with the verb ("Supprimer l'audit ?"). The body says the consequence in one or two sentences and, when true, "Cette action ne peut pas être annulée." The confirm button repeats the verb ("Supprimer l'audit"); the cancel button says "Annuler". ## Do and don't - Do set `destructive` when the action deletes or breaks something; the colour says the same thing as the word. - Do not write "Oui" or "OK" on the confirm button. - Do not confirm what can be undone: an undo costs the user less than a question. ## Examples ```svelte

Les 106 critères et les captures seront supprimés. Cette action ne peut pas être annulée.

``` ## Surfaces - App: the deletion of an audit, a page or a site, the reopening of an audit, the removal of the second factor, the revocation of every token, the deactivation of an account. No `confirm()` is left in the app. - Extension: deleting an audit or a page from the panel, when the panel is on Svelte. ## Related Dialog, Button. ## Decisions - 2026-09-24: cancel first and focused. The GOV.UK and the site's sheet both put the safe answer under the finger; a destructive default on Enter was the fault the extension's export dialog had. - 2026-09-24: `confirmLabel` is required, not defaulted, so that a caller has to write the verb. # ConformityRate --- name: ConformityRate slug: conformity-rate level: molecule status: stable surfaces: [app] since: 2026-08 replaces: - "apps/web: five renderings of the rate across the dashboard, the site page, the audit page and the report" criteria: - code: "3.1" why: "A symbol (● ◐ ○) says the band; the colour only confirms it." patterns: [] related: [status-chip, stat] --- ## Purpose The conformity rate, said one way. It is the central figure of the product and it used to show five ways a few clicks apart. The bands are those of `symboleDuTaux` in `@rgaa-dev/core`, 75 and 50; the middle band stays in the ink because the standard has no "warning" colour and saying otherwise would lie. ## When to use, when not Use it wherever a rate is published: a page, a site, a campaign. When the audit cannot publish a rate, give `null` and the official sentence; never a rounded or provisional figure. ## Anatomy 1. The symbol of the band, `aria-hidden`. 2. The figure, tabular numerals, with its percent sign. 3. Optionally a suffix ("de conformité"). 4. Or, when there is no rate, the reason in the soft ink. ## States The three bands, the unavailable state with and without a reason, the decimal form for a site average. ## Accessibility contract - Role and name: text. The figure is a `b`. - Keyboard, focus, live regions: none. - The unavailable state is a sentence, never a dash. ## Content rules Percent with a non-breaking space before the sign, French decimal comma. The unavailable reason comes word for word from the official grid. ## Do and don't - Do show the sentence when a criterion is still not tested. - Do not compute a provisional rate and call it a conformity rate. ## Examples ```svelte ``` ## Surfaces - App: the dashboard, the site page, the audit page. ## Related StatusChip, Stat. ## Decisions - 2026-08: the thresholds live in core; this component only draws them. - 2026-09-24: renamed from TauxDeConformite; the band is a modifier class, and the figure takes the decimal comma the doc always promised. # CriteriaRecap --- name: CriteriaRecap slug: criteria-recap level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: renderCriteriaRecap and renderCriterionBadge, with their inline styles" 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." related: [status-chip, status-counters, wizard-frame, jump-list] --- ## Purpose The criteria of a theme, each with its status, in one row under a wizard's header, the current one marked and the reachable ones as buttons. ## When to use, when not Use it in a wizard, for the theme it audits. Not for a page's list of criteria, which is a table. ## Anatomy 1. A list named by the theme. 2. One pill per criterion: the drawing, the number, the hidden word; a button when reachable. ## States Plain pills, and pills the wizard can jump to with the current one marked. ## Accessibility contract - Role and name: a list named by `label`; a reachable pill is a `button` named "Aller au critère 8.4, non conforme", the others read "Critère 8.4, conforme"; `aria-current="true"` on the current one. - Keyboard, focus: the buttons in reading order; the plain pills are not stops. - Live regions: none. ## Content rules French; the status words from `@rgaa-dev/core`, in lower case since they read inside the name. ## Do and don't - Do list only the criteria the wizard judges, in the referential's order. - Do not make every pill a button: a control that does nothing teaches the auditor to stop pressing. ## Examples ```svelte ``` ## Surfaces - Extension: every wizard's recap. ## Related StatusChip, StatusCounters, WizardFrame, JumpList. ## Decisions - 2026-09-26: the drawing comes from the icon set through statusIcon, as the chip's does; the old recap had a fourth table of symbols with two glyphs indistinguishable to the eye. # DataTable --- name: DataTable slug: data-table level: molecule status: stable surfaces: [site] since: 2026-08 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] --- ## Purpose A data table. A table written by hand almost always misses one of four things, and all four are criteria of theme 5: the caption, the declared headers, a scroll reachable at the keyboard, aligned figures. The component makes them impossible to miss. ## When to use, when not Use it for a table whose rows are records of the same shape and whose cells are values or small snippets. When cells need row groups, spanning headers or a sticky column, write the table by hand with the base classes inside a scroll region instead. ## Anatomy 1. The scroll region component, framed and named by the caption. 2. A table with a caption, visible or visually hidden. 3. Column headers with `scope="col"`, small capitals; numeric columns right-aligned in tabular numerals. 4. Rows keyed by an identity, cells rendered from the raw value or a snippet. 5. Or, when there are no rows, the empty snippet. ## States Plain, with a custom cell, empty, caption hidden. ## Accessibility contract - Role and name: a table named by its caption, inside a region named "caption (zone défilante)". - Keyboard: Tab reaches the region; a screen reader's table navigation reads headers with each cell. - Focus: the ring of the base layer around the region. - No zebra striping: a second background is a second contrast to check, for a gain the hairline already gives. ## Content rules The caption names the data, not the table ("Critères du thème 6", not "Tableau"). Column headers are nouns. ## Do and don't - Do mark numeric columns, so figures align and compare. - Do not hide the caption unless the heading just above says the same thing. ## Examples ```svelte {#snippet cell({ row, column })} {#if column.key === 'status'}{:else}{row[column.key]}{/if} {/snippet} ``` ## Surfaces - Site: the gallery only. The app's tables use the base classes by hand because of their row groups and sticky columns. ## Related ScrollRegion. ## Decisions - 2026-08: the caption is required, with no default, so a table cannot be added without naming it. - 2026-09-24: English props (`caption`, `columns` with `key`, `label`, `numeric`, `rows`, `rowKey`, `cell`, `empty`); the scroll wrapper is the scroll region component rather than a second copy of its markup. # DefinitionList --- name: DefinitionList slug: definition-list level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: the property blocks of the audit, site and admin pages written as divs, once the app is ported" 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." related: [table, stat-group] --- ## Purpose Pairs of a term and its value, as a definition list: the properties of an audit, a site, an account. Two columns when there is room, stacked otherwise; a value can be a string or anything rendered. ## When to use, when not Use it for the properties of one thing. For rows that share columns, Table; for figures that summarise, StatGroup. ## Anatomy 1. The list. 2. One pair per item: the term in small capitals, the value under it. ## States Plain, with rendered values, and in one column. ## Accessibility contract - Role and name: a `dl`; each pair wrapped in a `div`, which the element allows. - Keyboard, focus: the values' own, when they hold a link. - Live regions: none. ## Content rules French. Terms are nouns ("Site audité", "Créé le"); values are the facts, formatted for reading. ## Do and don't - Do keep one value per term; several values need several pairs or a rendered list. - Do not use it for a form's summary that the user edits in place; that is a form. ## Examples ```svelte ``` ## Surfaces - App: the account sheet and the profile's address; the report's context block waits for the report document. ## Related Table, StatGroup. ## Decisions - 2026-09-24: pairs wrapped in a `div`, the HTML standard's way to group a term and its value for layout. # Dialog --- name: Dialog slug: dialog level: molecule status: stable surfaces: [] since: 2026-09 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" 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." patterns: [destructive-confirmation] related: [confirm-dialog, button, live-region] --- ## Purpose A modal dialog on the native `dialog` element and nothing else. The element gives, correctly and for free, the four behaviours every hand-made modal gets wrong at least once: the focus is held inside, Escape closes, the page behind is inert, and the focus goes back to the button that opened it. The component adds the name, a labelled close button, a bindable `open` and the value of the button that closed it. ## When to use, when not Use it for a question the user must answer before going on, a short form, a list to pick from. Do not use it for a message that needs no answer (Alert, or LiveRegion for one that appears after an action), for content that could sit on the page, or for a confirmation of a destructive action, which ConfirmDialog composes from this one. ## Anatomy 1. The heading, which names the dialog. 2. The body. 3. The actions, optional: buttons of type submit inside a `form method="dialog"`, each with a value. 4. The close button, last in the DOM and drawn top right, labelled "Fermer". 5. The backdrop, the one veil the system allows. ## States Closed, then open as a question or as a form; the outcome is read from the value of the button that closed it. ## Accessibility contract - Role and name: `dialog`, named by the heading through `aria-labelledby`. The heading is an `h2`; the page's own structure stops at the dialog. - Keyboard, focus: `showModal` holds Tab inside. The initial focus goes to the first focusable element of the body, or to an element marked `autofocus`; the close button is last in the DOM for that reason. Escape closes. On close, the browser returns the focus to the opener. - Live regions: none. The outcome, when it must be said, is the page's, through LiveRegion. - Forced colours: the surface and the line take the system's colours; the backdrop takes Canvas. ## Content rules French. The title is a question ("Retirer la page de l'audit ?") or a noun phrase ("Renommer l'audit"). An action button says what it does with a verb ("Retirer la page", "Enregistrer"), never "OK" or "Oui". The cancel button says "Annuler". ## Do and don't - Do bind `open` and set it from the page; never call `showModal` yourself. - Do give every action button a value: it is what `onclose` hands back. - Do not put a second dialog inside a dialog. - Do not open a dialog at page load or after a delay: it takes the focus from wherever the user was. ## Examples ```svelte value === 'confirm' && remove()}>

Ses vérifications seront conservées, mais la page ne comptera plus.

{#snippet actions()}
{/snippet}
``` ## Surfaces - App: the confirmation panel of the audit page and the `confirm()` calls, the page switcher, the export dialog; not ported yet. - Extension: the page switcher and the export and sync dialogs of the panel, when the panel is on Svelte. ## Related ConfirmDialog, Button, LiveRegion. ## Decisions - 2026-09-24: the native element, no focus trap of our own. The site's modale demo proved the four behaviours come from `showModal`; a trap written by hand was the extension's second most patched file. - 2026-09-24: the close button is last in the DOM. A dialog whose first stop is "Fermer" announces its exit before its question. - 2026-09-24: the outcome travels as the closing button's value through `onclose`, not as one handler per button, so a confirmation is one function and reads the same whether the user pressed Escape, the close button or "Annuler". # Disclosure --- name: Disclosure slug: disclosure level: molecule status: stable surfaces: [] since: 2026-09 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" 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." patterns: [] related: [accordion, tabs] --- ## Purpose A heading that opens and closes a panel. The accessible version is a native button that says its state and a panel that is really hidden when folded; the component is that and nothing more, with the button inside the heading so the outline stays intact. ## When to use, when not Use it for a detail the reader may skip: the explanation under a criterion, a derogation's justification, a long list folded by default. Do not use it for content the page needs read (show it), for a set of exclusive panels (Accordion) or for views of equal weight (Tabs). ## Anatomy 1. A heading, at the level the page gives it, holding the button. 2. The button, with the chevron and the title; `aria-expanded` says the state. 3. The panel, `hidden` when folded, named as the button's `aria-controls` target. ## States Closed, open, and at another heading level. ## Accessibility contract - Role and name: a button, named by the title; `aria-expanded` for the state, `aria-controls` for the panel. The heading keeps its level. - Keyboard, focus: Enter and Space toggle. The focus stays on the button. The panel's content is in the tab order only when shown. - Live regions: none; the state change is announced by the button itself. - Motion: the chevron turns under motion only. ## Content rules French. The title is a noun phrase or a question, the same whether the panel is open or closed: never "Afficher" that becomes "Masquer", the state is already said. ## Do and don't - Do give the heading the level its place in the outline requires. - Do not open a panel on hover or on focus. - Do not close the siblings: that is Accordion, and it surprises whoever was reading. ## Examples ```svelte

La page ne contient aucun média temporel.

``` ## Surfaces - App: the criteria of each theme on the audited page, folded under the theme's figures. - Extension: the derogation panel of the wizards and the theme rows of the sidebar, when the panel is on Svelte. ## Related Accordion, Tabs. ## Decisions - 2026-09-24: `hidden` and not a height of zero, as the site's accordéon demo teaches; the demo is the source of this component. - 2026-09-24: the level is a prop, since a heading at the wrong level is the fault the product finds most on other sites. # EmptyState --- name: EmptyState slug: empty-state level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: the soft paragraphs under empty lists of the lab, the projects and the admin pages, once the app is ported" 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." patterns: [empty-state] related: [button, data-table] --- ## Purpose What a list shows when it has nothing to show: a heading that says what is missing, a sentence that says what to do, and the action that does it when there is one. ## When to use, when not Use it in place of an empty table or list, whether the user has nothing yet or a search found nothing. Not for an error (Alert), not for a loading state (BusyRegion). ## Anatomy 1. An optional drawing, hidden. 2. The heading, at the level the page gives. 3. The text. 4. The action. ## States With an action, without, and with a drawing. ## Accessibility contract - Role and name: a heading and text; the action is a Button. - Keyboard, focus: the action's. - Live regions: none. A search result that changes in place is announced by the page's LiveRegion, not by this block. ## Content rules French. The title starts with "Aucun" or "Aucune" and says what: "Aucun audit pour l'instant.", "Aucune capture pour ce critère." The text says what to do next, in one or two sentences. ## Do and don't - Do give `level` the level the list's own heading would have had. - Do not show an empty state while the list is loading. ## Examples ```svelte {#if audits.length === 0}

Ajoutez la première page et l'extension fera le reste.

{#snippet action()}{/snippet}
{/if} ``` ## Surfaces - App: the dashboard's search, a site without audits, an organisation without sites, an audit without pages, an account without tokens. - Extension: the audit list of the panel, when it is on Svelte. ## Related Button, DataTable. ## Decisions - 2026-09-24: a heading, not a paragraph, so the empty list is in the outline where the list would be. # ErrorSummary --- name: ErrorSummary slug: error-summary level: molecule status: stable surfaces: [] since: 2026-09 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" 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." patterns: [form-with-error-summary] related: [field, live-region, alert] --- ## Purpose The list of a form's errors at its top, focused at submission, each entry a link to the field at fault. The pattern is GOV.UK's: the summary says how many errors there are and where, the inline message says what to correct, where one corrects. ## When to use, when not Use it on every form with more than one field that can fail. Do not use it for a single field's error (Field shows it), for a success (LiveRegion), or as a live region: it takes the focus, so it is not a status message. ## Anatomy 1. The heading, with the count, which takes the focus. 2. The list, one link per error, with the field's message. 3. The line of non-conformity around, no fill. ## States A form that submits: no error, two errors, one error, saved. ## Accessibility contract - Role and name: a heading at the level given, inside a plain block. No `role="alert"`, no `aria-live`. - Keyboard, focus: the heading takes the focus when the list is set; it is never a Tab stop. Each entry is a link; Enter moves the focus to the field. - Live regions: none, by design. ## Content rules French. The heading counts: "Il y a un problème", "Il y a 3 problèmes". Each entry is the field's message, which says what to write and gives an example (criterion 11.11). ## Do and don't - Do set `errors` on submit and only then; the summary takes the focus each time. - Do give each error the id of its control, the one Field puts on the input. - Do not add a live region around it, nor a role. - Do not validate on each keystroke into the summary. ## Examples ```svelte
``` ## Surfaces - App: every form of the auth pages and the settings; not ported yet. ## Related Field, LiveRegion, Alert. ## Decisions - 2026-09-24: the focus goes to the heading and the link replays the focus move, as the site's demo does, since a router that intercepts internal links hides what the anchor does alone. # Field --- name: Field slug: field level: molecule status: stable surfaces: [site, app] since: 2026-08 replaces: - "apps/web: sixteen hand-built label and input pairs with help text in three sizes" 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." patterns: [form-with-error-summary] related: [button, alert, icon] --- ## Purpose A form field, label included. It exists to make impossible four faults the RGAA sanctions and that a hand-written field repeats endlessly: an unassociated label, a required mark by colour or asterisk only, an error not tied to its field, a help text shown but never announced. ## When to use, when not Use it for every text-like input: text, email, password, url, number, tel, search, and multi-line text. For a choice among options use native radios, checkboxes or a select with their own components; for a search form with a button, the search field component. ## Anatomy 1. The label, visible, with the required mark: an asterisk hidden from assistive technology and the word "(obligatoire)" visually hidden. 2. The hint, announced with the field. 3. The input or the textarea, with `aria-describedby` listing the error then the hint, and `aria-invalid` when there is an error. 4. The error, with the cross icon and the message. ## States Text, email, password, required, with hint, in error, multi-line. ## Accessibility contract - Role and name: a textbox named by its label, "(obligatoire)" included when required. - Description: the error first, then the hint, so what blocks is heard first. - Keyboard: native. - Focus: the ring of the base layer; the field's border turns to the action colour. - Live regions: the error carries `role="alert"` so that a correction is announced without moving the focus. ## Content rules Labels are nouns ("Adresse électronique"), hints are sentences that say the format, errors say what to do ("Indiquez une adresse avec un @"). ## Do and don't - Do give a hint for any format the user could not guess. - Do not rely on the placeholder for anything the user must keep in mind. ## Examples ```svelte ``` ## Surfaces - Site: the gallery; the contact and beta forms still write their fields by hand. - App: every form, from sign-in to the report's parameters; no label or input is written by hand any more. ## Related Button, Alert, Icon. ## Decisions - 2026-08: the error is announced before the hint, because it is what blocks. - 2026-09-24: English props (`required`, `hint`, `error`, `multiline`, `rows`); the error's symbol is the cross icon; the control's height comes from padding and font, never from a pixel value, so zoomed text zooms the field. # FilterGroup --- name: FilterGroup slug: filter-group level: molecule status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the row of status filter buttons above the audits list, written as links with a current class" 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'." patterns: [filter-group] related: [radio-group, select, live-region, chip] --- ## Purpose A row of filters that apply the moment they are pressed: a group named by a visible label, one button per filter with `aria-pressed`, several pressed at once or one alone when exclusive. No form, no "Appliquer"; the page says the count through its live region. ## When to use, when not Use it above a list for two to six filters the user sees at once. Use a RadioGroup when the filters are part of a form submitted later, a Select when there are more than six. Not for navigation between pages, which is a list of links. ## Anatomy 1. The visible label. 2. The buttons, each with its word and its count when given. ## States Exclusive with "Tous" first, and several at once. ## Accessibility contract - Role and name: a `group` named by the label through `aria-labelledby`; each button named by its word, with `aria-pressed`. - Keyboard, focus: Tab reaches each button; Enter and Space press it; the focus never moves on a press. - Live regions: none of the group's. The page says the count of the filtered list through LiveRegion, present from the first render. ## Content rules French. The label is a noun ("Statut", "Thématiques"); the filters are nouns or participles in the plural ("Terminés", "Brouillons"); the first exclusive filter is "Tous". ## Do and don't - Do announce the count after a press, in a sentence: "5 audits affichés". - Do list "Tous" first when exclusive, so the list is never filtered by nothing. - Do not move the focus to the list on a press; the user is still choosing. ## Examples ```svelte ``` ## Surfaces - App: the audits list, the users list. ## Related RadioGroup, Select, LiveRegion, Chip. ## Decisions - 2026-09-26: buttons with `aria-pressed` and not radios: the filters apply at once, and a radio group promises a form. - 2026-09-26: an exclusive group refuses to unpress its last filter: a list filtered by nothing is the whole list, which "Tous" already says. # JumpList --- name: JumpList slug: jump-list level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: renderListeSaut, the jump list of the resume module" 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." related: [disclosure, criteria-recap, resume-banner] --- ## Purpose The elements of a wizard, to jump to one already seen: a disclosure that says how many there are and how many remain, and one button per element. ## When to use, when not Use it in a wizard with several elements, under the header. Not for a list of one, and not for the criteria, which is CriteriaRecap. ## Anatomy 1. The disclosure, titled by the summary. 2. An ordered list of buttons: the drawing, the rank, the label. ## States Closed, and open with the current element and every status. ## Accessibility contract - Role and name: the disclosure's button names the list; each item is a `button` named "3. Photo du produit, non conforme", "élément courant" added on the current one, which also carries `aria-current="true"`. - Keyboard, focus: the disclosure's button, then the items when open. The list is marked `data-raccourcis="off"`: the wizard's shortcut listener leaves arrow keys alone inside it. - Live regions: none. ## Content rules French. The summary counts elements and what remains ("12 éléments, 3 sans réponse"); the labels are the elements' own words, cut by the ellipsis on screen and whole in the name. ## Do and don't - Do keep it closed by default: open, it buried the question under a long list. - Do not put the jump list in the footer; it sits under the header, with the recap. ## Examples ```svelte ``` ## Surfaces - Extension: every inventory wizard. ## Related Disclosure, CriteriaRecap, ResumeBanner. ## Decisions - 2026-09-26: on Disclosure rather than a bare details element, so the button and the panel are the package's; the `data-raccourcis="off"` mark is the wizard shortcut contract, documented on `use:shortcuts`. # Menu --- name: Menu slug: menu level: molecule status: stable surfaces: [] since: 2026-09 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" 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." patterns: [data-table-page] related: [icon-button, disclosure, button] --- ## Purpose A menu of commands behind a button. The rule: does the entry lead somewhere, or does it do something here? A list of links is a Disclosure; commands are what this component holds, with what the ARIA pattern promises at the keyboard and one Tab stop for the group. ## When to use, when not Use it for the commands of a row or an object: duplicate, export, close, delete. Not for links (a Disclosure with a list of links), not for one command (a Button), not for a choice that is a value (Select). ## Anatomy 1. The button, with its text and a chevron, or three dots named by the label. 2. The menu, named, aligned to the start or the end of the button. 3. The entries, one command each; a destructive one in the red of non-conformity. ## States Three dots, a labelled button, and an unavailable command. ## Accessibility contract - Role and name: a button with `aria-haspopup`, `aria-expanded` and, while open, `aria-controls`; a `menu` named by `label`; `menuitem` entries in `role="none"` items. - Keyboard, focus: Enter, Space and the down arrow open on the first entry, the up arrow on the last. Down, Up, Home and End move, wrapping; a letter moves to the next entry that starts with it; Escape closes and returns the focus; Tab closes and lets the focus go; a choice closes, returns the focus, then calls `onselect`. - Pointer: a click outside closes; no veil, which would cost a second click. - Live regions: none. The outcome of a command is the page's, through LiveRegion. ## Content rules French. Entries are verbs with their object: "Exporter en HTML", "Supprimer l'audit". The label names what the commands act on: "Actions pour camille@exemple.fr". ## Do and don't - Do give `label` a name that says which object, when several rows have a menu. - Do not put links in it; that is a Disclosure. - Do not confirm inside the menu: a destructive entry opens a ConfirmDialog after the menu closed. ## Examples ```svelte run(id, audit)} /> ``` ## Surfaces - App: the row menu of the users list, whose commands submit the row's forms. - Extension: the audit list's row commands, when the panel is on Svelte. ## Related IconButton, Disclosure, Button. ## Decisions - 2026-09-24: the entries are data and the outcome a callback with the id, so a page writes its commands once and the keyboard code lives here, where the app and the site each had a copy. # OTPField --- name: OTPField slug: otp-field level: molecule status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the Field of the two-factor page written with inputmode, autocomplete, maxlength and code by hand" 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." patterns: [form-with-error-summary] related: [field, password-field] --- ## Purpose The field of a one-time code: one input, the numeric keyboard, the system's one-time-code autofill, the digits spaced in the mono face, the length bounded to the digits expected. Field's props fixed once, so a page cannot forget one. ## When to use, when not Use it on the two-factor page and wherever a code from a message or an authenticator is typed. Not for a password (PasswordField), not for a code the user copies rather than types (CodeBlock). ## Anatomy 1. Field with the label, the hint, the input in the code face, the error. ## States Six digits, seven with a hint, and an error. ## Accessibility contract - Role and name: the input named by the label, "Code de vérification" by default. - Keyboard, focus: one Tab stop; `autofocus` on a page that holds this field alone; a paste fills the whole code. - Live regions: the error's, from Field. ## Content rules French. The label is "Code de vérification"; the hint says where the code comes from and how long it lasts; the error says what to do ("Demandez-en un nouveau."). ## Do and don't - Do give `autofocus` on the page that holds this field and nothing else; nowhere else. - Do keep one input: six boxes are six stops and a paste that lands in the first. - Do not strip the value on an error; the user corrects one digit. ## Examples ```svelte ``` ## Surfaces - App: the two-factor page. ## Related Field, PasswordField. ## Decisions - 2026-09-26: one input and not one box per digit: the keyboard, the screen reader and the paste all want one field. # PageHeader --- name: PageHeader slug: page-header level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: the three h1 shapes and the eight ruled section heads of the lab pages, once the app is ported" 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." patterns: [data-table-page] related: [heading, eyebrow, prose, button, breadcrumb] --- ## Purpose The head of a page: an eyebrow, the title as the h1, one sentence, and the page's actions where the eye expects them. One component so every page opens the same way. ## When to use, when not Use it at the top of every page of the app, and for a section laid out like a page at level 2. Not for a card's title, not for a dialog's. ## Anatomy 1. The eyebrow, optional. 2. The title, h1 or h2. 3. The lead, optional. 4. The actions, optional. ## States Plain, with an eyebrow, a lead and an action, and at level 2. ## Accessibility contract - Role and name: a `header` element inside the main landmark, holding the heading; no landmark role of its own, since the page's banner is the shell's. - Keyboard, focus: the actions' own. - Live regions: none. ## Content rules French. The title is a noun phrase, the page's name ("Mes audits"); the eyebrow gives the context or a count; the lead is one sentence. ## Do and don't - Do put one primary action at most in the actions; the rest are secondary or in a Menu. - Do not put the breadcrumb inside; it comes before the header. ## Examples ```svelte {#snippet actions()}{/snippet} ``` ## Surfaces - App: the dashboard, the organisation, the site and its settings, the new organisation, site and audit, the comparison, the audited page; at level 2, the section heads that carry an action or a count. ## Related Heading, Eyebrow, Prose, Button, Breadcrumb. ## Decisions - 2026-09-24: composed from Heading, Eyebrow and Prose, so the type of a page head cannot drift from the type elsewhere. # Pagination --- name: Pagination slug: pagination level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: lib/components/Pagination.svelte, gone" - "apps/site: the pagination demo, kept as teaching material" 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." patterns: [data-table-page] related: [table, data-table, link] --- ## Purpose The passage from one page of a list to the next: a list of links in a named landmark, each saying its destination out of context, the bounds gone rather than disabled, the truncation honest, the count announced. ## When to use, when not Use it under any list served in pages. Not for steps (a wizard), not for tabs. ## Anatomy 1. The count, a status region: "Résultats 11 à 20 sur 38 utilisateurs". 2. The landmark, named. 3. The links: previous, the numbers with the gaps, next. ## States Seven pages, forty-two pages, and with the count. ## Accessibility contract - Role and name: `navigation` named by `label`; a list of links; the current page carries `aria-current="page"`. The gaps are hidden and are not links. - Keyboard, focus: ordinary links. When a list updates in place and the activated link disappears, the focus is put on the current page's link. - Live regions: the count, `role="status"`, present even with one page. ## Content rules French. The label names what is paginated: "Pagination des utilisateurs". `rowsName` is the plural noun of the rows. ## Do and don't - Do give two paginations on one screen two labels. - Do let the links navigate; intercept them only for a list that updates in place, and then prevent the default in `onnavigate`. - Do not write the first page in the address; the component only makes the links, but the page's `href` should drop `page=1`. ## Examples ```svelte link(p)} /> ``` ## Surfaces - App: the users list and the two lists of registrations, with the address of a page from the app's helper. ## Related Table, DataTable, Link. ## Decisions - 2026-09-24: links only, and the bounds removed rather than greyed, as both sources argue; the truncation is the app's, which had learnt on the registrations that a sliding window hides the ends. - 2026-09-27: the focus is caught only on a change of page, never at mount. On a fresh load the focus is on the body too, and the list took it and scrolled the admin pages down to itself before anyone had pressed a key. # PasswordField --- name: PasswordField slug: password-field level: molecule status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the plain password Field of the sign-in, register and reset pages, which had no way to show what was typed" 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." patterns: [form-with-error-summary] related: [field, otp-field, button] --- ## Purpose A password field with a control that shows what is typed. Field for the label, the hint and the error; under it a button with `aria-pressed` that switches the input between password and text, keeps the focus, keeps the value, and keeps its name. ## When to use, when not Use it wherever a password is typed: sign in, register, choose a new one, confirm one. Not for a one-time code, which is OTPField; not for a secret the user never types (an API key shown once is a CodeBlock). ## Anatomy 1. Field: the label, the hint, the input, the error. 2. The toggle, under the input, in Field's slot. ## States Sign-in, a new password with its rules, and an error. ## Accessibility contract - Role and name: the input named by the label; the toggle a button named "Afficher le mot de passe" with `aria-pressed` and `aria-controls` on the input. - Keyboard, focus: Tab from the input to the toggle; Enter or Space toggles; the focus stays on the toggle. The value is kept through the toggle. - Live regions: the error's, from Field. ## Content rules French. The label is "Mot de passe" or "Nouveau mot de passe"; the toggle's name stays "Afficher le mot de passe" in both states; the hint of a new password says the rule and gives advice. ## Do and don't - Do give `autocomplete="new-password"` when the user chooses one, and let the default say current-password at sign-in. - Do not change the toggle's text to "Masquer": the pressed state says it, and a name that changes under the focus confuses. - Do not lay the toggle over the input: the browser's own reveal control and the manager's icon sit there. ## Examples ```svelte ``` ## Surfaces - App: sign in, register, forgotten password, reset. ## Related Field, OTPField, Button. ## Decisions - 2026-09-26: a toggle with a constant name and `aria-pressed` rather than a label that alternates: the state is announced, and the name a user learnt stays. - 2026-09-26: the toggle sits under the input, in Field's slot, rather than inside the input's box: the browser and the password manager already draw there. # Popover --- name: Popover slug: popover level: molecule status: stable surfaces: [app, extension] since: 2026-09 replaces: [] 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." patterns: [] related: [tooltip, dialog, menu, disclosure] --- ## Purpose A panel anchored to a button, that does not take the page: a little content or a few controls next to the control that opened it. The button says whether it is open, the panel takes the focus when it opens, Escape closes it and gives the focus back, Tab out of it closes it without taking the focus back, a click outside closes it with no veil. ## When to use, when not Use it for help that is more than a sentence, for a few options that apply at once (the columns of a table), for a small piece of content the page does not need read. Use Tooltip for one sentence that describes a term; Menu for commands; Dialog for what must be answered before going on; Disclosure for content that belongs in the flow. ## Anatomy 1. The button, with its text and the chevron. 2. The panel, when open: the title if any, then the content. ## States Help under a ghost button, controls in a titled panel, and end-aligned. ## Accessibility contract - Role and name: the button named by its text, with `aria-expanded` and `aria-controls` while open; the panel a `dialog` without `aria-modal`, named by its title or by the button's text, with `tabindex="-1"`. - Keyboard, focus: Enter or Space opens and moves the focus into the panel; Tab walks its controls and leaves it, which closes it; Escape closes and returns the focus to the button. - Live regions: none. - Motion: the chevron turns under motion only. ## Content rules French. The button's text says what the panel holds, as a noun or a question ("Colonnes", "Qu'est-ce qu'une dérogation ?"); a title, when given, is a noun phrase. ## Do and don't - Do put controls that apply at once; a form with a submit is a Dialog. - Do not open it on hover or on focus. - Do not nest a popover in a popover. ## Examples ```svelte ``` ## Surfaces - App: the column chooser of the audits table, help next to a setting. - Extension: help next to a wizard's control. ## Related Tooltip, Dialog, Menu, Disclosure. ## Decisions - 2026-09-26: a non-modal `dialog` role rather than a plain region: the panel takes the focus when it opens, and a reader hears what it entered. - 2026-09-26: no veil behind the panel; a click outside closes it and lands where it was aimed, as Menu does. # PrevNext --- name: PrevNext slug: prev-next level: molecule status: stable surfaces: [site, app, extension] since: 2026-09 replaces: - "apps/site: the previous and next links at the foot of the sheets and the articles" 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." patterns: [wizard-step] related: [pagination, breadcrumb, wizard-footer, link] --- ## Purpose The pair between two pages: the one before and the one after, as a small navigation landmark. Each step says its direction in a word and its destination in a label; a link with an address, a button with an action; a missing side is absent. ## When to use, when not Use it at the foot of an article, a sheet, a part of a report, or between two elements of a walk. Not for a numbered list of pages, which is Pagination; not for the path to the page, which is Breadcrumb; the wizard's foot with its counters is WizardFooter. ## Anatomy 1. The landmark. 2. The step before, at the left: the word with its arrow, the label. 3. The step after, at the right. ## States Both sides, the first page, the last page, and buttons in a walk. ## Accessibility contract - Role and name: `nav` named by `label`; each step a link named by its word and its label, or a button. - Keyboard, focus: two Tab stops at most; Enter follows, Enter or Space activates a button. - Live regions: none. A walk that moves in place announces where it landed. ## Content rules French. The words are "Précédent" and "Suivant", or the caller's ("Élément précédent"); the label is the destination's name, verbatim. ## Do and don't - Do name the landmark for what it links ("Fiches voisines"), and differently from another pair on the page. - Do not render a greyed side; give nothing. - Do not write "Page précédente" twice, in the word and in the label. ## Examples ```svelte ``` ## Surfaces - Site: the foot of every sheet and article. - App: between the parts of a report. - Extension: between two elements of a wizard, when the footer is not enough. ## Related Pagination, Breadcrumb, WizardFooter, Link. ## Decisions - 2026-09-26: a grid of two columns with the next step pinned to the second: the eye finds "Suivant" at the right whether or not there is a previous. # PropositionList --- name: PropositionList slug: proposition-list level: molecule status: stable surfaces: [extension] since: 2026-10 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] --- ## Purpose The criteria a fact makes inapplicable, offered to the auditor. Nothing is written until they accept: a wrong "non applicable" takes the criterion out of the denominator and inflates the rate, which is the figure that ends up in the declaration. ## When to use, when not Use it at the top of a page's criteria, where the engine has facts to offer. Not for a verdict the engine proved: that is `AutoVerdict`. ## Anatomy 1. A heading naming the section. 2. The bulk gesture, which becomes its own undo once used. 3. One row per proposition: the criterion, the fact, its source, what the analysis could not see, and a button to set it aside. ## States Two sources with a caveat, a single proposition, and nothing to offer. ## Accessibility contract - Role and name: a `section` named by its heading; three real buttons, each naming its criterion. - Keyboard, focus: every proposition is dismissed from the keyboard; after the bulk gesture the focus lands on the undo that replaced it, at the same place. - Live regions: none of its own. It hands the sentence to `onannounce`, since the panel owns its regions. ## Content rules French. The fact never names another criterion: "Aucun champ de formulaire sur la page", never "NA car 11.1 est NA". The voice of the source changes with the surface, and only there. In the panel the auditor is the reader: "Fait relevé dans la page", "Fait déclaré par vous". In the report and the exports the reader is someone else, so the same fact reads "déclaré par l'auditeur". The wordings live in `@rgaa-dev/core` so the two surfaces cannot drift apart. ## Do and don't - Do show the caveat before the gesture, not after: validating without knowing an accordion stayed closed is signing for a page one has not seen. - Do not write the status directly. The component proposes; the panel writes once the auditor accepts. ## Examples ```svelte ``` ## Surfaces - Extension: the panel's criteria screen. ## Related Button, StatusChip, ThemeList. ## Decisions - 2026-10-26: the undo covers the last accepted batch only, and until the next acceptance or dismissal. A longer history would need a stack the auditor cannot see, and an undo that reaches further than the last gesture is one that surprises. - 2026-10-26: the bulk gesture becomes its own undo, in place, rather than offering a separate "annuler" elsewhere. A gesture that commits twenty criteria at once must be reversible without hunting for the control, and the focus has nowhere to travel. # Question --- name: Question slug: question level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: the question paragraph and the criterion badge each wizard wrote by hand" 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." related: [wizard-frame, answer-buttons, glossary-term] --- ## Purpose The question a wizard asks about one element, with the criterion it settles. The number comes first, as the auditor reads the grid, then the sentence; a help line says what to look at. ## When to use, when not Use it for every question a wizard asks. Not for a finding the tool wrote itself (that is AutoVerdict), not for a heading. ## Anatomy 1. The criterion's number, a badge with a hidden "Critère" before it; several when one answer settles two. 2. The question, one sentence. 3. The help line, optional. ## States One criterion, two criteria settled by one answer, and with a help line. ## Accessibility contract - Role and name: a paragraph; its `id` names the answer buttons that follow it, through `aria-labelledby` on their group. - Keyboard, focus: none, it is text. - Live regions: none. ## Content rules French. The question ends with a question mark and can be answered by oui or non; it names what is judged ("ce titre", "cette image") rather than "l'élément". The help line is one sentence, without a verb of obligation. ## Do and don't - Do give the question an id and name the answer group by it. - Do not put the criterion's full text in the question; it is a disclosure the wizard adds under it. ## Examples ```svelte Ce titre décrit-il bien le contenu du cadre ? ``` ## Surfaces - Extension: every wizard question. ## Related WizardFrame, AnswerButtons, GlossaryTerm. ## Decisions - 2026-09-26: the number before the sentence, and a hidden word before the number: the old badge read "2.2" alone, and a reader said it as a decimal. # RadioGroup --- name: RadioGroup slug: radio-group level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: the hand-built radio list of the account sheet's role and the dashboard's filter buttons" 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." patterns: [form-with-error-summary] related: [checkbox, select, field] --- ## Purpose An exclusive choice among a few options, as native radio buttons in a fieldset with a legend. The shared name makes the group: exclusive, and one Tab stop with the arrows moving the choice. ## When to use, when not Use it for two to five options the user must see at once: a level, a status, a frequency. For more, or when the options need no comparing, Select. For independent options, Checkbox. ## Anatomy 1. The fieldset, with the legend as the question. 2. The hint, tied to the group. 3. The options, each a native radio with its label and an optional hint. 4. The error, tied to the group, with the line of non-conformity along the options. ## States Plain, with a choice made, inline, required, and in error. ## Accessibility contract - Role and name: a group named by the legend; each radio named by its label. - Keyboard, focus: one Tab stop, on the checked option or the first; the arrows move and check. The ring is the system's. - Live regions: the error is `role="alert"`. ## Content rules French. The legend is the question or the thing chosen ("Niveau visé"); the labels are the options as nouns; an option hint says what choosing implies. ## Do and don't - Do give `name` a value unique on the page: two groups with one name merge. - Do use `inline` for two or three short options only. - Do not preselect an option when the choice matters; an empty group asks. ## Examples ```svelte ``` ## Surfaces - App: the dashboard's status filter and the account sheet's role, each option with its sentence. - Extension: the status choice of a criterion, when the panel is on Svelte. ## Related Checkbox, Select, Field. ## Decisions - 2026-09-24: no `aria-invalid` anywhere: a radio does not carry it and a fieldset cannot be invalid. The error is tied to the group by `aria-describedby`, read as an alert, and drawn by a line along the options. # ResumeBanner --- name: ResumeBanner slug: resume-banner level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: renderBandeauReprise in the resume module" 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." related: [wizard-frame, review-flag, jump-list] --- ## Purpose What a wizard found when it reopened on a page already judged: answers kept, elements gone, answers to review, in one sentence before the first question. ## When to use, when not Use it above the body of a wizard that resumed. Not when nothing was resumed; the wizard renders nothing then. ## Anatomy 1. The summary line: the drawing, "Reprise :", the sentence. 2. The note, when the vanished answers need explaining. ## States Answers taken over, and an alert with a note. ## Accessibility contract - Role and name: a block of text; not a live region, since it is part of the screen that just opened and the screen's heading announces the change. - Keyboard, focus: none. - Live regions: none. ## Content rules French; the sentence is the resume module's, with counts and words: "3 réponses reprises, 1 élément à revérifier". ## Do and don't - Do keep the note for the case it explains: vanished elements whose answers still count. - Do not use it for an error saving an answer; that is an Alert. ## Examples ```svelte ``` ## Surfaces - Extension: every inventory wizard. ## Related WizardFrame, ReviewFlag, JumpList. ## Decisions - 2026-09-26: the drawing comes from the icon set (warning, or the return arrow) where the old banner drew glyphs a reader spelt out. # ReviewFlag --- name: ReviewFlag slug: review-flag level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: renderSignalementRevoir in the resume module" 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." related: [resume-banner, answer-buttons, jump-list] --- ## Purpose An answer to review: the element changed since it was given, so the answer is shown but does not count until the auditor confirms it. ## When to use, when not Use it under the element's summary in a wizard that resumed, for an element the resume module marked to review. Not for an element gone; the banner says that. ## Anatomy 1. The title: the drawing and "À revérifier, la page a changé depuis votre réponse". 2. What changed, one line each. 3. The note: the answer as words, and why it does not count yet. 4. The confirm button, named with the element. ## States With the differences, and without them. ## Accessibility contract - Role and name: a block of text and one `button`, "Confirmer ma réponse pour « Photo du produit »". - Keyboard, focus: the button. - Live regions: none. ## Content rules French. The differences are the resume module's sentences ("le texte alternatif a changé"); the answer is the criteria with their words, in the referential's order. ## Do and don't - Do name the element in the button: a screen can show several flags. - Do not confirm on the auditor's behalf after a delay. ## Examples ```svelte ``` ## Surfaces - Extension: every inventory wizard. ## Related ResumeBanner, AnswerButtons, JumpList. ## Decisions - 2026-09-26: the answer's criteria are listed in the referential's order, as the old flag did: "6.2 conforme, 6.1 conforme" cast doubt on the rest. # SearchField --- name: SearchField slug: search-field level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: lib/components/ChampRecherche.svelte, gone with the admin lists' port" - "apps/site: the champ-de-recherche demo, kept as teaching material" 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." patterns: [data-table-page] related: [field, button, live-region] --- ## Purpose A search field: a landmark, a label read and not shown, and a button that says its word. A GET form by default, so the search lives in the address; a page that searches in place intercepts the submission. ## When to use, when not Use it above any list that can be searched. Not for a filter among fixed values (RadioGroup or Select), not for a field whose label must be seen (Field). ## Anatomy 1. The form, `role="search"`. 2. The hidden label. 3. The field, `type="search"`. 4. The button, with the magnifier and its word. 5. The clear link, when there is a search and an address without it. ## States In place with the count announced, and with a clear link. ## Accessibility contract - Role and name: a `search` landmark; the field named by its label; the button by its text. - Keyboard, focus: Enter submits; the button is a real button for whoever navigates by pointer or finger. - Live regions: none here; the page announces the count through LiveRegion when the list updates in place. A GET navigation announces itself by the page's title. ## Content rules French. The label says what is searched: "Rechercher un utilisateur". The placeholder is an example, never the label. ## Do and don't - Do keep the form GET when the list is served by the server; the search then survives a reload and a share. - Do offer `clearHref` on a server-searched list, so a user can leave the search in one move. - Do not put a hidden page parameter in the form; a new search must go back to the first page. ## Examples ```svelte ``` ## Surfaces - App: the two admin lists, users and registrations; the dashboard filters with a Field of type search, since its list filters in place. ## Related Field, Button, LiveRegion. ## Decisions - 2026-09-24: GET by default and `onsearch` for the in-place case, so the app's server-searched lists and the site's demo are one component. # Stat --- name: Stat slug: stat level: molecule status: stable surfaces: [site] since: 2026-08 criteria: - code: "3.1" why: "The figure and its label are text; nothing about the value is said by colour." patterns: [] related: [conformity-rate] --- ## Purpose A figure and what it means. Figures are in tabular numerals so that a row of them aligns and compares at a glance; a secondary value carries the denominator of a fraction ("62/87") without weighing as much as the numerator. ## When to use, when not Use it for a number the page wants read on its own: a total, a coverage, a rate. Do not use it inside a table, where the column already aligns the figures. ## Anatomy 1. The value, large, tabular numerals. 2. The secondary value, in the soft ink, right after it. 3. The label, small capitals. 4. Optionally a sentence under it. ## States With or without a secondary value, with or without a sentence. ## Accessibility contract - Role and name: text. The value is a `b` (strong importance), the label a `span`; a screen reader reads value then label. - Keyboard, focus, live regions: none. ## Content rules The label says what the figure counts ("Critères, tous"), in French, without a colon. A sentence under it gives the source or the date. ## Do and don't - Do give the denominator as the secondary value rather than in the label. - Do not colour a value to say it is good or bad: that is the conformity rate's job, with its symbol. ## Examples ```svelte Mesuré au banc du 18 août 2026. ``` ## Surfaces - Site: the home page, the method page, the application page. - App: through StatGroup, on the audited page, the comparison and the report; the app's StatCard is gone. ## Related ConformityRate. ## Decisions - 2026-08: tabular numerals on the value, so figures in a row line up. - 2026-09-24: English props (`value`, `secondaryValue`, `label`); the sizes, weights and trackings come from the tokens in a scoped style. - 2026-09-24: a `tone` for the figures that count audit statuses, taken from the app's StatCard, which StatGroup retires; the label always says what the figure is. # StatGroup --- name: StatGroup slug: stat-group level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/web: lib/components/StatCard.svelte and the grids of figures of the dashboard and the audit page, once the app is ported" 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." related: [stat, card-grid] --- ## Purpose A row of figures that read together: the criteria, the compliant ones, the non-compliant ones, the rate. A named list of Stats on the surface, the hairline between, so the figures line up and a screen reader counts them. ## When to use, when not Use it for three to five figures that summarise one thing, at the top of a page or a section. Not for one figure (Stat), not for figures that belong to different things. ## Anatomy 1. A named list. 2. One Stat per item, on the surface, with the hairline. ## States Four figures of an audit, and two. ## Accessibility contract - Role and name: a list named by `label`; each item reads value then label. - Keyboard, focus: none. - Live regions: none. ## Content rules French. The label names the group ("Bilan de l'audit"); each item's label says what the figure counts, and the tone follows the audit statuses where they apply. ## Do and don't - Do keep the figures already formatted, with the French decimal comma. - Do not colour a figure that is not a status count. ## Examples ```svelte ``` ## Surfaces - App: the audited page's figures, the comparison's summary, the report's site figures. - Extension: the audit summary of the panel, when it is on Svelte. ## Related Stat, CardGrid. ## Decisions - 2026-09-24: the items are data and the cards draw their own outline, as CardGrid does, so an incomplete row stays clean. # StatusBreakdown --- name: StatusBreakdown slug: status-breakdown level: molecule status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the two loops and three colours the audit page and the audited page each wrote for the split of statuses" 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." patterns: [] related: [status-chip, stat-group, progress-bar] --- ## Purpose How the statuses of a set of criteria split, for a page of the sample or a theme: the count of compliant, non-compliant and non-applicable criteria, each with its chip, and a bar under them that shows the split at a glance. ## When to use, when not Use it wherever a set of criteria has been judged and its split matters more than one figure. Not for a single figure (Stat), not for a progress towards completion (ProgressBar), not when the statuses are all zero, where it shows nothing. ## Anatomy 1. A named list of counts, one item per status that has criteria, the figure then the StatusChip. 2. A bar, hidden from assistive technology, whose segments are the same counts against the set's total. ## States The split of a finished page, a theme in progress with untested criteria left as track, and the counts alone. ## Accessibility contract - Role and name: a list named by `label`; each item reads the figure then the status's abbreviation and word. The bar is `aria-hidden`. - Keyboard, focus: none. - Live regions: none. - Forced colours: the segments keep a border, so the bar still shows its parts. ## Content rules French. The label names the set ("Répartition des statuts de la page", "Statuts du thème Images"). The figures are counts, never percentages; the total is the set's criteria, tested or not, so the track shows what is left. ## Do and don't - Do give the label the set's name, since two breakdowns on one page must be told apart. - Do pass the set's total, not the sum of the counts, so untested criteria show as track. - Do not colour the counts: the chips say the status. ## Examples ```svelte ``` ## Surfaces - App: each page of the audit's sample, each theme of the audited page, and the audit's own split without the bar. ## Related StatusChip, StatGroup, ProgressBar. ## Decisions - 2026-09-26: the bar is hidden rather than described: a description would repeat the list just above it, and a list read twice is noise. # StatusCounters --- name: StatusCounters slug: status-counters level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: renderCompteurs, the counters strip of the wizard foot" criteria: - code: "3.1" why: "Each count carries its word; the status ink on the figure never carries alone." related: [status-chip, wizard-footer, status-breakdown] --- ## Purpose How many criteria of a theme stand in each status, as four figures with their words, in a wizard's foot. ## When to use, when not Use it in a wizard's footer, next to the navigation. Not for a page's summary bar, which is StatusBreakdown, and not for one status, which is StatusChip. ## Anatomy 1. A list named by what it counts. 2. Four items: the figure and the word, conformes, non conformes, non applicables, non testés. ## States A theme in progress, and a theme finished with nothing non-conforming. ## Accessibility contract - Role and name: a list named by `label`; each item reads "3 conformes". - Keyboard, focus: none, it is text. - Live regions: none; the counts change with an answer, and the answer button's state says it. ## Content rules French, the words of `@rgaa-dev/core`. Zero is written. ## Do and don't - Do give the label the theme's name when several themes are on screen. - Do not hide a zero: a theme with no non-conformity says so. ## Examples ```svelte ``` ## Surfaces - Extension: every inventory wizard's foot. ## Related StatusChip, WizardFooter, StatusBreakdown. ## Decisions - 2026-09-26: the words come from core's table, as the chip's do; the old strip had its own copies. # StatusToggle --- name: StatusToggle slug: status-toggle level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: the three status buttons of CriterionRow.svelte and of the string renderer before it" 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." patterns: [panel-shell] related: [criterion-row, status-chip, answer-buttons] --- ## Purpose The three statuses an auditor gives a criterion, as one button each in a named group: the retained one says so with `aria-pressed`, and each button carries the symbol of core next to the letter. "Non testé" is not offered: it is the absence of an answer. ## When to use, when not Use it wherever a criterion is judged in place: the rows of the panel, a criterion form. Not for a wizard's question, which is AnswerButtons with Oui and Non; not to display a status, which is StatusChip. ## Anatomy 1. The group, named "Statut du critère 1.1". 2. One button per status, the symbol hidden, the letter visible, the word in the name. ## States Nothing pressed yet, one pressed, a reduced set, the form size, and the buttons carrying a consumer's hooks. ## Accessibility contract - Role and name: a `group` named for the criterion; each button named "NC, Non conforme, critère 1.1" with `aria-pressed`. - Keyboard, focus: Tab reaches each button; Enter and Space press it; the focus stays on the button pressed, so the row can redraw in place. - Live regions: none; the pressed state is announced by the button itself. A row that also writes a note announces that elsewhere. ## Content rules French. The words are `LIBELLES_DE_STATUT` from core, never a copy; the shapes are the package's status icons, drawn and hidden, never a text glyph, which would count in the visible label and be missing from the name. The group's name is "Statut du critère" and the code. ## Do and don't - Do give the criterion's code, always: three hundred groups named "Statut" are one group. - Do not add a fourth button for "non testé"; clearing an answer is a command of the row, if the product wants one. - Do not colour the letter at rest; the fill is for the pressed state only. - Do give `hooks` when a click delegate answers the buttons: `hooks={() => ({ 'data-action': 'set-status', 'data-criterion-id': id })}`. The component's own `data-status` stays. ## Examples ```svelte setStatus(criterion.id, status)} /> ``` ## Surfaces - Extension: every criterion row of the panel. ## Related CriterionRow, StatusChip, AnswerButtons. ## Decisions - 2026-09-26: pressing the retained status calls the handler again rather than clearing it here: whether a second press clears the answer is the store's rule, and the component does not keep a state of its own. - 2026-09-27: `hooks` hangs a consumer's `data-*` attributes on each button, rather than the package naming the extension's actions: the extension's click delegate, its focus return and its walks read `data-action="set-status"` and the criterion, and the web app reads nothing. `DataHooks` admits `data-*` keys only, so no hook can change a name or a role. # Table --- name: Table slug: table level: molecule status: stable surfaces: [] since: 2026-09 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" 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." related: [data-table, scroll-region] --- ## Purpose A table written by hand, for the cells DataTable's columns and rows cannot express, held to the same things: a caption, the scroll region reachable at the keyboard and named by that caption, and the cell rules the base layer used to carry as `tableau` classes. ## When to use, when not Use it when the cells need their own markup: a status chip, a row menu, a multi-line constat. When the rows are records and the columns keys, DataTable. Never for layout. ## Anatomy 1. The scroll region, named by the caption. 2. The table, with its caption. 3. The caller's `thead` and `tbody`, with `scope` on every `th`. ## States Plain, the report variant, and with the caption hidden. ## Accessibility contract - Role and name: a table named by its caption; the scroll region around it is a region with the same name. - Keyboard, focus: the region takes the focus after the content before it, so a wide table scrolls at the keyboard. - Live regions: none. - Headers: every `th` carries `scope="col"` or `scope="row"`, written by the caller; the test of every story checks it. ## Content rules French. The caption says what the rows are ("Taux de conformité par page"), not "Tableau". ## Do and don't - Do write `scope` on every `th`; the component cannot do it for you. - Do put numbers in `rl-table__num` cells so they align. - Do not hide the caption unless the same title is right above the table. ## Examples ```svelte {#each pages as p}{/each}
PageTaux
{p.name}{p.rate}
``` ## Surfaces - App: the site's audits, the five measures tables, the registrations, the users, the comparison; the report's tables wait for the report document. ## Related DataTable, ScrollRegion. ## Decisions - 2026-09-24: the cell classes are `rl-table__num`, `rl-table__center`, `rl-table__clip` and `rl-table__line`, reached through `:global` since the cells are the caller's; the base layer's `tableau` rules leave with their last raw use. # Tabs --- name: Tabs slug: tabs level: molecule status: stable surfaces: [] since: 2026-09 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" 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." patterns: [] related: [accordion, disclosure] --- ## Purpose Views of equal weight, one shown at a time. The component of the system that needs the most ARIA, and the reason it is one: nothing in HTML makes a tab list, so the roles, the selected state, the relations and above all the keyboard path must be declared, and the point everyone misses is that Tab does not walk the tabs. ## When to use, when not Use it for two to six views of the same thing the user switches between: the detection, the assistants and the report of a page; the light and dark set of a token. Do not use it for sequential steps (a wizard), for sections a reader scrolls through (headings), or for content the page needs read at once. ## Anatomy 1. The tab list, named, with one button per view. 2. The selected tab, its line joining the panel. 3. The panels, one shown, each named by its tab and able to take the focus. ## States The first tab selected, and another selected at first render. ## Accessibility contract - Role and name: `tablist` named by `label`; `tab` for each button with `aria-selected` and `aria-controls`; `tabpanel` named by its tab with `aria-labelledby`. - Keyboard, focus: one tab is in the tab order, the selected one. Left and Right move and select, wrapping; Home and End go to the ends. After the list, one Tab lands in the panel, which carries `tabindex="0"`. - Live regions: none; the selected tab announces itself. - Activation is automatic: choosing a tab shows its panel. Right for light panels, which is the product's case. ## Content rules French. Tab labels are one or two words, nouns ("Détection", "Rapport"). The list's name says what the tabs are views of. ## Do and don't - Do give `label` a name that reads before the tabs: "Ce que fait l'extension, liste d'onglets". - Do not put a heading inside a tab; the tab is the name. - Do not use tabs for a form split in steps. ## Examples ```svelte {#snippet panel(tab)}

{texts[tab.id]}

{/snippet}
``` ## Surfaces - App: the report page's view switcher; not ported yet. - Extension: the wizard's evidence and comment views, when the panel is on Svelte. ## Related Accordion, Disclosure. ## Decisions - 2026-09-24: automatic activation, from the site's onglets demo, whose reasoning holds: the keyboard user discovers the panels by pressing the arrows. - 2026-09-24: the panel takes the focus, so nothing inside it is out of reach when it scrolls. # ThemeSwitch --- name: ThemeSwitch slug: theme-switch level: molecule status: stable surfaces: [site, app, extension] since: 2026-08 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: [] --- ## Purpose The choice of theme: automatic, light, dark. Following the system is a good default and not a sufficient answer; for whoever suffers from photophobia or visual fatigue, the choice is a reading condition. Three states rather than two, so that choosing does not mean giving up the system's switch at sunset. ## When to use, when not Once per surface, in the header or the sidebar. A second instance on a page, as in a mobile menu, takes its own `name`. ## Anatomy 1. A fieldset with a visually hidden legend that names the group and says the current choice. 2. Three labels, each wrapping a visually hidden radio and its word. ## States Automatic, light or dark checked. ## Accessibility contract - Role and name: a radio group named "Thème d'affichage"; each radio named Auto, Clair or Sombre. - Keyboard: Tab enters on the checked radio, the arrows move the choice, nothing else. - Focus: shown on the visible label, since the radio is out of the visual field; never `display: none`, which would remove the radio from the tab order. - Forced colours: the checked option is drawn with a border, since a background fill is dropped. - The choice is read, stored and applied by `theme/theme.ts`, the same module on the three surfaces. ## Content rules The three French words: Auto, Clair, Sombre. ## Do and don't - Do keep it a radio group; a cycling button makes the user guess what comes next. - Do not hide the radios with `display: none`. ## Examples ```svelte ``` ## Surfaces - Site: the header, and the mobile menu with its own name. - App: the lab sidebar. - Extension: the panel's toggle cycles the same three choices through the same module. ## Related None. ## Decisions - 2026-08: native radios, so the state is announced without a line of script. - 2026-09-24: reads and writes through the shared theme module. - 2026-09-24: renamed from ChoixTheme; the legend says "actuellement" with a comma, no dash. - 2026-09-27: `onchange`, called once the choice is stored and applied. The extension's panel keeps the theme in a store that also tells the audited page, so its highlights repaint; the switch stored and applied on its own, and the page never heard of the change. # Toast --- name: Toast slug: toast level: molecule status: stable surfaces: [] since: 2026-09 replaces: - "apps/extension: showToast and the green toast of the panel and the content script, when the panel is on Svelte" 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." related: [live-region, alert] --- ## Purpose The corner where the page says what just happened. A toast is a status message with a place on screen; what makes it heard is the region being present from the first render, and this component is that region, holding the messages the page gives it until the page or the user removes them. No timer. ## When to use, when not Use it for the outcome of an action the user does not need to answer: saved, exported, copied, failed with nothing to do. Not for an error that needs an answer (Alert in the page, or Dialog), not for a message the user must read before going on. ## Anatomy 1. The region, at the bottom right or in the flow. 2. One item per message: the drawing, the sentence, the close button. ## States In the flow, and with the success and error tones. ## Accessibility contract - Role and name: `status` with `aria-live="polite"` on the region; each close button named by `dismissLabel`. - Keyboard, focus: a toast never takes the focus. The close buttons are in the tab order where the region sits, at the end of the page when fixed. - Live regions: this is one. Mount it with the page, once, and change `messages`. - Timing: no automatic removal. If a page removes a message itself, it waits long enough to read it and never removes one the user is interacting with. ## Content rules French. One sentence that says what happened: "Audit enregistré.", "Rapport publié.", "La connexion a été perdue." ## Do and don't - Do mount one Toast per page, with the page; never one per message. - Do give every message a stable id, so closing one removes that one. - Do not put an action inside a toast; what needs an answer is a dialog. - Do set `passthrough` when the corner sits over controls the user is about to press, as the panel's wizard bar; then the page removes the messages itself, since there is no close button. ## Examples ```svelte (toasts = toasts.filter((t) => t.id !== id))} /> ``` ## Surfaces - Extension: the panel's confirmations, rendered by the shell from a list of messages, with `passthrough` since the corner sits over a wizard's bar. - App: the save and copy confirmations; not ported yet. ## Related LiveRegion, Alert. ## Decisions - 2026-09-24: no timer. The extension's toast vanished after two seconds, which is shorter than a sentence read by a screen reader; the page removes a message when it knows it can. - 2026-09-26: `passthrough`, for the panel. Its toast sat on a wizard's Précédent / Suivant bar and a Firefox walk refused the click Chromium had let through; a status is not a control, and the click goes to the bar. - 2026-09-27: the fixed corner's width counts its gutters. `min(100%, 24rem)` resolved to the viewport while the box sat 16 px from its right edge, and in the 216-pixel panel every message lost its left side. # WizardFooter --- name: WizardFooter slug: wizard-footer level: molecule status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: renderPiedDInventaire and the four copies it replaced" 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." related: [wizard-frame, button, status-counters] --- ## Purpose The foot of a wizard: back, forward, and finish on the last element, with the theme's counters on the left. ## When to use, when not Use it in every wizard's footer slot. Not for a dialog's actions, which are the Dialog's. ## Anatomy 1. The counters, on the left, given by the wizard. 2. Précédent, then Suivant, or Terminer on the last element. ## States In the middle of a list, on the first element, on the last, and a finish that is unavailable with its reason. ## Accessibility contract - Role and name: three buttons, named by their words; `data-action` says which for the walks. - Keyboard, focus: Tab reaches them in reading order; Enter on the screen advances through the page's shortcut listener, and the hint on the button says so. - Live regions: none. ## Content rules French. Précédent, Suivant, Terminer; the reason of an unavailable finish is one sentence saying what is missing. ## Do and don't - Do show Terminer instead of Suivant on the last element, never both. - Do not disable Suivant to force an answer: an element can be skipped and come back through the jump list. ## Examples ```svelte {#snippet counters()}{/snippet} ``` ## Surfaces - Extension: every inventory wizard. ## Related WizardFrame, Button, StatusCounters. ## Decisions - 2026-10-07: the label of a nav button never wraps; the row wraps instead. `white-space: nowrap` is set here rather than on Button, whose link variant is written inside a sentence and must break with it. - 2026-09-26: Terminer replaces Suivant on the last element, as the old foot did; the reason of an unavailable finish is written next to the button through Button's `reason`. # CriterionRow --- name: CriterionRow slug: criterion-row level: organism status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: sidepanel/sidebar/CriterionRow.svelte, whose part under the row is now CriterionDetail.svelte" 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." patterns: [panel-shell] related: [status-toggle, theme-list, evidence-block, button, chip] --- ## Purpose One criterion of the grid, judged in place: the code and the title, the statuses in a named group, the note button, the wizard of its own when it has one, the comment, and under all that whatever the panel adds. The row keeps no state; the extension is the consumer and writes through the store. ## When to use, when not Use it inside ThemeList, or in any list of criteria judged one by one. Not for a criterion displayed without being judged (a table row with a StatusChip), and not for a wizard's question. ## Anatomy 1. The info: the code, the title, the engine chip when the engine wrote the status. 2. The actions: StatusToggle, the note button, the criterion's own wizard. 3. The comment. 4. The children: the note editor, the engine's line, the doc, the proof. ## States Untested, judged with a comment, written by the engine with a wizard of its own, and carrying a consumer's hooks with its wizard collecting. ## Accessibility contract - Role and name: an `li`, so the parent is a list; inside, the StatusToggle group named for the criterion and buttons whose names carry the criterion's code. - Keyboard, focus: Tab through the statuses, the note, the wizard; a status pressed keeps the focus, since the row redraws in place. - Live regions: none of the row's. The panel announces a status change through its own region. ## Content rules French. The title is the referential's, verbatim; the note button reads "Ajouter une note" or "Modifier la note", and its name adds the criterion's code; the engine chip reads "Posé automatiquement". ## Do and don't - Do give `oncomment` only where a note can be written; without it there is no note button. - Do put the note editor in the children, under the row, so the focus moves from the button to the field and back. - Do not put the proof in the row for a conforming criterion; EvidenceBlock is for a non-conformity. - Do hang a click delegate's attributes through `hooks` (`row`, `status`, `note`, `action`), never by wrapping the row: the focus return of a delegate finds the very button it marked. ## Examples ```svelte setStatus(criterion.id, s)} comment={result?.comment} oncomment={() => edit(criterion.id)}> {#if result?.status === 'NC'}{/if} ``` ## Surfaces - Extension: the rows of every theme of the panel. ## Related StatusToggle, ThemeList, EvidenceBlock, Button, Chip. ## Decisions - 2026-09-26: the engine's origin is a chip with a word, not a data attribute alone: the extension's row said it with `data-origine` and the eye had nothing. - 2026-09-26: the row is an `li` and nothing else; a caller that has no list gives it one. - 2026-09-27: `hooks` carries the consumer's `data-*` attributes to the row, the statuses, the note and the wizard, and `action` takes `busy`: the extension's delegate answers `set-status` and `edit-comment`, returns the focus to them after a write, and its walks read `data-criterion-row`; the package names none of them. - 2026-09-27: `action` takes `reason` (unavailable with its sentence) or `describedBy` (unavailable, pointing at a sentence the screen says once, which ThemeList sets from its own reason). 11.9's wizard ran off the audited page and wrote another page's buttons under it. # DangerZone --- name: DangerZone slug: danger-zone level: organism status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the settings page's zone-de-suppression section, its frame and its hand-wired ConfirmDialog" 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'." patterns: [destructive-confirmation] related: [confirm-dialog, button, dialog] --- ## Purpose The part of a settings page where the actions cannot be undone: a heading that says what is at stake, a sentence that says what is lost, and one destructive button per action, each asking first through ConfirmDialog. ## When to use, when not Use it at the foot of a settings page for deleting, resetting or transferring what cannot be restored. Not for an action that can be undone (a plain Button, perhaps a Toast with "Annuler"), not for a single delete button in a table row (a Button with its own ConfirmDialog). ## Anatomy 1. The frame, the strong structural line. 2. The heading. 3. The explanation. 4. One destructive button per action, and its question, closed until pressed. ## States One action, several actions, and an action under way. ## Accessibility contract - Role and name: a `section` named by its heading through `aria-labelledby`; each action a button named by its words; each question a `dialog` named by its heading. - Keyboard, focus: Tab reaches each button; Enter opens its question with the focus on "Annuler"; Escape closes it and the focus returns to the button. - Live regions: none of the zone's. After the work, the page says what happened, through its LiveRegion or its Toast, or by leaving the page. ## Content rules French. The heading names what is at stake ("Suppression du site"). The explanation starts with "Irréversible." when the actions are, and says what is lost. The button label is a verb and its object ("Supprimer le site"); the question repeats it with "définitivement" and a question mark; the consequence ends with "Cette action est irréversible." ## Do and don't - Do put the zone last on the page, after everything that can be changed and changed back. - Do let `onconfirm` do the work the page owns: submit its form, call its API. - Do not tint the zone with a status ink; the words carry the warning. - Do not write a glyph in a button's words; the verb says it. ## Examples ```svelte
deletion?.requestSubmit() }]} >

Irréversible. Tous les audits de ce site seront perdus.

``` ## Surfaces - App: a site's settings page. ## Related ConfirmDialog, Button, Dialog. ## Decisions - 2026-09-27: the frame is the strong line and not the non-conformity red: a status ink used as an accent would stop meaning a status. - 2026-09-27: the page keeps its form and the zone calls back on confirm: a form inside the component could not take the page's `use:enhance`. # Derogation --- name: Derogation slug: derogation level: organism status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: renderDerogation, the derogation form and its posed state in the resume module" 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." related: [disclosure, radio-group, field, checkbox, resume-banner] --- ## Purpose Derogating a content from the evaluation, with the nature the referential distinguishes and a required justification, or the derogation already posed with a way to take it back. ## When to use, when not Use it under an element's question in a wizard. Not for a criterion: a criterion is never derogated, and the block says so. ## Anatomy 1. Posed: the state line, the justification, "Retirer la dérogation". 2. To pose: a disclosure, the help line, the nature as a radio group, the alternative checkbox for a disproportionate burden, the justification field, "Déroger ce contenu". ## States The form closed, the form open on a disproportionate burden, and a derogation posed. ## Accessibility contract - Role and name: the disclosure's button "Déroger ce contenu"; a radio group with the legend "De quelle nature ?"; the field required and labelled; the two action buttons named with the element's label. - Keyboard, focus: the disclosure, then the form's controls in order. The block is marked `data-raccourcis="off"`, so the wizard's letter shortcuts leave it alone while a sentence is typed. - Live regions: the error is the field's, tied by `aria-describedby`. ## Content rules French, the referential's words: "exempté par le cadre légal", "charge disproportionnée". The justification names the content and the reason. ## Do and don't - Do ask the nature before the justification: the two do not leave the count the same way. - Do not pose a derogation without a justification; the form refuses it. ## Examples ```svelte ``` ## Surfaces - Extension: every inventory wizard. ## Related Disclosure, RadioGroup, Field, Checkbox, ResumeBanner. ## Decisions - 2026-09-26: the alternative checkbox shows only for a disproportionate burden, since the question has no meaning for an exempted content. - 2026-09-27: the submit button's name is its visible words followed by the element, "Déroger ce contenu, Photo du produit", with no aria-label. The aria-label "Déroger « Photo du produit »" left out "ce contenu", which fails label in name (criteria 6.1 and 11.9): a voice user saying what they see did not reach the button. # EvidenceBlock --- name: EvidenceBlock slug: evidence-block level: organism status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: sidepanel/sidebar/EvidenceBlock.svelte" 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." patterns: [panel-shell] related: [criterion-row, figure, button, kbd] --- ## Purpose The proof of a non-conformity: the elements at fault, each with its capture or the way to take it, the way to point at one the detection did not find, and the instruction while the pointing runs. Drawn for every non-conformity, even with nothing detected: empty, it carries the one gesture that fills it. ## When to use, when not Use it under a CriterionRow whose status is non conforme. Not for a conforming or a non-applicable criterion, and not in the wizards, which capture as they go. ## Anatomy 1. The title, which counts the elements, with the flags: missing, refused, complete. 2. The list: per element its label, Montrer, and Capturer, Reprendre or Réessayer; the refusal's reason; the capture. 3. The sentence when nothing is detected. 4. The pointing: the button, or the instruction with its keys and the way out. ## States Detected with the three cases, complete, empty, pointing, and as the extension draws it: its own Montrer control, its hooks, a capture in flight and a capture kept of an element no longer detected. ## Accessibility contract - Role and name: the list is named by the title through `aria-labelledby`; every button names the element and the criterion; the cancel button is described by the instruction. - Keyboard, focus: Tab through Montrer and Capturer per element, then the pointing button; the instruction says how to reach the page (F6 under Chrome) and how to point with Tab and Entrée. - Live regions: none of the block's. The panel announces a capture taken or refused. - The drawings are hidden; the words carry the flags. ## Content rules French. The title reads "Éléments fautifs (3)" or "Aucun élément désigné"; the flags "sans capture", "non capturable", "preuves complètes"; the verb follows the state: "Capturer", "Reprendre", "Réessayer". The empty sentence is "Une non-conformité sans capture est une affirmation. Montrez l’élément.". ## Do and don't - Do count a refused element apart from a missing one: a warning nothing can clear sends the auditor to the same wall. - Do keep a capture whose selector left the detected set: it is evidence the auditor took. Give it `detached`, and a `key` when two such captures share a selector. - Do give an item `busy` while its capture is taken, rather than disabling the button under the finger that pressed it. - Do hand a Montrer control of your own through `show` when it keeps a state (lit or not in the page); `onshow` draws a plain button. - Do not hide the block when nothing is detected; the auditor can see the defect and needs a way to point at it. ## Examples ```svelte pick(criterion.id)} oncancel={cancelPick}> {#snippet shot(item)}{/snippet} ``` ## Surfaces - Extension: under every non-conforming criterion of the panel. ## Related CriterionRow, Figure, Button, Kbd. ## Decisions - 2026-09-26: the captures are a snippet, not a prop: the extension stores them as blobs in its own store, and the block has no reason to know their shape. - 2026-09-26: the flags are icon drawings rather than the text symbols: the block sits in the panel, where the icon set is loaded, and a drawing sizes with the icon tokens. - 2026-09-27: an item can be `detached` (a capture kept with no button, counted in the title and never as missing), `busy` (its capture in flight) and keyed apart from its selector; `show` takes the consumer's own Montrer control; `hooks` carries the consumer's `data-*` attributes to the capture, pointing and cancel buttons, which the extension's delegate answers and its focus return finds after a capture or a pick. What the extension drew by hand, and the package could not, is now the package's. # Onboarding --- name: Onboarding slug: onboarding level: organism status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the dashboard's welcome card, its etapes list and its hand-drawn ranks" 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." patterns: [empty-state] related: [empty-state, card, button, link] --- ## Purpose The first-steps block of a workspace that has nothing in it yet: a heading, a sentence that says what the steps lead to, the steps in order, each with its label, its sentence, its state and its action, the one call to action that starts, and an optional way to dismiss it. ## When to use, when not Use it on a dashboard or a home screen the first time a user arrives, when there are two to five things to do in an order. Not for an empty list the user has already started (EmptyState), not for a form split in steps (a wizard), not for a message that interrupts (Banner). ## Anatomy 1. The heading, at the level the page gives, and the dismiss button when the page allows it. 2. The intro, a sentence. 3. The ordered list of steps: the hidden rank, the label, the state word and its drawing when done, the description, the step's own action. 4. The call to action. 5. The foot, under a line: another way in. ## States The first visit with nothing done, a visit with a step done and actions on the others, and dismissible. ## Accessibility contract - Role and name: a `section` named by its heading through `aria-labelledby`; an `ol` of steps; a step's action is a Link with `href`, a Button otherwise. - Keyboard, focus: the actions', in reading order. The dismiss comes first, next to the heading; the page moves the focus to what follows when it hides the block. - Live regions: none of the block's. The page says "Les premiers pas sont masqués." through its LiveRegion after a dismiss. - A step done says "Fait" in words, after a hidden comma so the label and the state are heard apart. ## Content rules French. The heading welcomes or names the moment ("Bienvenue sur RGAA Lab", "Premiers pas"). Each label is a verb in the infinitive ("Créer un audit"); each description one sentence ending with a period. The call to action says what it does ("Créer mon premier audit"). ## Do and don't - Do keep the steps to what the user must do, in the order it must be done. - Do give a step done its `done` flag rather than removing it: the list says how far the user has come. - Do not number the labels by hand; the list numbers them. - Do not put a glyph in an action's words; the words say it. ## Examples ```svelte

Un audit se mène en trois temps.

{#snippet actions()}{/snippet} {#snippet footer()}

Connectez l'extension depuis les paramètres du site.

{/snippet}
``` ## Surfaces - App: the dashboard of an account without audits. ## Related EmptyState, Card, Button, Link. ## Decisions - 2026-09-27: the rank is drawn and hidden, the list is ordered: the number is read once, by the list. - 2026-09-27: a step done keeps its place with the check and "Fait", and loses its action: there is nothing left to do there. # PageSwitcher --- name: PageSwitcher slug: page-switcher level: organism status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: sidepanel/dialogs/PagesDialog.svelte, once the panel reads its store through props" 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." patterns: [panel-shell] related: [dialog, panel-shell, button, icon-button] --- ## Purpose The pages of an audit, in a dialog: each one to open, with how far it is, the current one said as such, one to delete when there are several, and the tab's page to add. The dialog stays mounted and opens on the prop, so a close by the app still returns the focus. ## When to use, when not Use it from the panel's header, where the current page is named. Not in the app, which lists an audit's pages in a table with their rates. ## Anatomy 1. The dialog, named "Pages de l’audit". 2. The list: per page a link with its name, its short address, its progress; a delete button when there are several pages. 3. The add button with the tab's path and the plan's note, or the sentence when the page is already in the audit. ## States Several pages with the tab's page to add, and a lone page already in the audit. ## Accessibility contract - Role and name: Dialog's, named by its heading; the links named by the page's name with the address and the progress in their text; `aria-current="page"` on the current one; the delete buttons named "Supprimer" and the page. - Keyboard, focus: the first link takes the initial focus, since the close button is last in the DOM; Tab walks the links and the delete buttons, then the add button; Escape closes and returns the focus. - Live regions: none of the dialog's. The panel announces the page shown. ## Content rules French. The address is shown without its scheme and cut at forty characters; the progress reads "38 %" with "avancement" for a reader; the add button reads "Ajouter cette page" with the path under it. ## Do and don't - Do close the dialog from `onswitch`: the caller sets `open` back, and the focus returns to the header's button. - Do give `addPath` only when the tab's page is not in the audit; the sentence covers the other case. - Do not offer to delete the last page: an audit without a page is not an audit. ## Examples ```svelte ``` ## Surfaces - Extension: the page switcher of the panel's header. ## Related Dialog, PanelShell, Button, IconButton. ## Decisions - 2026-09-26: a link per page, intercepted on the plain click, rather than a button: the address is the page's, and the middle click still opens it in a tab. - 2026-09-26: the bar's width is a custom property written from the progress, the one dynamic value the component styles; the figure next to it carries the information. - 2026-09-27: the heading and the sentence take the typographic apostrophe ("Pages de l’audit"), as every French string of the panel does; the straight one was the only one left on its screen. # Section --- name: Section slug: section level: organism status: stable surfaces: [site] since: 2026-08 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] --- ## Purpose The page section, and the owner of the vertical rhythm. Without it, spacing gets negotiated page by page in utility classes and diverges; the site had reached one combination of margins per section. ## When to use, when not Use it for every block of a page that opens with a heading. Do not use it inside a card or a form, and do not nest one inside another. ## Anatomy 1. A `section` element, in the wide container. 2. Optionally a structural rule above, drawn as a top border, never as a background. ## States Three rhythms (lead, default, tight) and the optional rule. ## Accessibility contract - Role and name: a plain `section`, unnamed, so it is not announced as a region. The heading inside it gives the structure. - Keyboard, focus, live regions: none. ## Content rules None of its own. ## Do and don't - Do open a page with the lead rhythm and follow with the default one. - Do not add margins by hand on a section; change the rhythm. ## Examples ```svelte
Outil de travail Les composants, dans tous leurs états.
Actions
``` ## Surfaces - Site: 34 pages. ## Related Heading, Eyebrow, Prose. ## Decisions - 2026-08: the rule is a top border only, because Tailwind's `border-strong` is a width that applies to all four sides. - 2026-09-24: props `rhythm` (`lead`, `default`, `tight`) and `rule`; the container, the rhythms and the rule are `rl-section` rules on the spacing and line tokens. # ThemeList --- name: ThemeList slug: theme-list level: organism status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: sidepanel/sidebar/ThemeRow.svelte, and the theme loop of ThemeList.svelte, now ThemeScreen.svelte on this list" 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." patterns: [panel-shell] related: [criterion-row, status-toggle, panel-shell, disclosure, button] --- ## Purpose The themes of the grid with their criteria under them: one row per theme with its number, its name, the count of its non-conformities and of the engine's lines, the count of what is evaluated as the button that unfolds the criteria, and the wizard's button whose word follows the work. Everything comes in as props and goes out as callbacks; the extension's store stays the single source. ## When to use, when not Use it as the content of the panel's audit screen. Not for a page of the app, which reads the same criteria in tables; not for a single theme, which is one entry of the list. ## Anatomy 1. Per theme, the header: the number, the heading, the non-conformity chip, the engine chip, the folding button, the wizard button. 2. The list of CriterionRows, hidden when folded. ## States The grid with a started, a finished and an untouched theme; off the page; a wizard collecting; and as the extension draws it, the unfolded themes held by its store, its hooks, a theme without a wizard. ## Accessibility contract - Role and name: a `section` per theme, its heading "Thématique 1, Images" at `level`; the folding button with `aria-expanded` and `aria-controls`, named by the tally and "ouvrir les critères un par un"; the wizard button named "Tester la thématique 1, Images avec l’assistant". The engine's count and the non-conformities' symbol are hidden; their words are in the chip for a screen reader. - Keyboard, focus: Tab reaches the folding button and the wizard button of each theme, then the rows of an unfolded theme; a fold keeps the focus on its button. - Live regions: none of the list's. A status change is announced by the panel. ## Content rules French. The tally reads "4 sur 9" and "Terminé" when every criterion is examined, never a check mark: examined is not passed. The wizard word is "Tester", "Continuer" or "Revoir". The reason for an unavailable wizard says what to do: "Revenez à la page auditée pour lancer ce test.". ## Do and don't - Do give `wizardReason` when the tab left the audited page rather than hiding the buttons: the auditor learns why. - Do put what belongs under a row in `detail`; the list never reads the store. - Do pass `open` from the store and write it back in `ontoggle` when the list is mounted again and again; alone, the list keeps it itself. - Do hang a click delegate's attributes through `hooks` (`toggle`, `wizard`, `criteria`, `row`): the wizard hook is where each theme names its own action. - Do give `wizard: false` to a theme that has no wizard rather than dropping `onwizard` for the whole list. - Do not unfold every theme at first: thirteen open lists bury the one being worked. ## Examples ```svelte {#snippet detail(criterion)} {#if criterion.status === 'NC'}{/if} {/snippet} ``` ## Surfaces - Extension: the audit screen of the panel. ## Related CriterionRow, StatusToggle, PanelShell, Disclosure, Button. ## Decisions - 2026-09-26: which themes are unfolded is the list's own state, reported through `ontoggle`, as Accordion does; it is the one thing a store does not need to know to redraw the grid. - 2026-09-26: the folding button is not a Disclosure: its heading is not the button, since the row holds two buttons and the name of the theme must stay a plain heading. - 2026-09-27: `open` is bindable and followed, where it was seeded once: the extension's list is mounted again at every full redraw of its panel, and the unfolded themes live in its store, so the list must reopen what the store says. `hooks` carries the consumer's `data-*` attributes, the wizard hook naming each theme's action; `wizard: false` leaves a theme without the button; the engine's chip draws the settings icon before its figure, which alone read as a second tally, and the non-conformities' chip keeps the codes in its tooltip, as the extension's did. - 2026-09-27: the reason the wizards cannot run is written once above the themes, every wizard button disabled and pointing at it with `aria-describedby`: Button's own reason, next to each, repeated one sentence thirteen times in a 216-pixel column. # WizardFrame --- name: WizardFrame slug: wizard-frame level: organism status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: the shell each wizard wrote as a string, header, body and footer, eight copies" 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." patterns: [wizard-step] related: [question, answer-buttons, wizard-footer, criteria-recap, jump-list] --- ## Purpose The frame of a wizard screen in the extension's panel: a region named by its title, a header with the counter and the close button, a body that scrolls, a footer that stays in sight. Every wizard writes its screen inside it. ## When to use, when not Use it for every screen of a wizard, the empty-population screen included. Not for the panel's sidebar, which has its own shell, and not for a dialog. ## Anatomy 1. The header: the title as a level-2 heading, the progress "3 / 12", the wizard's tools (the veil switch, an inspector), the close button. 2. What a wizard puts above the body: a resume banner, a jump list, a recap. 3. The body, which scrolls. 4. The footer: the navigation and the counters. ## States Bare, with a progress and a close button, and with a footer. ## Accessibility contract - Role and name: a `section` named by its title, which is an `h2` with `tabindex="-1"` so a screen change can move the focus to it. - Keyboard, focus: the close button is a labelled button; the body scrolls with the keyboard since it holds focusable controls; the shortcuts of a wizard are the page's, on `use:shortcuts`. - Live regions: none; the panel's shell owns them. ## Content rules French. The title names the theme audited ("Audit des cadres", "Images"); the progress is the element's rank over the total, never a percentage. ## Do and don't - Do give the frame the wizard's kind, so the panel's doors between wizards can read it. - Do not put the counters in the header: they belong to the footer, with the navigation they inform. ## Examples ```svelte {#snippet footer()}{/snippet} Ce titre décrit-il bien le contenu du cadre ? ``` ## Surfaces - Extension: every wizard, as they are ported. ## Related Question, AnswerButtons, WizardFooter, CriteriaRecap, JumpList. ## Decisions - 2026-09-26: the body scrolls, not the frame. The old shells scrolled two different containers depending on the wizard, and a Oui/Non answer sent some of them back to the top at each click. # AppShell --- name: AppShell slug: app-shell level: view status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: routes/lab/+layout.svelte, the top bar, the column, the rail, the drawer and the content area written by hand" 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." patterns: [app-shell] related: [busy-region, skip-link, banner, icon-button, theme-switch, logo] --- ## Purpose The frame of the application: a column of navigation on the left, the content area on the right, and what sits between them. The column holds every way to go somewhere, the foot says who is signed in and how to leave, the content area says when it is loading. ## When to use, when not Use it as the layout of every page a signed-in user sees. Not for the pages before sign-in, which take AuthShell; not for a document meant to be printed, which hides the column anyway. ## Anatomy 1. The top bar, on small screens only: the button that opens the drawer, and the mark. 2. The column: the mark and the fold button above lg; the navigation landmark, with the home link, then one section per group of links, each with an icon, a title and its list; the foot with the address, the theme switch, the link out and the sign-out form. 3. Folded, the column is a rail of icons. A section becomes a button that opens a panel beside the rail, holding the same list. 4. Under lg, the column is a drawer, closed at first, with a backdrop button that closes it. 5. The status region, outside the main landmark, that says a page is loading. 6. The banner slot, above the content area. 7. The content area, the main landmark, busy while a navigation loads. ## States The column open, the rail with one panel open, the closed section, and the busy content area. ## Accessibility contract - Role and name: `nav` named by `navLabel`; the content area is `main` with `id="main-content"` and `tabindex="-1"` on a page, a `div` inside a docs page; the status region is a `p` with `role="status"`, present and empty from the first render. - Keyboard, focus: the skip link's target is the content area; the fold button and the rail buttons are buttons with `aria-expanded`; Escape on a rail panel closes it and returns the focus to its button; Escape closes the drawer; a click outside a panel closes it; a navigation closes it, including the browser's back. The closed drawer is `visibility: hidden`, so its links leave the tab order. - Live regions: the status region says "Chargement de la page …" once the wait passes the page's threshold; the shell writes what `announcement` says and nothing else. The page empties it, since SvelteKit's announcer already says the title of the page that arrived. - The current link carries `aria-current="page"` and a line on the left; a finished audit's check carries a hidden "Terminé". - The section titles are paragraphs tied to their lists by `aria-labelledby`, never headings. - Reduced motion: the column's slide and the chevron's turn are cut. - Print: the bar, the column and the backdrop leave the paper; the content area drops its gutters and its maximum width, since a printed document sets its own margins. ## Content rules French. Section labels are nouns ("Audits récents", "Organisations", "Paramètres"); the home link is "Tableau de bord"; the rail names of a closed section say the section and the gesture ("Administration : activer la vérification en deux étapes"). The closed sentence names the one link that opens the section, in the sentence's own words. ## Do and don't - Do give every section an icon, learnt while the word is still next to it. - Do keep the sign-out a form that posts, never a link. - Do write the busy state from a navigation helper with a threshold, never from every navigation. - Do not put a heading in the column. - Do not claim a menu role on the rail's panels: they are disclosures. - Do not put the status region inside the content area. ## Examples ```svelte (document.cookie = `barre_repliee=${collapsed ? '1' : '0'}; path=/; max-age=31536000; samesite=lax`)} busy={navigation.busy} announcement={navigation.announcement} email={data.user.email} logoutAction="/auth/logout" logoutEnhance={(form) => enhance(form, occupe())} > {#snippet banner()}…{/snippet} {@render children()} ``` ## Surfaces - App: the lab layout, every page under `/lab`. ## Related BusyRegion, SkipLink, Banner, IconButton, ThemeSwitch, Logo. ## Decisions - 2026-09-25: the rail's panels are disclosures, not menus, as the lab layout had decided: a menu role promises arrow keys, Home, End and type-ahead, and a promise half kept is worse than none. - 2026-09-25: the fold is one layout, not two. Folding only narrows the column and cuts the text; every element's middle stays on the same vertical line, so nothing jumps. The two measured margins that centre the mark and the button on the rail are kept as written. - 2026-09-25: the panel is held by the strong line, where the layout drew a shadow; the direction has no elevation. - 2026-09-25: the sign-out form takes an action from the page rather than a submit handler, so SvelteKit's `enhance` and the page's busy helper compose as they did. - 2026-09-27: a navigation closes the drawer as it closes a panel, and Escape gives the focus back to the menu button. The shell stays mounted across the lab's routes, and on a phone a link tapped in the drawer loaded the page under the drawer and its veil. # AuthShell --- name: AuthShell slug: auth-shell level: view status: stable surfaces: [app] since: 2026-09 replaces: - "apps/web: the centred column written by hand on the six auth and pairing pages" 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." patterns: [form-with-error-summary] related: [field, button, alert, link, logo, theme-switch] --- ## Purpose The shell of a page that holds one thing: sign in, ask for access, choose a password, type a code, pair the extension. One column, centred in the height: the mark as a way out, the h1, a lead with its links, the card on the surface holding the form, the footer for the way back. ## When to use, when not Use it for every page a visitor sees before the lab, and for the extension pairing page. Not inside the lab, which has its own shell; not for a dialog. ## Anatomy 1. The top row: the mark, a link to the site or the lab, and the theme switch across from it. 2. The title. 3. The lead, with its links. 4. The card, holding the messages and the form. 5. The footer. ## States The sign-in page, and a page holding a message. ## Accessibility contract - Role and name: `main` with `id="main-content"` and `tabindex="-1"` on a page; the h1 is the page's name. Inside a docs page, a div at level 2. - Keyboard, focus: the skip link's target; then the mark, the theme switch, the lead's links, the form, the footer, in reading order. - The theme switch's radio group takes `themeName`, `theme-auth` by default; two shells on one page give two names, or the browser merges them into one group. - Live regions: none of the shell's; the page's Alerts carry theirs. ## Content rules French. The title is the page's name as a noun phrase ("Connexion", "Nouveau mot de passe"); the lead is one sentence with its link; the footer is the way back. ## Do and don't - Do put the Alerts inside the card, above the form. - Do keep `logoHref` on the lab for a signed-in user. - Do not nest a second main; render as a div inside a page that has one. ## Examples ```svelte {#snippet lead()}

Pas encore de compte ? Créer un compte

{/snippet}
…
{#snippet footer()}

Retour à rgaa.dev

{/snippet}
``` ## Surfaces - App: sign in, register, forgotten password, reset, two-factor code, extension pairing. ## Related Field, Button, Alert, Link, Logo, ThemeSwitch. ## Decisions - 2026-09-24: a plain surface for the card rather than the Card component, which is a summary that links somewhere; this one holds a form. - 2026-09-26: the theme switch is on the shell, across from the mark, as the plan's section 3b asks of every surface; the group name is a prop so the design site's four copies of a story stay four groups. # PanelShell --- name: PanelShell slug: panel-shell level: view status: stable surfaces: [extension] since: 2026-09 replaces: - "apps/extension: the column the string renderer wrote in view.ts, with its three scroll containers" 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." patterns: [panel-shell] related: [wizard-frame, theme-list, live-region, banner, status-counters] --- ## Purpose The column of the extension's side panel: a header that stays, a content landmark that scrolls, a foot that stays, and a banner above them when something concerns the page. One shell for every state of the panel, hidden with the attribute while a wizard runs over it. ## When to use, when not Use it as the frame of the panel's audit screen, and of any screen that has a header and a scrolling list. Not for a wizard, which has WizardFrame; not for the welcome and access screens, which are one column of text. ## Anatomy 1. The banner, above everything, for what concerns the page. 2. The header: the progress, the title row with the theme switch, the counters, the page switcher. 3. The content, the panel's main landmark, which scrolls. 4. The foot, with the keys. ## States Bare, full with its id and the progress strip, and hidden under a wizard. ## Accessibility contract - Role and name: `main` named by `label` in the panel, with `id="main-content"`; a `region` with the same name inside a page that already has a main. Both take `tabindex="0"`: the scrolling area must be reachable by Tab when it holds only text (criterion 7.3). The header is `header`, the foot is `footer`. - Keyboard, focus: the content scrolls with the keyboard since it holds focusable controls. Hidden, the whole shell leaves the tab order, and the focus a wizard took comes back to the control that opened it when the shell shows again. - Live regions: none of the shell's. The panel's root owns the status and alert regions, present from the first render, above every screen. ## Content rules French. The label names what the content holds ("Audit de la page"); the foot lists the keys the wizards answer to, in one line. ## Do and don't - Do put the two live regions before the shell, in the panel's root, not in a header snippet. - Do keep the header short: at 216 pixels every row of it costs the list a line. - Do not scroll the header or the foot; only the content moves. - Do give the column its `id` when a click delegate listens on it, rather than wrapping the shell in an element of your own. ## Examples ```svelte