Skip to content
design system
Thème d’affichage, actuellement Auto

packages/design/CHANGELOG.md

Changelog

All notable changes to @rgaa-dev/design. The format follows Keep a Changelog. Dates are the day the change landed on main.

Unreleased

Added

  • Package scaffolding for the design-system plan of 2026-09-24: test and TypeScript configuration, the manifest schema, this changelog and a README that states the contract.
  • The token source, tokens/primitives.json, tokens/semantic.json and tokens/contrast.json (W3C Design Tokens), and the generator scripts/build.mjs that writes styles/tokens.css, styles/tokens.scoped.css, tokens.ts and tailwind-preset.js from it. The generator measures every documented colour pair in the light, dark and print sets with the audit engine’s own formula (a test proves the two agree) and refuses to write when a pair drops below its floor; the ratios in the CSS comments are the measured ones.
  • New roles, taken from the copies that had grown them or from what every surface improvised: attention, derogation, hover, canvas, canvas-ink, backdrop, focus (with width and offset), the spacing scale, z-index, durations and easings, line heights, letter spacings, weights, icon and control sizes, the pointer target floor, containers, measures, breakpoints. A print set (white paper, whatever the theme) and a forced-colours set (system keywords).
  • The [data-theme] selectors are element-level, so a wrapper can show the other set inside a page, and color-scheme follows the same selectors.

Added

  • theme/theme.ts and theme/boot.js: one theme mechanism for the three surfaces. The choice (auto, light, dark) lives under rgaa-theme and is applied as data-theme on the root; auto is the absence of both. The boot script is the pre-paint code the SvelteKit shells inline and the extension panel loads as a file; a test holds the inline copies equal to it. The theme picker component reads and writes through the module.

  • fonts/fonts.css: the two families as woff2-only faces from the @fontsource packages, which are now dependencies of this package; Lexend leaves the site, nothing used it. The extension panel carries the fonts too (about 80 KB) where it showed the system stack.

  • Guard rails: the app and the site import fonts, tokens, preflight, base, components and utilities in that order (consumers.test.ts); the base layer stays under a size and selector budget with no !important, no @apply, no @layer (css-budget.test.ts); no em dash in the package outside the components still to rewrite (no-em-dash.test.ts).

  • One folder per component, four files in it: the implementation, a doc (Name.md, front matter with level, status, surfaces, the RGAA criteria it satisfies and why, then eleven sections in a fixed order), stories (Name.stories.svelte, one exported snippet per state that changes markup or ARIA, with notes for the non-obvious ones) and a test that mounts every story and asserts the accessibility contract. The 18 original components have theirs, on their current API, as draft.

  • manifest.json and llms.txt, generated by scripts/build.mjs from the folders: name, level, status, surfaces, props read from each interface Props (type, default, JSDoc), snippets, stories, rl- classes, criteria, what the component replaces. The freshness test covers them like the token outputs.

  • Card draws its hairline as an outline and CardGrid paints no background: the line colour behind the grid used to show through the empty cells of an incomplete row, visible on the public site wherever five cards sat in three columns.

  • Dialog and ConfirmDialog: the native dialog element with showModal, named by its heading, a labelled close button last in the DOM so the body takes the initial focus, a bindable open, and the closing button’s value handed back through onclose; the confirmation composes it with the cancel button first and focused and a required verb on the confirm button. LiveRegion: the region present and empty from the first render whose text the page changes, status or alert from the tone. SkipLink: the first stop of the page, on the component instead of the base layer’s skip-link class, which leaves with its three raw uses (the lab layout, the site and the design site; the site’s main takes tabindex="-1" so the target really takes the focus). Kbd: a key or a combination, nested as the element is meant to be.

  • Disclosure, Accordion and Tabs, from the site’s accordéon and onglets demos, which stay as teaching material. Disclosure is a button inside a heading at the level the page gives, aria-expanded for the state and hidden on the folded panel; Accordion stacks them, independent by default and exclusive on request, with the panel content as a snippet; Tabs declares the tab list, the tabs and the panels with one Tab stop for the list, the arrows, Home and End moving and selecting, and a panel that takes the focus.

  • ErrorSummary and the form controls Checkbox, RadioGroup, Switch and Select, from the site’s demos, which stay as teaching material. The summary lists a form’s errors under a heading that counts them and takes the focus at submission, one link per field, no live role; the controls are the native elements with the label, the hint and the error tied as Field ties them, the radios in a fieldset with a legend and one name, the switch a button with the switch role and aria-checked written from the state, drawn as a track and a thumb (the native checkbox version failed the engine’s rule 4e8ab6 on the design site), the select with its empty option. Field’s value has no fallback any more, for the same reason as Dialog’s open.

  • IconButton, Breadcrumb, Chip, LevelBadge, EmptyState and Table. The icon button requires its name and hides its drawing, with aria-pressed for a toggle; the breadcrumb is a named landmark holding a list, separators hidden and the last step current; the chip is the base layer’s with Alert’s tones; the level badge reads “Niveau AA”; the empty state is a heading, a sentence and an action; the table is the hand-written one held to a caption, a named scroll region and the cell classes the base layer’s tableau rules carried.

  • Menu, Toast, Pagination, ProgressBar, Tooltip and SearchField, from the site’s demos and the app’s copies, which stay or leave as their docs say. The menu holds commands with the ARIA pattern’s keyboard and one Tab stop; the toast is one live region present from the first render, with no timer; the pagination is links in a named landmark, the bounds gone rather than greyed, the truncation honest, the count a status region; the progress bar is the native element; the tooltip is the trigger’s description, dismissable and hoverable; the search field a landmark with a hidden label and a button with a word. A more drawing joins the icon set for the menu’s three dots.

  • Banner, StatGroup, PageHeader and DefinitionList, from the app’s beta banner, stat card, page heads and property blocks. The banner is a named aside with no live role, dismissed and remembered under a key; the stat group a named list of Stats, which gain a tone; the page header the h1 with its eyebrow, lead and actions; the definition list a real dl in columns.

  • AuthShell, the column of the six pages a visitor sees before the lab: the mark, the h1, a lead, the card, the footer, as the main landmark the skip link targets. The six pages of the app are on it, with Field, Button, Alert and Link; Field gains minlength, maxlength, inputmode, autofocus and code for the verification code.

  • AppShell, the frame of every page under the lab: the column of navigation with its sections, the rail of icons it folds to with a disclosure panel per section, the drawer it becomes on a small screen, the foot with the address, the theme switch and the sign-out form, the status region outside the main landmark and the content area as a BusyRegion. The lab layout is on it, and on Banner for the beta announcement; zone-occupee leaves the base layer with its last raw use, and the app’s BandeauBeta leaves with it.

  • StatusBreakdown, the split of a set of criteria by status: a named list of counts with their StatusChips, and a bar under it, hidden from assistive technology, whose segments are the counts against the set’s total. The audit page and the audited page each drew it with two loops and three colours.

  • CodeBlock, Figure and ReportDocument. The code block is a value the reader copies, inline or as a named block, wrapping anywhere; the figure is a picture with its caption, the whole picture a link when it opens large, its name saying so; the report document is the frame of a document read on screen and handed over on paper: the cover, the rights page and the table of contents that only paper shows, the printed header, the sticky summary, and the page rules (@page, breaks, orphans, the address after a link) that lived in the app’s stylesheet. Heading takes an id and a base size for a fourth level. With the app’s print block gone, AppShell, Breadcrumb and SkipLink hide themselves on paper, and the content area drops its gutters; a named code block is a group, since a bare pre cannot take a name (criterion 7.1).

  • The wizard skeleton of the extension’s panel, thirteen components written from the eight wizards’ strings: WizardFrame (the region, its header, its scrolling body and its footer), Question (the criterion said as such before the sentence), AnswerButtons (a group named by the question, aria-pressed, the key printed), CommentField, WizardFooter (Terminer replaces Suivant on the last element), CriteriaRecap, StatusCounters, AutoVerdict (the status, who said it, the findings with a Montrer button), Derogation (the two natures, the required justification), JumpList (closed by default, out of the letter shortcuts), ResumeBanner, ReviewFlag and GlossaryTerm (a button, never a tooltip).

  • WizardFrame takes a tools snippet in its header, between the progress and the close button, for the veil switch and the inspector the extension’s wizards carry.

  • AutoVerdict takes the auditor as a third source, “d’après vos réponses”, for a criterion that is the sum of answers given element by element on a walk, as 7.1 and 7.3 are in the script wizard; the wizard’s own sentence then says where the answers are changed.

  • Phase C of the plan, the fourteen components the ports still lacked. For the extension’s panel: PanelShell (the column: a header that stays, the content as the main landmark that scrolls, a foot that stays, hidden with the attribute under a wizard), ThemeList (the themes with their tally, their chips, the folding button and the wizard button whose word follows the work, prop-driven with callbacks and no store), CriterionRow (the code, the title, the statuses, the note, the criterion’s own wizard, the comment, and a slot under the row), StatusToggle (C, NC, NA as buttons with aria-pressed in a group named for the criterion, the symbol of core next to the letter, never NT), EvidenceBlock (the elements at fault with their capture or the way to take it, the refused ones counted apart, the pointing instruction with its keys) and PageSwitcher (the audit’s pages in a Dialog, each a link with its progress, the current one said, delete and add as callbacks). For every surface: DisabledReason (a control that refuses says why, the snippet spreads disabled and aria-describedby; Button keeps its reason shorthand), Text (a run of text with a size and a tone), PasswordField (Field with a show button that keeps its name, its focus and the value, aria-pressed for the state), OTPField (one input, the numeric keyboard, the one-time-code autofill, the code face), FilterGroup (a group named by a visible label of buttons with aria-pressed, exclusive or not, applied at once), Popover (a non-modal named dialog anchored to a button, Escape returns the focus, Tab out closes), PrevNext (the pair between two pages, links or buttons, a missing side absent) and PrintPage (one part of a document that starts a sheet or refuses to be cut, with its screen-only and paper-only slots).

  • styles/print.css, exported as @rgaa-dev/design/print.css: the page’s size and margins, the breaks, the headings kept with their text, the tables whose header repeats and the address after a link, moved out of ReportDocument’s style and shared with PrintPage. A surface that prints a document loads it after base.css; rl-report__part and rl-report__keep keep working as aliases.

  • tooling/rehype-tables.js, exported as @rgaa-dev/design/tooling/rehype-tables.js: the mdsvex plugin that writes scope="col" and the Tableau : caption on Markdown tables, moved from the site’s src/lib/serveur/rehype-tableaux.js with its comments in English; the site’s config and its test import it from here.

  • AuthShell carries the theme switch across from the mark, with themeName for its radio group so two shells on one page stay two groups (plan, section 3b).

  • Onboarding and DangerZone, two organisms the web app wrote by hand. Onboarding is the first-steps block of an empty dashboard: a section named by its heading, an intro, the steps as an ordered list with the rank drawn and hidden, a step done told by the check drawing and the word “Fait”, a step’s own action as a Link or a Button, the call to action, a foot under a line, and an optional dismiss button with its words. DangerZone is the part of a settings page whose actions cannot be undone: a section named by its heading, framed by the strong structural line rather than a status ink, the explanation, and one destructive button per action, each asking through ConfirmDialog before onconfirm runs. The dashboard’s welcome card and its etapes list, and the settings page’s zone-de-suppression, leave with them.

  • Text resets its margin at no specificity (:where), so the margin utility its class documents is no longer zeroed by the scoped rule.

Changed

  • The no-em-dash guard checks every file of the package: the exemption for the component implementations and the manifest is closed, since nothing needed it any more.
  • The docs guard reads an acronym in a component name as one word: OTPField is otp-field.
  • Figure’s stories draw their picture with the light set’s values from the generated token module instead of three hex literals; DataTable’s example is on the English props.
  • The panel’s components take what the extension’s own copies still did by hand, so the copies leave. StatusToggle, CriterionRow, ThemeList and EvidenceBlock take hooks: data-* attributes (the DataHooks type admits no other key) a consumer hangs on the row, the statuses, the note, the wizards, the folding button, the lists and the capture, pointing and cancel buttons, for its click delegate, its focus return and its walks; the package names none of the extension’s actions. ThemeList’s open is bindable and followed, a theme takes wizard: false, the engine’s chip draws the settings icon before its figure and the non-conformities’ chip keeps the codes in its tooltip. CriterionRow’s own wizard takes busy. EvidenceBlock’s items take detached (a capture kept of an element no longer detected, with no button), busy and key, and the block a show snippet for the consumer’s own Montrer control. PanelShell takes an id for its column. ProgressBar takes strip, the thin line across a shell’s edge with its label hidden. PageSwitcher, ThemeList and EvidenceBlock write the typographic apostrophe.

Fixed

  • CommentField lost the space before “(facultatif)” to whitespace trimming; AutoVerdict opened one before its comma, the flex gap between two text nodes. Both sentences read as written.
  • Toast gains passthrough: clicks go through the messages, for a corner laid over controls the user is about to press, as the extension panel’s wizard bar; without a close button, since one that cannot be pressed would be a lie.
  • Button gains the link variant: an action written in a sentence (“Renommer”, “Modifier”), underlined like Link and still a button. The app’s last five raw base-class uses take it, and its guard forbids the class.
  • rl-prose in the base layer: the typography of a body compiled from Markdown (an article, a sheet, a glossary entry), which the site’s article layout held as thirty :global rules and its glossary page as nine more. Headings in the display family and weight, links underlined except the ones dressed as buttons, code that folds rather than scrolls, tables with a visible caption.
  • A story that writes an id or a radio group name takes a scope argument and suffixes it (Field, ScrollRegion, BusyRegion, ThemeSwitch): the design site renders every story four times on one page, and four copies of one id broke the label associations and merged the theme switches into one group. The test harness passes no scope, so a story still mounts as it always did.
  • Guard rails closed: the two Tailwind configs no longer scan the package’s components, the web app’s socle test goes (its checks live in the package), the apps’ base-class overrides are declared in consumers.test.ts, the extension lists every hex literal it still writes with a reason in couleurs-autorisees.json and a test that only lets the list shrink, and CI fails when a generated file is stale.
  • Eight navigation drawings in the icon set (menu, chevron-left, home, document, building, settings, shield-check, logout), the lab layout’s own paths moved into it; the lab layout and the site header draw their icons with the Icon component and carry no inline SVG.
  • Link and BusyRegion, the two atoms the base layer used to carry as raw classes: the underlined link in running text, and the page-scale busy zone with aria-busy and the fixed ring. link-underline and zone-occupee stay in the base layer for the pages that still write them by hand.
  • Icon, the first component on the target shape: fourteen stroke drawings in icons/svg, their paths generated into icons/paths.ts, a component that is always hidden from assistive technology and sized by the icon tokens, statusIcon pairing each audit status with its drawing, and svgIcon() from @rgaa-dev/design/icons for the extension’s overlays, which write markup as a string. A test holds every source to paths on the 24-unit grid with no colour of its own.
  • The base layer named --rl-ease-default where the generator writes --rl-ease, and every transition in it was silently dropped; fixed, and a test now refuses a var() on a name the tokens do not declare.
  • Three more guard rails: every folder holds exactly its four files and the index exports it from there (component-folder.test.ts); a component writes rl- classes only, no Tailwind utility, no literal colour, no dark: variant, with a named list of the components still on utilities that can only shrink (component-css.test.ts); every doc has the front matter, the section order, existing criteria and related components, dated decisions and notes on real stories (docs.test.ts, criteria checked against @rgaa-dev/rgaa).

Changed

  • Button takes variant (primary, secondary, ghost, destructive), size (sm, md, lg), fullWidth, busy, and a new reason, which renders the button unavailable with its reason next to it, tied by aria-describedby. The four variants, the unavailable and busy states and the ring are rl-button rules on the tokens; the base layer keeps btn-* for the pages that still write the classes by hand. With it, no component of the package writes a utility class any more, and the allow-list of component-css.test.ts is empty.
  • Section takes rhythm (lead, default, tight) and rule; the wide container, the three rhythms and the top rule are rl-section rules on the spacing and line tokens, with no style attribute.
  • Eyebrow, Prose and Heading keep their English props and take scoped rules on the tokens: rl-eyebrow, rl-paragraph (not rl-prose, which the base layer reserves for article typography) and rl-heading with a modifier per size. Heading’s measure is derived, which clears its three Svelte warnings.
  • Card takes listItem and CardGrid takes columns and list; the grid, its breakpoints and the card’s surface and padding are rl-card-grid and rl-card rules on the tokens, and the hairline between cards is the border token over the line colour.
  • ZoneDefilante is ScrollRegion, with label, labelledBy and framed; the frame is a modifier class on the tokens instead of a style attribute, and the overflow rule is the component’s own rather than a utility.
  • Alert takes tone (info, success, warning, error) and title; the drawing comes from the icon set, the ink of each tone is a modifier class on the tokens, and the warning tone takes the attention ink the tokens define for it where it used to borrow the plain ink.
  • StatusChip takes status and short; the symbol character gives way to the status drawing from the icon set, the ink and the dashed line of “non testé” are modifier classes on the tokens, and the chip carries no style attribute. Documents and running text keep the characters from core.
  • Logo takes height and markOnly; the word’s size derives from the height through one custom property, and the rest is rl-logo rules on the tokens.
  • Stat takes value, secondaryValue and label; its sizes, weights and trackings are rl-stat rules on the tokens.
  • Encadre is Callout, with rule (strong, subtle), status (NC, C, the abbreviations of core) and aside; the line is drawn by modifier classes on the tokens instead of a style computed once at mount, so a prop that changes now redraws.
  • Field takes required, hint, error, multiline and rows; its rules are rl-field on the tokens, the error’s symbol is the cross icon, the described-by ids end in -hint and -error, and the control’s height comes from its padding and font rather than a pixel value.
  • DataTable takes caption, captionHidden, columns (key, label, numeric), rows, rowKey, cell and empty; its scroll wrapper is the scroll region component instead of a second copy of its markup, and its cells are rl-data-table rules on the tokens.
  • TauxDeConformite is ConformityRate, props rate, unavailableReason, suffix, decimals; the band’s ink is a modifier class on the tokens instead of an inline style, and the figure is written with the French decimal comma, which the doc promised and the component did not do.
  • Riche is InlineMarkup, its prop text; the code span is styled in a scoped rule on the tokens instead of an inline style and two utilities.
  • ChoixTheme is ThemeSwitch, on rl-theme-switch classes with the focus and weight tokens, a forced-colours rule for the checked option, and a legend that says “actuellement” without a dash. The site’s mobile menu gives its switch its own group name, so the two on the page stop sharing one.
  • The grid relay is components/context.ts with openGrid, closeGrid and gridIsList (was contexte.ts, French names). ChoixTheme takes a name for its radio group, theme by default as before, so two pickers on one page stop sharing a group. Button and Eyebrow declare their props as an interface Props like the others, which the manifest generator requires. The package typecheck runs svelte-check, since tsc cannot see the exports of a stories file’s module script.
  • base.css is plain CSS under styles/, no Tailwind @apply or @layer, so the extension consumes it; the body defaults and the focus ring moved into it from the two app sheets (the site’s body line-height goes from 1.65 to 1.6, and the focused element is no longer rounded). The base layer sits after Tailwind’s preflight and before its utilities in both apps, through @import 'tailwindcss/...' in place of the directives.
  • The Tailwind preset replaces the default colours, radii, shadows and weights instead of extending them: rounded-lg, shadow-md and text-gray-500 stop existing. font-medium and font-semibold are kept as names mapped to 400 and 700, the weights the browser already picked for them with a font that ships only those two. The apps’ own Tailwind configs keep nothing but the preset, the content globs and the forms plugin; the site’s typography plugin and its theme are gone.
  • The extension panel is on the shared theme: auto by default (it opened light before), a choice made under its old key is moved under the shared one on first read, the toggle cycles the three states and says where it stands, the shell carries no theme class any more.
  • The extension consumes the package. Its two stylesheets are bundled by esbuild and import the tokens: the panel sheet the document-level tokens.css (on :root and data-theme, as the site and the app), the injected sheet the container-level tokens.scoped.css (on .rl-scope, never on an audited page’s root). The 181-line hand copy at the top of reader-view.css is gone; every element the extension draws into a page carries the scope through one helper. The AMO sources archive includes the package and hashes the built sheets too.
  • The two consumers that embed their style now read generated modules: the report and declaration documents (packages/core/src/document-tokens.generated.ts, light set plus the print set) and the emails (apps/web/src/lib/server/courriels/colours.generated.ts, light and dark hex). The hand copies and the two tests that compared them to tokens.css are gone. The web app’s own print block is gone too: the print set comes from the generated sheet, on the selectors the block used to name.
  • tokens.css moved to styles/tokens.css (the package export is unchanged). Every light and dark value is identical to 0.1.0, held by a test against a fixture of the old file, with one exception: the display weight is written as 700 instead of 750, which is what the font has and what every browser rendered.

0.1.0

The state before the plan: tokens.css (the “Encre chaude” direction, light and dark sets with measured contrast ratios), base.css (a class contract written with Tailwind @apply), tailwind-preset.js, and 18 Svelte 5 components with a mixed French and English API, consumed by the site and the web app only.