Beta Gouv (DSFR)
The genuine design.md file published by Beta Gouv (DSFR).
Visit website
Last updated 07/22/2026
#0v: 0px
#0-5v: 0.125rem
#1v: 0.25rem
#1-5v: 0.375rem
#2v: 0.5rem
#none: 0px
#sm: 0.25rem
#pill: 9999px
## Overview
The **Système de Design de l'État** (DSFR) is the official design system of the French Republic — the visual identity, component library, and accessibility framework that every `.gouv.fr` digital service is required to follow under [circulaire n°6411-SG](https://www.systeme-de-design.gouv.fr/version-courante/fr/a-propos/articles-et-actualites/circulaire-d-application-du-7-juillet-2023). It exists to *unify* State websites, to *improve the citizen experience*, and to *guarantee accessibility*.
Visually, the DSFR is **architectural and journalistic**: square corners by default, generous whitespace without ornament, a workhorse sans-serif (Marianne) doing 90% of the typographic load, and a single saturated brand colour — *Bleu France* `#000091` — used as a deliberate signal of officiality and primary action. Decoration is restrained; the State does not fashion-shop. Type and information hierarchy carry the design.
> **Accessibility baseline.** All components target the **RGAA** (*Référentiel Général d'Amélioration de l'Accessibilité*) — France's legal accessibility standard, broadly aligned with **WCAG 2.1 AA**. Concretely:
>
> - Body text contrast ≥ **4.5:1**; large text and UI components ≥ **3:1**.
> - **Visible focus** indicator on every interactive element (the DSFR uses a 2px outer halo in `focus-ring` `#0a76f6`).
> - Touch targets **≥ 44×44 px**.
> - Colour is never the sole carrier of information.
> **Theming.** The DSFR supports a light theme and a dark theme, switched via the `data-fr-scheme` attribute on `<html>` (`"system" | "light" | "dark"`). This `DESIGN.md` encodes the **light theme** in the YAML token graph. See [Colors → Decision tokens](#decision-tokens) below for the dark-theme mappings.
> **Brand & legal scope.** The Marianne wordmark, the *République Française* logo, and the use of *Bleu France* as a State mark are reserved by law for official .gouv.fr services. This file is a derived, machine-readable description; for production .gouv.fr builds always use the canonical [`@gouvfr/dsfr`](https://www.npmjs.com/package/@gouvfr/dsfr) npm package.
## Colors
The DSFR palette is rooted in the French State's graphic charter. Every colour is a **design token** — a transverse name shared across Sketch, Figma, SCSS variables and CSS custom properties. Tokens come in two flavours: **option tokens** (raw palette values) and **decision tokens** (the names UI code references). A well-implemented DSFR site never references option tokens directly.
### Marque de l'État (brand colours)
The brand is built on two anchors — **Bleu France** and **Rouge Marianne** — with white absorbed into the neutrals.
- **Bleu France** `#000091` — the State's primary signal. Reserved for **primary actions** (CTA buttons), **active states**, and **brand surfaces** (the *bloc marque*). Hover `#1212ff`, active `#2323ff`. Light variants step down through `#cacafb` → `#e3e3fd` → `#ececfe` → `#f5f5fe` for low-contrast surfaces (selected list rows, badges, action backgrounds).
- **Rouge Marianne** `#e1000f` — used **sparingly**, attached to the Marianne identity (logo lockups, emphasis in editorial contexts). Hover `#f93f42`, active `#f95a5c`. *Not* a generic destructive-action red — that role belongs to `error` (`#ce0500`).
### Neutrals
A 10-step grey scale handles backgrounds, text, borders, and dividers:
| Role | Light theme | Token |
|------|-------------|-------|
| Page background | `#ffffff` | `grey-1000-50` |
| Alt background | `#f6f6f6` | `grey-975-100` |
| Contrast surface (inputs) | `#eeeeee` | `grey-950-150` |
| Default border | `#dddddd` | `grey-900-175` |
| Mention / placeholder text | `#666666` | `grey-425-625` |
| Body text | `#161616` | `grey-50-1000` |
### System (functional) colours
Reserved for **system feedback only** — never decorative.
| Role | Hex (`-425-625`) | Tinted background (`-950-100`) | Alert background (`-975-75`) |
|------|------------------|-------------------------------|------------------------------|
| Success | `#18753c` | `#b8fec9` | `#dffee6` |
| Warning | `#b34000` | `#ffe9e6` | `#fff4f3` |
| Error | `#ce0500` | `#ffe9e9` | `#fff4f4` |
| Info | `#0063cb` | `#e8edff` | `#f4f6ff` |
Each interactive shade (`-425-625` and `-950-100`) ships explicit `-hover` and `-active` states; `-975-75` does not (alerts are not actionable surfaces).
### Illustrative accents
The DSFR ships **12 illustrative families** for complementary editorial use — *tilleul-verveine*, *bourgeon*, *émeraude*, *menthe*, *archipel*, *écume*, *cumulus*, *glycine*, *macaron*, *tuile*, *tournesol*, *moutarde*, *terre-battue*, *café-crème*, *caramel*, *opéra*, *gris-galet*. Rules:
1. **One family per page**, never combined.
2. **Never** for actions or system states (those use Bleu France and the system colours, respectively).
3. Used to add hierarchy, theme a section, or distinguish content categories.
(In the YAML graph these tokens are intentionally **orphaned** — the linter will emit `orphaned-tokens` warnings, which is correct.)
### Decision tokens
UI code must reference **decision tokens**, not option tokens directly. Decision tokens encode *intent* (e.g. "the background of a primary button", "the text colour for a disabled field") rather than a specific shade. They abstract over the light/dark theme split — each decision token references a paired option token whose two values resolve through `data-fr-scheme`.
#### Naming convention
Decision token names follow the canonical DSFR format `<role>-<level>-<family>`:
- **Role** — what kind of style: `background`, `text`, `border`, `artwork`.
- **Level** — the contextual purpose: `default` (the most generic value for the role), `alt` (a light alternative), `contrast` (a more saturated alternative), `action-high` (high-emphasis interactive surface, e.g. primary button), `action-low` (low-emphasis interactive, e.g. tonal button), `active` (currently-selected state), `plain` (saturated solid for status), `disabled`, `open` (expanded/disclosed state), `mention` (lower-priority text), `inverted` (text on a coloured background), `label`, `title`.
- **Family** — which palette the value belongs to: `grey` (neutral), `blue-france` (primary), the four system colours `info`/`success`/`warning`/`error`, or one of the seventeen illustrative-accent families (`green-tilleul-verveine`, `orange-terre-battue`, `pink-macaron`, …).
Examples:
- `background-action-high-blue-france` — the fill of a primary button.
- `border-plain-success` — the solid border of a "success" alert.
- `text-disabled-grey` — body text inside a disabled control.
- `background-disabled-grey` — the surface of a disabled control.
- `text-inverted-grey` — text rendered on top of a saturated coloured surface.
Hover and active states extend the base name with `-hover` / `-active` suffixes, e.g. `background-action-high-blue-france-hover`.
#### Theme handling
A decision token has a single name that resolves to different colour values in light vs dark mode. The translation lives one level down — every decision token references a **paired option token** like `grey-1000-50` whose first value (`#ffffff`) is the light-theme colour and whose second (`#161616`) is the dark-theme colour. Switching `data-fr-scheme="dark"` on `<html>` flips the resolution; the decision token's name never changes.
#### Most-used tokens
| Decision token | Light | Dark | Use |
|---|---|---|---|
| `border-default-grey` | `#dddddd` | `#353535` | Default border (177× — the most-used decision token in DSFR) |
| `border-plain-error` | `#ce0500` | `#ff5655` | Error alerts/borders (95×) |
| `border-plain-success` | `#18753c` | `#27a658` | Success alerts/borders (93×) |
| `border-action-high-blue-france` | `#000091` | `#8585f6` | Primary-button border (83×) |
| `background-disabled-grey` | `#e5e5e5` | `#2a2a2a` | Disabled surfaces (73×) |
| `text-disabled-grey` | `#929292` | `#666666` | Disabled text (59×) |
| `border-active-blue-france` | `#000091` | `#8585f6` | Selected/focused element border (54×) |
| `background-default-grey` | `#ffffff` | `#161616` | Default page surface (48× incl. variants) |
| `background-action-high-blue-france` | `#000091` | `#8585f6` | Primary-button fill (47× incl. variants) |
| `text-action-high-blue-france` | `#000091` | `#8585f6` | Link text, primary-action text (34×) |
| `background-contrast-grey` | `#eeeeee` | `#242424` | Secondary panels, contrast cards (33× incl. variants) |
| `border-plain-grey` | `#3a3a3a` | `#cecece` | Solid neutral borders (26×) |
| `border-plain-info` | `#0063cb` | `#518fff` | Info alerts (23×) |
| `border-contrast-grey` | `#929292` | `#666666` | Contrast-card borders (22×) |
| `text-mention-grey` | `#666666` | `#929292` | Captions, metadata, secondary text (19×) |
| `text-title-grey` | `#161616` | `#ffffff` | Headings on default surfaces (19×) |
| `border-disabled-grey` | `#e5e5e5` | `#2a2a2a` | Disabled-control borders (19×) |
| `focus-ring` | `#0a76f6` | `#4ea7ff` | Focus indicator (theme-stable) |
The full set (~78 Tier 1+2 decision tokens covering 73% of all references in DSFR component CSS) is encoded in the YAML front matter. To regenerate the YAML block from the canonical DSFR sources, run `python3 scripts/build-decisions.py`.
> **Critical rule.** Don't apply Bleu France to non-interactive titles. The DSFR reserves it for *clickable* elements; using it on a heading creates ambiguity with links and buttons.
## Typography
### Faces
- **Marianne** (primary, sans-serif). The State's bespoke typeface. Use weights `light` (300), `regular` (400), `bold` (700), each with italics. **Fallback**: Arial.
- **Spectral** (secondary, serif). Weights `regular` (400) and `extra-bold` (800). Reserved for *secondary, minor, or distinguishing* text — applied via the `fr-text--alt` class. **Fallback**: Georgia.
> The DSFR explicitly recommends *not* using Spectral as the primary face. It's an accent.
### Titres (semantic headings)
The `h1`…`h6` typography tokens are applied to native `<h1>`–`<h6>` elements, or via the `fr-h1`…`fr-h6` utility class on any heading element. The breakpoint is **48em (768px)**: below it the tokens fall back to their `*-mobile` companions automatically (the `fr-h*` classes carry the `@media` rules — emit them rather than custom markup whenever possible).
All title levels share the same colour bindings: `{colors.text-title-grey}` on light backgrounds, inverted on dark (the dark mode of the same decision token already resolves to `#fff`). Margin-bottom is `{spacing.title-margin}` (24px) for every level.
| Niveau | Usages | Balise | Desktop (taille / line) | Mobile (taille / line) |
|--------|--------|--------|-------------------------|------------------------|
| **H1** | Titre principal de la page : il ne peut y en avoir qu'un par page. | `<h1>` | 40 / 48 px | 32 / 40 px |
| **H2** | Second niveau de titre de section ou de paragraphes. Leur nombre n'est pas limité. | `<h2>` | 32 / 40 px | 28 / 36 px |
| **H3** | Troisième niveau de sous-titre. Leur nombre n'est pas limité. | `<h3>` | 28 / 36 px | 24 / 32 px |
| **H4** | Quatrième niveau de sous-titre. Leur nombre n'est pas limité. | `<h4>` | 24 / 32 px | 22 / 28 px |
| **H5** | Cinquième niveau de sous-titre. Leur nombre n'est pas limité. | `<h5>` | 22 / 28 px | 20 / 28 px |
| **H6** | Sixième et plus petit niveau de sous-titre. Leur nombre n'est pas limité. | `<h6>` | 20 / 28 px | 18 / 24 px |
### Titres alternatifs (display sizes)
For editorial / hero / marketing surfaces the DSFR provides five oversized title styles. They are *not* semantic — apply them via the `fr-display--{size}` utility class on a heading element of the appropriate semantic level (most often an `<h1>`). All share `{colors.text-title-grey}` and a margin-bottom of `{spacing.display-margin}` (32px).
| Niveau | Classe | Desktop (taille / line) | Mobile (taille / line) |
|--------|--------|-------------------------|------------------------|
| **Titre alternatif XL** | `fr-display--xl` | 80 / 88 px | 72 / 80 px |
| **Titre alternatif LG** | `fr-display--lg` | 72 / 80 px | 64 / 72 px |
| **Titre alternatif MD** | `fr-display--md` | 64 / 72 px | 56 / 64 px |
| **Titre alternatif SM** | `fr-display--sm` | 56 / 64 px | 48 / 56 px |
| **Titre alternatif XS** | `fr-display--xs` | 48 / 56 px | 40 / 48 px |
### Body, lead, label
Body styles are size-stable across breakpoints (no mobile companions).
| Token | Class | Size / line | Weight | Use |
|-------|-------|-------------|--------|-----|
| `body-xl` | `fr-text--xl`, `fr-text--lead` | 1.25rem / 2rem (20 / 32 px) | 400 | Lead paragraph |
| `body-lg` | `fr-text--lg` | 1.125rem / 1.75rem (18 / 28 px) | 400 | Emphasised paragraph |
| `body-md` | `fr-text--md` | 1rem / 1.5rem (16 / 24 px) | 400 | Default body |
| `body-sm` | `fr-text--sm` | 0.875rem / 1.5rem (14 / 24 px) | 400 | Metadata, secondary |
| `body-xs` | `fr-text--xs` | 0.75rem / 1.25rem (12 / 20 px) | 400 | Captions, microcopy |
| `label` | — | 0.875rem / 1.5rem (14 / 24 px) | 700 | Form labels, button text |
| `body-md-alt` | `fr-text--alt` (+`fr-text--md`) | 1rem / 1.5rem (16 / 24 px) | 400 | Editorial Spectral pull-quote |
| `body-sm-alt` | `fr-text--alt` (+`fr-text--sm`) | 0.875rem / 1.5rem (14 / 24 px) | 400 | Spectral footnote / source |
### Rules
- Body line-height: `1.5rem` (1.5× on `body-md`) for comfortable long-form reading.
- Use sentence case for headings; reserve uppercase for utility microcopy with expanded letter-spacing (e.g. `fr-link--icon` labels).
- **Never** colour a title in Bleu France except via the `text-title-blue-france` decision token, which is reserved for explicit identity expressions.
- For all-caps utility microcopy, set letter-spacing `0.06em`.
- Prefer the `fr-h*` / `fr-display--*` utility classes over raw font-size declarations — they carry the responsive `@media` rules so mobile sizing happens automatically.
## Layout
### Container & grid
- **Max content width**: `78rem` (1248 px). Wider hero / footer surfaces extend full-bleed but content wraps to this max.
- **Grid**: 12 columns. Gutters: 16px mobile / 24px tablet / 32px desktop.
- **Side padding**: 24px mobile / 40px desktop.
### Breakpoints
| Name | Range |
|------|-------|
| `xs` | < 576 px |
| `sm` | ≥ 576 px |
| `md` | ≥ 768 px |
| `lg` | ≥ 992 px |
| `xl` | ≥ 1200 px |
The DSFR class system uses suffixed modifiers (`fr-grid-row--gutters`, `fr-col-md-6`) keyed on the breakpoints above.
### Spacing system
The DSFR is built on the **`v` unit**: `1v = 0.25rem = 4px`. The full ladder runs in single-`v` steps from `0v` to `32v` (with two fractional steps `0-5v` and `1-5v` for hairlines), and every step has a matching `fr-{prop}-Nv` utility class — `fr-mb-4v`, `fr-py-2v`, `fr-gap-6v`, `fr-mt-12v`, etc. The same scale governs both **horizontal** (margin/padding-left, gap-x) and **vertical** (margin/padding-top, gap-y) axes; the DSFR docs separate them visually but the tokens are shared.
The Figma library uses a parallel "**W** unit" where `1W = 2v = 8px`. The W naming surfaces in design specs and in the visual ladder below; the canonical implementation name is always `Nv`.
The most common stops, with their W aliases and typical use:
| Token | W | Value | Common use |
|-------|---|-------|------------|
| `1v` | — | 4px | Hairline spacing |
| `2v` | `1W` | 8px | Tight inline gaps |
| `3v` | — | 12px | Compact stack |
| `4v` | `2W` | 16px | **Default inter-component gap** (button group, form rows) |
| `6v` | `3W` | 24px | Card / container padding, `title-margin` |
| `8v` | `4W` | 32px | Section spacing, `display-margin` |
| `10v` | `5W` | 40px | Wide section gap |
| `12v` | `6W` | 48px | Large section gap |
| `14v` | `7W` | 56px | Page-level rhythm |
| `16v` | `8W` | 64px | Hero / page-level rhythm |
| `18v` | `9W` | 72px | Generous hero margin |
| `24v` | `12W` | 96px | Major editorial spacing |
| `30v` | `15W` | 120px | Maximum section break |
The DSFR also exposes a negative ladder (`n0-5v` … `n8v`) for negative margins, used sparingly — the YAML token block intentionally omits these; agents needing one should emit the corresponding `fr-m-nNv` class directly.
> **Whitespace philosophy.** Generous but utilitarian — *espace pour respirer*, not *espace pour épater*. The State is sober; the layout reflects that.
## Elevation & Depth
The DSFR is **flat by default**. Shadows are reserved for ephemeral surfaces and meaningful state changes:
| Token | Shadow | Use |
|-------|--------|-----|
| `e-0` | none | Default for cards, tiles, panels |
| `e-1` | `0 1px 2px rgba(0, 0, 18, 0.16)` | Subtle separation (rarely used) |
| `e-2` | `0 4px 8px rgba(0, 0, 18, 0.16)` | Modals, dialogs |
| `e-3` | `0 8px 16px rgba(0, 0, 18, 0.16)` | Dropdowns, popovers, on-hover lift for cards |
> **Focus ≠ shadow.** Visible focus is rendered as a **2px outer halo** in `focus-ring` (`#0a76f6`), drawn with `outline` rather than `box-shadow`. This is a hard accessibility requirement — never replace it with a shadow.
## Shapes
The DSFR has **no fundamentals-level border-radius spec**. Corners are square by convention — `rounded.none = 0` is the default for buttons, cards, modals, alerts, inputs, tiles, panels, and effectively everything else. Radius decisions live at the component level, where a small set of component-scoped exceptions are spelled out:
| Component | Radius | Token |
|-----------|--------|-------|
| Search input (top corners) | 4px | `rounded.sm` |
| Tags, badges | full | `rounded.pill` |
| Toggle switch knob/track | full | `rounded.pill` |
These three exceptions account for every non-zero radius in the DSFR. If you find yourself reaching for `rounded.sm` or `rounded.pill` on a component not listed above, you're out of step with the system.
> **Rationale.** Angularity reinforces officiality, restraint, and republican neutrality. Rounded corners would soften the visual register away from the State register the DSFR is designed to preserve.
## Components
Each component below is described once, then surfaced in the YAML token graph as `<component-name>` plus `<component-name>-<state>` entries. State variants follow the alpha `DESIGN.md` convention of separate component entries.
For each component, the **BEM class** column gives the canonical `fr-*` name an implementing agent should emit when generating real HTML — these classes pick up the JS behaviour shipped in `dsfr.module.min.js` automatically.
### Actions
#### Variants
The DSFR exposes four button variants. Each one has hover / active / disabled states; the disabled state collapses every variant to the same grey-on-grey rendering (`background-disabled-grey` / `text-disabled-grey`).
| Variant | BEM class | Visual |
|---------|-----------|--------|
| Primary | `.fr-btn` | `background-action-high-blue-france` fill, `text-inverted-blue-france` label, no border. |
| Secondary | `.fr-btn.fr-btn--secondary` | Transparent fill, blue label, 1 px `border-action-high-blue-france` outline. |
| Tertiary | `.fr-btn.fr-btn--tertiary` | Transparent fill, blue label, 1 px `border-default-grey` outline. |
| Tertiary, no outline | `.fr-btn.fr-btn--tertiary.fr-btn--no-outline` | Transparent fill, blue label, no border. Underlies the pre-baked icon buttons (see below). |
> **Implementation notes.**
>
> 1. **Borders.** The DSFR draws variant outlines with `box-shadow: inset 0 0 0 1px <color>`, not a real `border`, so the box does not gain 1 px of layout when the outline appears. The schema's component sub-tokens do not include `border`, so border colour is not encoded in the YAML — agents should emit the canonical `fr-*` classes (which set the right inset shadow) or apply the shadow themselves: `box-shadow: inset 0 0 0 1px {colors.border-action-high-blue-france}` for secondary, `{colors.border-default-grey}` for tertiary, and none for primary or tertiary-no-outline.
> 2. **Font weight.** All four variants render their label at `font-weight: 500` (500 = medium), one step heavier than the `body-md` default of 400. The schema does not surface font-weight as a component sub-token, so this lives in prose only.
> 3. **Disabled contrast.** Every `*-disabled` variant lands on `#929292` text over `#e5e5e5` background (2.47:1, below WCAG AA's 4.5:1 active-text minimum). This is intentional — WCAG 2.1 §1.4.3 exempts inactive controls from the contrast requirement, and the DSFR uses this exact pair throughout the system to mark disabled state distinctly.
#### Sizes
Three sizes; `md` is the default and is what the YAML's `button-primary` / `button-secondary` / `button-tertiary` entries describe. Sizes are deltas — `button-sm` and `button-lg` override only the dimensional properties and compose with any variant.
| Size | BEM modifier | min-height | padding | typography |
|------|--------------|------------|---------|------------|
| Small | `.fr-btn--sm` | 32 px | 4 px / 12 px | `body-sm` |
| Medium (default) | *(none)* | 40 px | 8 px / 16 px | `body-md` |
| Large | `.fr-btn--lg` | 48 px | 8 px / 24 px | `body-lg` |
#### Icons
Icon composition is markup-only — the same variant tokens apply, only the HTML changes. The DSFR uses `fr-btn--icon-left` / `fr-btn--icon-right` modifier classes plus an icon class (`fr-icon-*-line` or `fr-icon-*-fill`) to position a 16 px glyph next to the label.
```html
<!-- Icon on the left -->
<button class="fr-btn fr-btn--icon-left fr-icon-checkbox-line">Valider</button>
<!-- Icon on the right -->
<button class="fr-btn fr-btn--icon-right fr-icon-arrow-right-line">Continuer</button>
<!-- Icon only (square button — the label still exists for screen readers) -->
<button class="fr-btn fr-icon-close-line" title="Fermer">Fermer</button>
```
Rule: square icon-only buttons must keep an accessible label via `title=` or visually-hidden text; never strip the label from the markup.
#### Pre-baked icon buttons
The DSFR ships several convenience classes that compose `fr-btn--tertiary-no-outline` with a specific icon: `.fr-btn--close` (×), `.fr-btn--display` (theme switcher), `.fr-btn--team` (people), `.fr-btn--briefcase` (recruitment), `.fr-btn--account` (user), `.fr-btn--fullscreen`, `.fr-btn--tooltip`. They are not new design decisions — they are shorthands over `button-tertiary-no-outline` — and so are not enumerated in the YAML. Use them when the surface they appear on already requires that exact icon-and-tone combination (closing a modal, opening the display-mode panel, etc.).
#### Grouping
Group buttons with `.fr-btns-group` — vertical 2W (16 px) gap by default; `--inline` for horizontal at a breakpoint; `--equisized` to match all widths to the widest member.
### Forms
| Component | BEM class |
|-----------|-----------|
| Input (champ de saisie) | `.fr-input` (wrap in `.fr-input-group`; add `.fr-input-group--error` / `--valid` / `--disabled` for state) |
| Select (liste déroulante) | `.fr-select` |
| Checkbox (case à cocher) | `.fr-checkbox-group` + native `<input type="checkbox">` |
| Radio (bouton radio) | `.fr-radio-group` + native `<input type="radio">` |
| Toggle (interrupteur) | `.fr-toggle` |
Every form control must have a visible `<label>`; help and error messages live below the input as `.fr-hint-text`, `.fr-error-text`, or `.fr-valid-text`. The DSFR's *Champ de saisie* covers a fairly large surface (default text + 7 HTML input types + 4 composition patterns), so the rest of this section is dedicated to it.
#### Champ de saisie — states
| State | YAML entry | Underline (`box-shadow: inset 0 -2px 0 0 …`) | Label colour | Helper colour |
|-------|------------|--------------|--------------|---------------|
| Default | `input` | `{colors.border-plain-grey}` | `{colors.text-label-grey}` | `{colors.text-mention-grey}` (hint) |
| Focus | — *(uses global focus ring; no token override)* | unchanged | unchanged | unchanged |
| Error | `input-error` (group adds `.fr-input-group--error`) | `{colors.border-plain-error}` | `{colors.text-default-error}` | `{colors.text-default-error}` (`.fr-error-text`) |
| Success | `input-success` (group adds `.fr-input-group--valid`) | `{colors.border-plain-success}` | `{colors.text-default-success}` | `{colors.text-default-success}` (`.fr-valid-text`) |
| Disabled | `input-disabled` (group adds `.fr-input-group--disabled`) | `{colors.border-disabled-grey}` | `{colors.text-disabled-grey}` | `{colors.text-disabled-grey}` |
Three points the schema cannot express directly:
1. **Asymmetric corners.** The input rounds only its top corners (`border-radius: 0.25rem 0.25rem 0 0`). The YAML records `rounded: "{rounded.sm}"` because that is the closest single-value approximation; agents emitting `.fr-input` get the asymmetric form for free, anything custom should reproduce the half-rounding.
2. **Underline as box-shadow.** The 2 px bottom rule is drawn with `box-shadow: inset 0 -2px 0 0 <colour>`, not a real `border-bottom`, so the input's content area never shifts when the underline changes colour by state. The underline colour is the primary state signal — see the table above.
3. **Group-level error/success accent.** `.fr-input-group--error` and `--valid` add a 2 px coloured strip on the *left* of the whole group via a `::before` pseudo-element (offset −0.75 rem). It marks the entire field cluster (label + input + helper) as one validation unit.
#### Champ de saisie — HTML types
The `.fr-input` class applies the same tokens to every text-like form control. Switching `type=` only changes browser behaviour (keyboard, validation, native picker), never tokens. Stories on the DSFR demo page:
| `type` | Use case | Notes |
|--------|----------|-------|
| `text` (default) | Free text | — |
| `email` | E-mail | Native validation; `inputmode="email"` recommended |
| `tel` | Telephone | `inputmode="tel"` recommended; pair with hint *« Exemple : 0123456789 »* |
| `number` | Integer / decimal | Pair with hint *« Saisissez un nombre entier »* |
| `password` | Password | Native masking |
| `search` | Search query | Visually identical to `text`; the **search bar** component (`.fr-search-bar`) is a different composition that wraps an input + a primary button |
| `url` | URL | Pair with placeholder `https://` and hint *« Saisissez une url valide, commençant par https:// »* |
| `date` | Date | The DSFR injects a calendar SVG via `background-image` on the right (1 rem icon, 1 rem from the right edge) and reserves `padding-right: 3rem` so the native `::-webkit-calendar-picker-indicator` overlays it |
`<textarea>` reuses `.fr-input` directly (`textarea.fr-input`) and only changes `min-height` to `3.75rem` (60 px). All other tokens — colours, padding, underline, asymmetric corners — are identical.
#### Champ de saisie — composition
Four patterns from the DSFR demo page that combine `.fr-input` with surrounding markup. None of these introduce new tokens; they are layout/markup recipes.
**1. Hint text** — small caption between the label and the input.
```html
<div class="fr-input-group">
<label class="fr-label" for="phone">Numéro de téléphone
<span class="fr-hint-text">Exemple : 0123456789</span>
</label>
<input class="fr-input" type="tel" id="phone">
</div>
```
The hint text uses `text-mention-grey` and inherits `body-sm`. Place it inside the `<label>` so screen readers announce it as part of the field name.
**2. Trailing icon** — a 1 rem glyph anchored to the right of the input, used to surface a secondary signal (e.g. `fr-icon-warning-line` for a soft warning that does not warrant the full error state).
```html
<div class="fr-input-group">
<label class="fr-label" for="city">Champ avec une icône</label>
<div class="fr-input-wrap fr-icon-warning-line">
<input class="fr-input" type="text" id="city">
</div>
</div>
```
`.fr-input-wrap` is the positioning context; the icon is rendered via the `fr-icon-*` pseudo-element on the wrap, sized at 1 rem and offset 1 rem from the right edge. The input itself reserves no extra padding for it, so keep the icon purely decorative — never put critical information here.
**3. Combined with a button** (envoi or action) — input and button sit side-by-side inside `.fr-input-wrap--addon`. The input's underline switches to `border-action-high-blue-france` (blue) to signal the bound action, and the button squares to the input's height.
```html
<div class="fr-input-group">
<label class="fr-label" for="newsletter">Champ avec bouton d'envoi associé</label>
<div class="fr-input-wrap fr-input-wrap--addon">
<input class="fr-input" type="email" id="newsletter">
<button class="fr-btn">Envoyer</button>
</div>
</div>
```
For the action variant, swap the `<button class="fr-btn">` for a `<button class="fr-btn fr-btn--tertiary fr-icon-delete-line">…</button>` — this composes the *Tertiary, no outline* button variant (already in the components YAML) with an icon.
**4. Date** — uses `<input type="date">` and lets the browser provide the popover. **The DSFR ships no datepicker JavaScript**; the calendar UI that opens on click is whatever the browser provides natively (Chrome, Edge, Safari, and Firefox each have their own interface, with their own translations and locale rules). The design system stylises only the *field* — same surface, same underline, plus an SVG calendar icon overlaid on the right via `background-image`, scoped to browsers that expose `::-webkit-calendar-picker-indicator` (Chromium + Safari). The browser's default indicator is hidden (`opacity: 0`) but kept as the click target so the popover still opens. Firefox falls back to its native rendering, which differs visually but works the same.
This is a deliberate trade-off: the field is consistent across browsers, the popover is not. Pair `<input type="date">` with a hint text giving the expected format (e.g. *« Format : jj/mm/aaaa »*) because some browsers do not render the placeholder for `type="date"` inputs.
```html
<div class="fr-input-group">
<label class="fr-label" for="dob">Date simple
<span class="fr-hint-text">Format : jj/mm/aaaa</span>
</label>
<input class="fr-input" type="date" id="dob">
</div>
```
#### Liste déroulante
The select reuses every convention from the input — same surface tokens, same asymmetric corners, same `box-shadow: inset 0 -2px 0 0 <colour>` underline, same group-level `::before` accent strip, same hint-text and helper-text patterns. The state mapping is identical too:
| State | YAML entry | Underline colour | Group modifier |
|-------|------------|------------------|----------------|
| Default | `select` | `{colors.border-plain-grey}` | — |
| Focus | — *(global focus ring)* | unchanged | — |
| Error | `select-error` | `{colors.border-plain-error}` | `.fr-select-group--error` |
| Success | `select-success` | `{colors.border-plain-success}` | `.fr-select-group--valid` |
| Disabled | `select-disabled` | `{colors.border-disabled-grey}` | `.fr-select-group--disabled` |
The two select-specific differences:
1. **Chevron icon.** A 1 rem chevron-down SVG is drawn on the right via `background-image` (light fill `#161616` / dark fill `#fff`), anchored 1 rem from the right edge — same technique as the date input. Right padding is bumped to `2.5 rem` to keep the option text from running under the icon. The browser's default `<select>` arrow is suppressed via `appearance: none`.
2. **No `<input type=…>` axis.** A `<select>` is a single element — no twelve-way HTML-type axis to document. Composition is also narrower: hint text is the only common pattern. Inputs combined with a button use `.fr-input-wrap--addon`; selects do not have an equivalent addon recipe in the canonical CSS (use a separate `.fr-btn` placed beside the field via the grid).
```html
<!-- Default + hint -->
<div class="fr-select-group">
<label class="fr-label" for="region">Libellé de la liste déroulante
<span class="fr-hint-text">Texte de description additionnel</span>
</label>
<select class="fr-select" id="region">
<option value="" disabled selected hidden>Sélectionnez une option</option>
<option>Île-de-France</option>
<option>Bretagne</option>
<option>Occitanie</option>
</select>
</div>
<!-- Error -->
<div class="fr-select-group fr-select-group--error">
<label class="fr-label" for="region">Libellé de la liste déroulante</label>
<select class="fr-select" id="region" aria-describedby="region-error">…</select>
<p id="region-error" class="fr-error-text">Sélectionnez une option valide</p>
</div>
```
#### Case à cocher
The checkbox is a multi-axis component. Standalone, it carries `checkbox` (default 24 px) and `checkbox-sm` (16 px) tokens; checked state swaps the fill to `background-active-blue-france` and overlays a white check SVG (light) / blue check SVG (dark). The box itself uses `rounded.sm` (`0.25 rem`) and a 1 px outline drawn from `border-action-high-blue-france`. Disabled state replaces both fill and outline with `background-disabled-grey` and dims the label to `text-disabled-grey`.
Most production usage wraps multiple checkboxes inside a `<fieldset class="fr-fieldset">` with a `<legend class="fr-fieldset__legend">`. The fieldset adds the *group* axis:
| Group state | YAML entry (label) | Modifier | Visual |
|-------------|--------------------|----------|--------|
| Default | — *(`checkbox` only)* | — | Black legend, default labels |
| Hint | — | `<legend>` includes `.fr-hint-text` | Hint paragraph under legend |
| Hint per item | — | each `<label>` includes `.fr-hint-text` | Hint paragraph under each box |
| Inline | — | `.fr-fieldset--inline` | Boxes laid out horizontally |
| Disabled | `checkbox-disabled` | `<fieldset disabled>` | Greyed-out legend + boxes |
| Error | `checkbox-error` | `.fr-fieldset--error` | Red legend + 2 px red strip on left + `.fr-error-text` |
| Success | `checkbox-success` | `.fr-fieldset--valid` | Green legend + 2 px green strip on left + `.fr-valid-text` |
Two notes:
1. **The boxes themselves do not change colour for error/success states** — only the surrounding legend, labels, and the 2 px left strip change. This mirrors the `.fr-fieldset` accent used by all other group-able form controls (radios, date-fields).
2. **The fieldset's left strip is `position: absolute; left: -0.75 rem`** in the canonical CSS — it visually escapes the fieldset's content area to align with the form field's natural left edge. In our preview we collapse this into a 2 px `::before` inside the fieldset's left padding for simplicity; the visual is identical.
```html
<!-- Default group -->
<fieldset class="fr-fieldset">
<legend class="fr-fieldset__legend">Légende pour l'ensemble des éléments</legend>
<div class="fr-fieldset__element">
<div class="fr-checkbox-group">
<input type="checkbox" id="cb-1">
<label class="fr-label" for="cb-1">Checkbox 1</label>
</div>
</div>
<!-- … -->
</fieldset>
<!-- Error group with hint per item -->
<fieldset class="fr-fieldset fr-fieldset--error" aria-labelledby="cb-legend cb-error">
<legend class="fr-fieldset__legend" id="cb-legend">Légende pour l'ensemble des éléments</legend>
<div class="fr-fieldset__element">
<div class="fr-checkbox-group">
<input type="checkbox" id="cb-1">
<label class="fr-label" for="cb-1">Checkbox 1
<span class="fr-hint-text">Texte additionnel</span>
</label>
</div>
</div>
<div class="fr-messages-group" id="cb-error">
<p class="fr-message fr-message--error">Texte d'erreur</p>
</div>
</fieldset>
```
#### Bouton radio
The radio button mirrors the checkbox structurally: same `<fieldset>/<legend>` group composition, same `--inline` modifier, same hint patterns, same disabled / error / success group states with the 2 px left strip. Differences are purely visual:
1. **Pill-shaped, not square.** `rounded.pill` instead of `rounded.sm`. Default size is still 24 px (`md`); sm is 16 px.
2. **Checked indicator is an inner dot, not a check SVG.** Canon stacks two radial gradients: the outer ring (transparent core, blue ring at 11–12 px), and the inner dot (blue fill at 5–6 px). In our preview we use a single `radial-gradient(circle at center, blue 0 32%, white 40%)` to fake the same visual.
3. **Inner dot stays blue in error/success groups.** Only the *ring* changes colour — canon achieves this by re-emitting the outer radial-gradient with `border-plain-error` / `border-plain-success` while leaving the dot's gradient unchanged. Functionally equivalent: red/green ring around a blue dot.
| Group state | YAML entry | Modifier | Visual |
|-------------|------------|----------|--------|
| Default | — *(`radio` only)* | — | Black legend + black labels + blue ring |
| Hint | — | `<legend>` + `.fr-hint-text` | Hint paragraph under legend |
| Hint per item | — | each `<label>` + `.fr-hint-text` | Hint paragraph under each radio |
| Inline | — | `.fr-fieldset--inline` | Radios laid out horizontally |
| Disabled | `radio-disabled` | `<fieldset disabled>` | Greyed legend + greyed rings |
| Error | `radio-error` | `.fr-fieldset--error` | Red legend, red ring, blue dot, 2 px red strip on left |
| Success | `radio-success` | `.fr-fieldset--valid` | Green legend, green ring, blue dot, 2 px green strip on left |
##### Bouton radio riche (`fr-radio-rich`)
A composition pattern unique to radio. The radio is wrapped in a tile-like container that includes:
- A 5.5 rem × 5.5 rem **pictogram cell** on the side (canon defaults to flush-left; an `--align-right` variant flips it).
- A **title + optional hint** stack in the body.
- The actual radio circle anchored to the top-right of the tile.
Selection lights up the entire tile border in `border-action-high-blue-france` (canon adds an inset 1 px shadow to keep the border weight stable while the colour transitions). Hover and active states tint the body and pictogram cells via `background-default-grey-hover` / `-active`.
```html
<!-- Default radio group -->
<fieldset class="fr-fieldset">
<legend class="fr-fieldset__legend">Légende pour l'ensemble des éléments</legend>
<div class="fr-fieldset__element">
<div class="fr-radio-group">
<input type="radio" id="r-1" name="r"><label class="fr-label" for="r-1">Radio 1</label>
</div>
</div>
<!-- … -->
</fieldset>
<!-- Bouton radio riche -->
<div class="fr-radio-group fr-radio-rich">
<input type="radio" id="rr-1" name="rr">
<label class="fr-label" for="rr-1">Titre
<span class="fr-hint-text">Description courte</span>
</label>
<div class="fr-radio-rich__pictogram">
<svg class="fr-artwork" aria-hidden="true" viewBox="0 0 80 80">…</svg>
</div>
</div>
```
#### Interrupteur
The toggle (`fr-toggle`) is structurally a `<input type="checkbox">` rendered as a 40 × 24 px sliding pill. Three details set it apart from the other form controls:
1. **OFF state is stroked, not filled.** The track in OFF state is a *white interior* with a 1 px `border-action-high-blue-france` outline — turning it ON fills the track with `text-action-high-blue-france` and reveals a small blue check ✓ inside the still-white thumb. The thumb keeps its 1 px blue-france ring at all times.
2. **Optional microstate label.** A small "Activé" / "Désactivé" caption can render *below* the track in `text-active-blue-france` — exposed via the `data-fr-checked-label` / `data-fr-unchecked-label` attributes on the `<label>` and rendered through `::before` in canon. Useful for longer-form toggles where a screen-only state caption helps clarity.
3. **Border-bottom modifier.** `.fr-toggle--border-bottom` adds a single 1 px `border-default-grey` rule under the toggle row — used to visually separate stacked toggles in a fieldset (the *Border Group Story*).
| Story | YAML entry | Modifier | Visual |
|-------|------------|----------|--------|
| Default (off) | `toggle` | — | White track, blue stroke, white thumb left |
| Activé (on) | `toggle-on` | `<input>:checked` | Blue track, white thumb right with blue ✓ |
| Description | — | `+ .fr-hint-text` | Hint paragraph below toggle row |
| Microstate | `toggle-state` | label has `data-fr-{un,}checked-label` | "Activé" / "Désactivé" caption below track |
| Disabled | `toggle-disabled` | `<input>:disabled` | Grey track stroke, grey thumb ring, grey label |
| Error (group) | `toggle-error` | `.fr-fieldset--error` (or `.fr-toggle--error`) | Red label, 2 px red strip on left, helper text |
| Success (group) | `toggle-success` | `.fr-fieldset--valid` | Green label, 2 px green strip on left, helper text |
| Border-bottom | — | `.fr-toggle--border-bottom` | 1 px bottom rule under each toggle |
The ON state's track fill stays blue even inside an error or success group — only the *label* and the *strip* take the state colour. This mirrors the radio's blue inner dot.
```html
<!-- Default toggle -->
<div class="fr-toggle">
<input type="checkbox" class="fr-toggle__input" id="t-1">
<label class="fr-toggle__label" for="t-1">Libellé de l'interrupteur</label>
</div>
<!-- With microstate caption + hint -->
<div class="fr-toggle">
<input type="checkbox" class="fr-toggle__input" id="t-2">
<label class="fr-toggle__label" for="t-2"
data-fr-checked-label="Activé" data-fr-unchecked-label="Désactivé">
Libellé de l'interrupteur
</label>
<span class="fr-hint-text">Texte additionnel</span>
</div>
<!-- Border group: stack with separators -->
<fieldset class="fr-fieldset">
<legend class="fr-fieldset__legend">Légende pour l'ensemble des éléments</legend>
<div class="fr-fieldset__element">
<div class="fr-toggle fr-toggle--border-bottom">
<input type="checkbox" id="t-g-1"><label for="t-g-1">Libellé 1</label>
</div>
</div>
<!-- … -->
</fieldset>
```
### Surfaces
| Component | BEM class |
|-----------|-----------|
| Card (carte) | `.fr-card` (`--horizontal` for side-by-side image; `--no-arrow` to hide the action chevron) |
| Tile (tuile) | `.fr-tile` (smaller, denser variant of card; primary content is a single link) |
| Callout (mise en avant) | `.fr-callout` |
Cards are flat at rest; on `:hover` they may apply `e-3` to suggest interactivity if the entire card is a link.
#### Carte
The card has three sizes that modulate **body padding** and **title size** while keeping the same overall structure (image / title / optional description / arrow). The `--md` modifier exists in the canon but is a no-op vs default, so practical usage is `--sm`, default, `--lg`.
| Story | Modifier | Body padding | Title (size / line-height) | Description size | Arrow icon |
|-------|----------|--------------|----------------------------|------------------|-----------|
| Default | *(none)* | `2rem` (32 px) | `1.25rem / 1.75rem` (20 / 28) | `0.875rem / 1.5rem` | 1 rem |
| Sm | `.fr-card--sm` | `1.5rem` (24 px) | `1.125rem / 1.5rem` (18 / 24) | same | 1 rem |
| Md | `.fr-card--md` | `2rem` (= default) | `1.25rem / 1.75rem` (= default) | same | 1 rem |
| Lg | `.fr-card--lg` | `2.5rem` (40 px) | `1.375rem / 1.75rem` (22 / 28) | `1rem / 1.5rem` | 1.5 rem |
Three visual constants across all sizes:
- **Title** is rendered in `text-title-blue-france` with weight 700; the trailing `→` arrow is anchored to the bottom-right of the body via the `__end` slot in canon, drawn from the `arrow-right-line.svg` system icon at 1 rem (1.5 rem in `--lg`).
- **Image** uses an aspect ratio of ~16/10 in the canonical demos. The image element itself is structurally `.fr-card__img > img` and has no token styling beyond `object-fit: cover`. The placeholder asset is the same lavender mountain SVG across all sizes — the size modifiers do not change the image styling.
- **Border** is 1 px `border-default-grey` unless `.fr-card--no-border` or `.fr-card--shadow` is set.
**Hover & active states.** When the entire card is interactive (`.fr-enlarge-link` or `.fr-enlarge-button` — the most common preset), hovering darkens the image via a `filter: brightness()` (canon decrements 10 % in light theme, increments 10 % in dark theme — a unified "darken" perception thanks to the `--brighten` direction-aware variable), and tints the card body background to `background-default-grey-hover`. Active state pushes the same delta to 20 %. The two effects together produce a clearly perceptible interactivity feedback: the image area appears to gain a grey overlay, and the body background shifts from white to a faint warm grey.
**Layout axis.** Above the `md` breakpoint the card switches from a top-image vertical stack to a side-by-side row when one of three modifiers is set. The image region width is fixed; everything else flows into the remaining track.
| Modifier | Image width | Body width |
|----------|-------------|------------|
| *(none)* | 100 % (top) | 100 % (below image) |
| `.fr-card--horizontal` | 40 % (left) | 60 % (right) |
| `.fr-card--horizontal-half` | 50 % | 50 % |
| `.fr-card--horizontal-tier` | 33.3 % | 66.7 % |
Below the `md` breakpoint, all three horizontal modifiers collapse back to the vertical stack — the canonical CSS guards the `flex-direction: row` behind a `min-width` media query so the layout responds gracefully on mobile.
**Decoration axis.** Background colour, border, and elevation are toggled independently:
| Modifier | Background | Border | Elevation |
|----------|------------|--------|-----------|
| *(none)* | `background-default-grey` (white) | 1 px `border-default-grey` | flat |
| `.fr-card--grey` | `background-contrast-grey` (light grey) | 1 px border | flat |
| `.fr-card--shadow` | `background-raised-grey` | none | `drop-shadow(--raised-shadow)` |
| `.fr-card--shadow.fr-card--grey` | `background-contrast-raised-grey` | none | `drop-shadow` |
| `.fr-card--no-border` | `background-default-grey` | none | flat |
| `.fr-card--no-background` | `transparent` | 1 px border | flat |
The border-vs-shadow split is handled by the canonical selector `.fr-card:not(.fr-card--no-border):not(.fr-card--shadow)` — adding either modifier removes the inset gradient that draws the four 1 px sides.
**Slots & composition.** Inside `__content` the canonical card lays out three optional regions plus the title and description:
| Slot | BEM | Typical content |
|------|-----|-----------------|
| Start | `.fr-card__start` | `fr-badges-group`, `fr-tags-group`, `fr-card__detail` (small descriptor in `text-mention-grey`, body-xs, sits above the title) |
| Title + desc | `.fr-card__title`, `.fr-card__desc` | The card's main reading content |
| End | `.fr-card__end` | Detail tail (date, author), the trailing arrow icon when `enlarge-link` |
| Footer | `.fr-card__footer` | Standalone action buttons, secondary links — sits below the body and outside the enlarged hit-area |
`fr-badges-group` placed in `__header` instead of `__start` floats the badges over the top-left of the image (used in news-item cards). `__detail` is a single-line caption — typically a date or category — that scoots up to sit just above the title with `margin-bottom: -1rem` (canon).
**Action variants.** The trailing icon and the click target both depend on three independent flags:
| Modifier | Icon | Click target | Use case |
|----------|------|--------------|----------|
| `.fr-enlarge-link` *(default in canon demos)* | arrow-right (`→`) | entire card | Card linking to a page |
| `.fr-enlarge-button` | arrow-right | entire card (button) | Card triggering an action (modal, expand) |
| `.fr-card--download` | download (`↓`) | entire card | File card — title becomes a download link, body shows file metadata via `__detail` |
| `.fr-card--no-arrow` | *(none)* | depends on link/button modifier | Decorative or contextual card without explicit affordance |
| `.fr-card--no-icon` | *(none)* | depends on link/button modifier | Same as `--no-arrow`, kept for legacy markup |
`.fr-card--download` also overrides the layout to row (image left at 40 %), and constrains its `__img` to `object-fit: contain` over a `background-alt-grey` panel — file thumbnails are shown padded inside their tile rather than cropped to fill.
```html
<div class="fr-card fr-enlarge-link fr-card--lg">
<div class="fr-card__body">
<div class="fr-card__content">
<h3 class="fr-card__title"><a href="…">Intitulé de la carte</a></h3>
<p class="fr-card__desc">Description optionnelle.</p>
</div>
</div>
<div class="fr-card__header">
<div class="fr-card__img">
<img src="…" alt="">
</div>
</div>
</div>
```
#### Tuile
The tile (`fr-tile`) is the card's denser sibling. It carries a **pictogram** (an SVG illustration sized 5 rem × 5 rem) instead of a hero image, and is decorated by a **4 px coloured rule on its bottom edge** that signals interactivity. Unlike the card, the tile only ships in two sizes (default and `--sm`).
**Sizes.**
| Story | Modifier | Padding | Title | Pictogram | Detail |
|-------|----------|---------|-------|-----------|--------|
| Default | *(none)* | `2rem 2rem 2.25rem` (32 / 32 / 36) | `1.125rem / 1.5rem` (18 / 24) | 5 rem × 5 rem | `0.75rem / 1.25rem` |
| Sm | `.fr-tile--sm` | `1.5rem 1.5rem 1.75rem` (24 / 24 / 28) | `1rem / 1.5rem` (16 / 24) | 3.5 rem × 3.5 rem | same |
| Horizontal | `.fr-tile--horizontal` | same as default | same | 4 rem × 4 rem | margin-bottom: -2.5 rem |
| Sm + Horizontal | combo | sm padding | sm title | 2.5 rem × 2.5 rem | sm spacing |
**The bottom rule.** Drawn via a `linear-gradient` background on `__title::before` (4 px high, full-width). Two colours:
| Variant | Title element | Rule colour | Title text colour |
|---------|---------------|-------------|-------------------|
| Interactive | `__title > a` or `__title > button` | `border-active-blue-france` (`#000091` / `#8585f6`) | `text-action-high-blue-france` |
| Static / no-link | `__title` (text only, no nested anchor) | `border-plain-grey` (`#3a3a3a` / `#cecece`) | `text-title-grey` |
This is the tile's most distinctive token: it is the only common surface in DSFR where the bottom 4 px is a coloured rule, and the colour carries semantic weight (blue = clickable, dark grey = decorative).
**Layout axis.** Vertical (default — pictogram on top, content centred) vs horizontal (pictogram left, content right, text left-aligned). The horizontal modifier reduces the pictogram to 4 rem so the row stays compact. A responsive variant `.fr-tile--horizontal.fr-tile--vertical@md` (and `@lg`) starts horizontal and switches to vertical above the breakpoint — useful for grids that flow from compact list-style on mobile to picto-on-top above the fold on desktop.
**Decoration axis.** Identical mechanism to the card — same 5 modifiers, same colour pairs:
| Modifier | Background |
|----------|------------|
| *(none)* | `background-default-grey` (white) |
| `.fr-tile--grey` | `background-contrast-grey` |
| `.fr-tile--shadow` | `background-raised-grey` + drop-shadow, no border |
| `.fr-tile--shadow.fr-tile--grey` | `background-contrast-raised-grey` + drop-shadow |
| `.fr-tile--no-border` | white, no border |
| `.fr-tile--no-background` | transparent |
By DSFR convention, **tiles carrying a `__start` badge** are typically paired with `--grey` (the badge sits more clearly on the tinted body). Tiles with a `__start` tag use the default white background.
**Slots & composition.**
| Slot | BEM | Order | Typical content |
|------|-----|-------|-----------------|
| Header | `.fr-tile__header` | 1 (top) | `.fr-tile__pictogram > svg` |
| Start | `.fr-tile__start` (inside `__body`) | 1 (above title) | `fr-badges-group`, `fr-tags-group` |
| Body | `.fr-tile__body > __title + __desc + __detail` | 2 | Title (h3 wrapping `<a>`), description, detail |
| Detail | `.fr-tile__detail` | 4 (bottom of body) | Caption — date, file metadata (Extension - Poids - Langue), author |
**Action variants.**
| Modifier | Trailing icon | Title element | Rule colour | Use case |
|----------|---------------|---------------|-------------|----------|
| `.fr-enlarge-link` *(default in canon demos)* | `→` (1.5 rem; 1 rem in `--sm`) | `<a>` inside title | blue | Tile linking to a page |
| `.fr-enlarge-button` | `→` | `<button>` inside title | blue | Tile triggering an action |
| `.fr-tile--download` | `↓` (download-line.svg) | `<a>` (download attribute) | blue | File tile — `__detail` carries Extension / Weight / Language |
| `.fr-tile--no-icon` | *(none)* | `<a>`/`<button>` | blue | Decorative tile without explicit affordance |
| *no nested link* | *(none)* | text only | grey | Static informational tile (DSFR's "No Link Story") |
The `--download` modifier also forces horizontal layout (`flex-direction: row`) and overrides the trailing icon to the download glyph. When stacked with `--sm` it produces the canon's "Download Sm" preset.
```html
<!-- Default tile -->
<div class="fr-tile fr-enlarge-link">
<div class="fr-tile__body">
<div class="fr-tile__content">
<h3 class="fr-tile__title"><a href="…">Intitulé de la tuile</a></h3>
<p class="fr-tile__desc">Description (optionnelle)</p>
<p class="fr-tile__detail">Détail (optionnel)</p>
</div>
</div>
<div class="fr-tile__header">
<div class="fr-tile__pictogram">
<svg class="fr-artwork" aria-hidden="true" viewBox="0 0 80 80">…</svg>
</div>
</div>
</div>
<!-- Download tile -->
<div class="fr-tile fr-tile--download fr-enlarge-link">
<div class="fr-tile__body">
<div class="fr-tile__content">
<h3 class="fr-tile__title"><a download href="…">Télécharger le document XX</a></h3>
<p class="fr-tile__detail">PDF — 1,2 Mo — Français</p>
</div>
</div>
<div class="fr-tile__header">
<div class="fr-tile__pictogram">…</div>
</div>
</div>
```
#### Mise en avant
The callout (`fr-callout`) is a grey-tinted block with a **4 px coloured rule on its left edge**, used to highlight a notice, summarise key information, or pair an action with a contextual prompt. Structurally simpler than card and tile — no image, no pictogram, just three optional slots inside a tinted body.
**Anatomy.**
| Element | BEM | Typography | Colour |
|---------|-----|------------|--------|
| Body (background) | `.fr-callout` | — | `background-contrast-grey` |
| Left rule (4 px) | drawn via `linear-gradient` background-image | — | `border-default-blue-france` (default) or thematic |
| Icon *(optional)* | `fr-icon-*` class on `.fr-callout` itself, rendered via `::before` | 1 rem icon mask | `text-title-grey` |
| Title | `.fr-callout__title` | h4 — `1.375rem / 1.75rem`, weight 700 | `text-title-grey` |
| Text | `.fr-callout__text` | body-lg — `1.125rem / 1.75rem` | `text-default-grey` |
| Action *(optional)* | `.fr-btn` (any standard button variant) | — | inherits button tokens |
Padding is `1.5rem` on all four sides; default block margin is `0 0 1.5rem`. The icon, when present, sits **above** the title (`display: block; margin: -0.5rem 0 0.5rem`) — it's not a left-side decorator but a stacked tag.
**Composition matrix.** The 4 DSFR demo stories (Default / Icon / Button / Icon+Button) are all permutations of two binary slots:
| Slot | Toggle | Effect |
|------|--------|--------|
| Icon | Add `fr-icon-information-line` (or any `fr-icon-*` class) directly on `.fr-callout` | Icon block appears above the title |
| Button | Add `<button class="fr-btn">…</button>` (or `<a class="fr-btn">`) inside the callout | Button appears below the text with `margin-top: 1rem` |
The Icon+Button story is just `icon=on, button=on` — no extra rule.
**Thematic colour variants.** The callout supports a 17-element thematic palette via single-class modifiers `fr-callout--<name>`. Each variant swaps **both** the body background (`background-contrast-{name}`) and the left rule (`border-default-{name}`) to a coordinated colour pair, while leaving title, text, and icon colours unchanged (so contrast remains readable across themes).
| Variant | BG (light) | Rule (light) | Use |
|---------|------------|--------------|-----|
| *(default)* | `background-contrast-grey` (`#eeeeee`) | `border-default-blue-france` (`#000091`) | Generic notice |
| `--green-tilleul-verveine` | tilleul tinted bg | green rule | Sustainability, ecology |
| `--green-bourgeon` | bourgeon tinted bg | green rule | Plant / agriculture |
| `--green-emeraude` | emeraude tinted bg | green rule | Health |
| `--green-menthe` | menthe tinted bg | green rule | Education |
| `--green-archipel` | archipel tinted bg | green rule | Maritime / overseas |
| `--blue-ecume` | écume tinted bg | blue rule | Defence / sea |
| `--blue-cumulus` | cumulus tinted bg | blue rule | Sport / weather |
| `--purple-glycine` | glycine tinted bg | purple rule | Culture |
| `--pink-macaron` | macaron tinted bg | pink rule | Tourism |
| `--pink-tuile` | tuile tinted bg (`#fee9e7`) | terracotta rule (`#ce614a`) | DSFR's "Accent" preset |
| `--yellow-tournesol` | tournesol tinted bg | yellow rule | Energy |
| `--yellow-moutarde` | moutarde tinted bg | mustard rule | Justice / regulation |
| `--orange-terre-battue` | terre-battue tinted bg | orange rule | Industry |
| `--brown-cafe-creme` | café-crème tinted bg | brown rule | Heritage |
| `--brown-caramel` | caramel tinted bg | caramel rule | Local government |
| `--brown-opera` | opéra tinted bg | brown rule | Generic warm accent |
| `--beige-gris-galet` | beige tinted bg | beige rule | Generic neutral accent |
The thematic colour names map to French ministerial / administrative palettes — agencies typically pick the variant aligned with their branding. From a CSS perspective they are all generated by the same template: each variant rule is exactly two background-* property assignments. The light/dark theme system handles the corresponding dark-mode tokens automatically.
```html
<!-- Default callout, no icon, no button -->
<div class="fr-callout">
<h3 class="fr-callout__title">Titre de la mise en avant</h3>
<p class="fr-callout__text">Lorem ipsum dolor sit amet, consectetur adipiscing.</p>
</div>
<!-- With icon and button -->
<div class="fr-callout fr-icon-information-line">
<h3 class="fr-callout__title">Information</h3>
<p class="fr-callout__text">Texte d'explication.</p>
<button class="fr-btn">En savoir plus</button>
</div>
<!-- Thematic accent -->
<div class="fr-callout fr-callout--pink-tuile">
<h3 class="fr-callout__title">Mise en avant accent</h3>
<p class="fr-callout__text">Texte d'explication.</p>
</div>
```
### Tags & badges
| Component | BEM class |
|-----------|-----------|
| Tag | `.fr-tag` (sizes `--sm`, default; can be `<a>` or `<button>` for selectable) |
| Badge | `.fr-badge` (variants `--success`, `--warning`, `--error`, `--info`, `--new`) |
Tags and badges are the **only** components that use `rounded.pill`.
#### Tag
The tag (`fr-tag`) is a pill-shaped label. Five orthogonal axes drive every variant DSFR ships:
| Axis | Toggle | Effect |
|------|--------|--------|
| Size | `.fr-tag--sm` | min-height 32 → 24, font 14/24 → 12/20, radius 1rem → 0.75rem, padding 4/12 → 2/8 |
| Variant | element + modifiers | static / clickable / pressable / dismissible (see table below) |
| Icon | `.fr-tag--icon-left` + `fr-icon-*` | leading 1 rem icon (`::before`) with `margin-right: 0.25rem` |
| Thematic | `.fr-tag--<colour>` (clickable only) | swap colour pair to thematic palette (17 variants — same names as callout) |
| Group | wrap children in `<ul class="fr-tags-group">` | flex-wrap row, 0.25 rem gutters; flattens `<ul>` defaults |
**Variants (the type axis).** The variant is determined by the **HTML element** and modifiers — *not* by a single class:
| Variant | Element | Modifier | Background | Text | Trailing icon |
|---------|---------|----------|------------|------|---------------|
| Static | `<span>`, `<p>` | — | `background-contrast-grey` (light grey) | `text-label-grey` | none |
| Clickable | `<a>`, `<button>` | — | `background-action-low-blue-france` (lavender) | `text-action-high-blue-france` (`#000091`) | none |
| Pressed (toggled-on) | `<button>` | `[aria-pressed="true"]` | `background-active-blue-france` (filled dark navy) | `text-inverted-blue-france` (white) | `checkbox-circle-line.svg` 1 rem badge in the **top-right corner**, with the bg drawn as a `radial-gradient` "punching" a transparent hole around the badge |
| Dismissible | `<a>`, `<button>` | `.fr-tag--dismiss` | filled dark navy (matches pressed) | white | `close-line.svg` ::after icon, inline after the label |
The pressed-state corner-badge trick is the tag's most distinctive piece of CSS: the canon doesn't use a separate decorator element — it draws a `radial-gradient` background that is opaque except for a small hole at top-right, then overlays the check SVG via `::after` inside that hole. This produces a coherent visual where the badge appears "cut out" of the pill.
**Icon placement.** Two patterns:
* `.fr-tag--icon-left` paired with an `fr-icon-*` class on the tag puts a leading icon (`::before`, 1 rem, margin-right 0.25 rem) before the label.
* For `target="_blank"` external links, the canon adds an external-link `::after` icon automatically (no opt-in modifier).
**Thematic palette.** Clickable tags expose the same 17 colour variants as the callout (`fr-tag--green-tilleul-verveine`, `fr-tag--pink-tuile`, etc.). Each swaps both `color` (to `text-action-high-{name}`) and `background-color` (to `background-action-low-{name}`). Thematic variants apply only to anchor/button tags — static tags stay grey.
**Group (`fr-tags-group`).** A `<ul>` (or `<ol>`) wrapper that lays children out as a flex-wrap row with 0.25 rem inter-tag spacing (achieved via `-0.25rem` outer margins and `0.25rem` per-child margins, so the group sits flush with surrounding content). The group exposes one modifier — `.fr-tags-group--sm` — which forces all children to the sm size in one place.
```html
<!-- Static -->
<p class="fr-tag">Libellé</p>
<!-- Clickable -->
<a class="fr-tag" href="…">Libellé</a>
<!-- Pressable (toggle button) -->
<button class="fr-tag" aria-pressed="true">Libellé</button>
<!-- Dismissible -->
<button class="fr-tag fr-tag--dismiss">Libellé</button>
<!-- With leading icon -->
<a class="fr-tag fr-tag--icon-left fr-icon-arrow-right-line" href="…">Libellé</a>
<!-- Group -->
<ul class="fr-tags-group">
<li><p class="fr-tag">Libellé 1</p></li>
<li><p class="fr-tag">Libellé 2</p></li>
<li><p class="fr-tag">Libellé 3</p></li>
</ul>
```
#### Badge
The badge (`fr-badge`) is a compact label for status, accent, or categorisation cues. It shares the pill radius family with tags but has a different default behaviour: **neutral grey at rest**.
| Axis | Toggle | Effect |
|------|--------|--------|
| Size | `.fr-badge--sm` | 32px (md) → 24px (sm), typography 14/24 → 12/20 |
| Status | `.fr-badge--success|--warning|--error|--info` | Functional background/text pairs |
| Icon | optional leading icon | Same colour pair as status/no-status variant |
| Accent | `.fr-badge--new` | Editorial accent colour (outside functional status family) |
| Ellipsis | truncation utility | Long labels clipped with `…` in constrained width |
| Group | `.fr-badges-group` | Flex-wrap row container for multiple badges |
**Status and accent variants.**
| Variant | BEM modifier | Background | Text |
|---------|--------------|------------|------|
| Default | *(none)* | `background-contrast-grey` | `text-label-grey` |
| Success | `--success` | `success-975-75` | `success-425-625` |
| Warning | `--warning` | `warning-975-75` | `warning-425-625` |
| Error | `--error` | `error-975-75` | `text-default-error` |
| Info | `--info` | `info-975-75` | `text-default-info` |
| Accent | `--new` | `green-menthe-925-125` | `green-menthe-sun-373-moon-652` |
**Icons.** Status badges can be rendered with or without icon. Icon presence does not define a separate token family — it is a composition choice (`::before` or inline icon element).
**Ellipsis.** For long labels in constrained columns, keep the badge on one line and clip with ellipsis (`white-space: nowrap; overflow: hidden; text-overflow: ellipsis;`).
**Group (`fr-badges-group`).** A list wrapper (`<ul>`/`<ol>`) that lays badges out in a wrapped row. Use md for dense desktop content and sm for compact sidebars or metadata stacks.
```html
<!-- Default -->
<p class="fr-badge">Libellé badge</p>
<!-- Small -->
<p class="fr-badge fr-badge--sm">Libellé badge</p>
<!-- Status with icon -->
<p class="fr-badge fr-badge--success fr-badge--icon-left fr-icon-checkbox-circle-fill">Libellé badge</p>
<!-- Status without icon -->
<p class="fr-badge fr-badge--warning">Libellé badge</p>
<!-- Accent -->
<p class="fr-badge fr-badge--new">Libellé badge</p>
<!-- Ellipsis in constrained width -->
<p class="fr-badge" style="max-width: 24rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
Libellé très long qui sera tronqué lorem ipsum dolor sit amet consectetur adipiscing…
</p>
<!-- Group -->
<ul class="fr-badges-group">
<li><p class="fr-badge">Libellé badge 1</p></li>
<li><p class="fr-badge">Libellé badge 2</p></li>
<li><p class="fr-badge">Libellé badge 3</p></li>
</ul>
```
#### Alerte
The alert (`fr-alert`) is a feedback container with a strong status rail on the left, optional icon, mandatory title, and optional description. It is **not** a button-like component: the body stays neutral while status is encoded by the left rail and icon.
| Axis | Toggle | Effect |
|------|--------|--------|
| Content | title-only / title+description | Compact vs explanatory message |
| Status | `.fr-alert--success|--error|--info|--warning` | Rail + border colour swap and status icon |
| Size | `.fr-alert--sm` | Reduced vertical rhythm and smaller text |
| Dismissible | close button (`fr-btn--close`) | User can close alert in JS-enabled contexts |
| No-JS dismiss | close button without JS behavior | Visual parity when script is disabled |
| Icon custom | custom pictogram in rail | Replace default status icon for domain-specific semantics |
**Status variants.**
| Variant | Modifier | Rail / border colour |
|---------|----------|----------------------|
| Success | `--success` | `success-425-625` |
| Error | `--error` | `error-425-625` |
| Info | `--info` | `info-425-625` |
| Warning | `--warning` | `warning-425-625` |
The title should include the status noun (e.g. *Succès*, *Erreur*, *Information*, *Attention*) so meaning remains explicit even without colour.
```html
<!-- Title only -->
<div class="fr-alert fr-alert--info">
<h3>Titre du message d'information</h3>
</div>
<!-- Title + description -->
<div class="fr-alert fr-alert--warning">
<h3>Titre du message d'avertissement</h3>
<p>Texte de description de l'alerte.</p>
</div>
<!-- Dismissible -->
<div class="fr-alert fr-alert--info fr-alert--dismissible">
<button class="fr-btn--close" title="Masquer le message">Masquer le message</button>
<h3>Titre du message</h3>
<p>Cliquer sur la croix pour fermer l'alerte.</p>
</div>
<!-- Small -->
<div class="fr-alert fr-alert--error fr-alert--sm">
<h3>Erreur : Description détaillée du message…</h3>
</div>
```
### Feedback
| Component | BEM class |
|-----------|-----------|
| Alert | `.fr-alert` + `.fr-alert--success | --warning | --error | --info`; size suffixes `--sm` |
| Notice (bandeau d'information) | `.fr-notice` |
Alerts include a leading icon (info/check/exclamation/cross) and an optional close button (`.fr-btn--close`).
#### Modal
The modal (`fr-modal`) is primarily differentiated in its **opened state**: backdrop, centered panel, size, and optional footer actions. The trigger button is not the variant axis.
| Axis | Toggle | Effect |
|------|--------|--------|
| Size | `.fr-modal--sm|--md|--lg` | Changes panel max width in opened state |
| Content density | short / long body | Affects scroll behavior in modal body |
| Footer | with / without action area | Optional separated action slot |
| Dismiss | close button (`.fr-btn--close`) | Top-right close affordance |
Canonical opened-state anatomy:
- backdrop overlay (`rgba(0,0,0,0.55)`-like dim)
- centered white panel with square corners
- close action in the top-right
- title row (often with icon)
- scrollable body when content exceeds viewport
- optional footer separator + stacked actions
```html
<!-- Opened modal (md) -->
<div class="fr-modal fr-modal--opened fr-modal--md" role="dialog" aria-modal="true">
<div class="fr-modal__content">
<button class="fr-btn fr-btn--close" aria-label="Fermer">Fermer</button>
<h1 class="fr-modal__title">Titre de la modale</h1>
<div class="fr-modal__body">
<p>Lorem ipsum…</p>
</div>
</div>
</div>
<!-- Opened modal with footer actions -->
<div class="fr-modal fr-modal--opened fr-modal--lg" role="dialog" aria-modal="true">
<div class="fr-modal__content">
<button class="fr-btn fr-btn--close" aria-label="Fermer">Fermer</button>
<h1 class="fr-modal__title">Titre de la modale</h1>
<div class="fr-modal__body"><p>Lorem ipsum…</p></div>
<div class="fr-modal__footer">
<button class="fr-btn">Libellé bouton</button>
<button class="fr-btn fr-btn--secondary">Libellé bouton</button>
</div>
</div>
</div>
```
### Navigation & branding
| Component | BEM class |
|-----------|-----------|
| Breadcrumb (fil d'Ariane) | `.fr-breadcrumb` |
| Modal (modale) | `.fr-modal` (focus trap + scroll lock provided by the JS) |
| Header (en-tête) | `.fr-header` (includes `.fr-header__brand`, `.fr-header__service`, `.fr-header__tools`) |
| Footer (pied de page) | `.fr-footer` (includes `.fr-footer__brand`, `.fr-footer__bottom`, `.fr-footer__partners`) |
The header is the brand carrier: the Marianne lockup (*"République Française"*) sits in `.fr-header__brand`, and the operator/service lockup sits beside it in `.fr-header__service`. This is the canonical *bloc marque* and must be present on every State page.
#### Header
The header (`fr-header`) has two rendering modes driven by breakpoints:
| Breakpoint | Layout | Nav + tools location |
|-----------|--------|---------------------|
| < `lg` (mobile) | Brand-top row with hamburger (`fr-btn--menu`) + search icon. Service below separator. | Inside a `fr-modal` opened by the hamburger button. |
| ≥ `lg` (desktop) | Brand + service inline left, tools top-right, nav bar below. Hamburger hidden (`display: none`). | Inline in `__tools` (top-right) and `fr-nav` (below brand). |
**Stories (desktop state, which the previews render):**
| Story | What's visible |
|-------|---------------|
| Minimal | Bloc marque only (Marianne lockup + "INTITULÉ OFFICIEL" + Liberté Égalité Fraternité) |
| Service | Minimal + service name (`__service-title`) + baseline (`__service-tagline`) below a separator |
| Navigation | Service + horizontal `fr-nav` bar below the header body |
| Tool links | Service + `__tools-links` top-right (Contact, Espace recruteur, etc.) |
Canonical anatomy:
- Top border: 4 px solid `border-action-high-blue-france`
- Brand bg: `background-raised-grey` (with `drop-shadow(raised-shadow)`)
- Service title: `1.125rem / 1.5rem` (base), `1.25rem / 1.75rem` (md+), weight 700
- Service tagline: `0.875rem / 1.5rem`, weight 400
- Nav links: `0.875rem / 1.5rem`, weight 400, active = 2 px bottom border in `text-action-high-blue-france`
- Tool links: `0.875rem / 1.5rem`, weight 500, icon + label, color `text-action-high-blue-france`
```html
<!-- Minimal header -->
<header class="fr-header">
<div class="fr-header__body">
<div class="fr-header__body-row">
<div class="fr-header__brand">
<div class="fr-header__brand-top">
<div class="fr-header__logo">
<p class="fr-logo">INTITULÉ<br>OFFICIEL</p>
</div>
</div>
</div>
</div>
</div>
</header>
<!-- Header with service + navigation -->
<header class="fr-header">
<div class="fr-header__body">
<div class="fr-header__body-row">
<div class="fr-header__brand">
<div class="fr-header__brand-top">
<div class="fr-header__logo">
<p class="fr-logo">INTITULÉ<br>OFFICIEL</p>
</div>
</div>
<div class="fr-header__service">
<p class="fr-header__service-title">Nom du site / service</p>
<p class="fr-header__service-tagline">baseline - précisions sur l'organisation</p>
</div>
</div>
</div>
</div>
<nav class="fr-nav" aria-label="Navigation principale">
<ul class="fr-nav__list">
<li class="fr-nav__item"><a class="fr-nav__link" href="#" aria-current="page">Accueil</a></li>
<li class="fr-nav__item"><a class="fr-nav__link" href="#">Démarches</a></li>
<li class="fr-nav__item"><a class="fr-nav__link" href="#">Aide</a></li>
</ul>
</nav>
</header>
```
#### Footer
Unlike the header, the footer (`fr-footer`) is documented as a single canonical composition rather than multiple interaction stories.
Canonical anatomy:
- top blue rule (`4px`) in `border-action-high-blue-france`
- body split in two zones: `__brand` (bloc marque) and `__content` (description + ecosystem links)
- bottom area (`__bottom`) separated by a neutral divider
- mandatory legal links in `__bottom-list` (site map, accessibility, legal notice, privacy, cookies)
- licensing sentence in `__bottom-copy`
Layout behavior:
- desktop: two-column body (`brand` fixed + `content` flexible)
- mobile: stacked blocks; bottom links wrap naturally
- no modal behavior, no hidden navigation state
```html
<footer class="fr-footer" role="contentinfo" id="footer">
<div class="fr-container">
<div class="fr-footer__body">
<div class="fr-footer__brand fr-enlarge-link">
<a title="Retour à l'accueil" href="/">
<p class="fr-logo">Intitulé<br>officiel</p>
</a>
</div>
<div class="fr-footer__content">
<p class="fr-footer__content-desc">Lorem ipsum dolor sit amet…</p>
<ul class="fr-footer__content-list">
<li class="fr-footer__content-item"><a class="fr-footer__content-link" href="https://info.gouv.fr">info.gouv.fr</a></li>
<li class="fr-footer__content-item"><a class="fr-footer__content-link" href="https://service-public.fr">service-public.fr</a></li>
<li class="fr-footer__content-item"><a class="fr-footer__content-link" href="https://legifrance.gouv.fr">legifrance.gouv.fr</a></li>
<li class="fr-footer__content-item"><a class="fr-footer__content-link" href="https://data.gouv.fr">data.gouv.fr</a></li>
</ul>
</div>
</div>
<div class="fr-footer__bottom">
<ul class="fr-footer__bottom-list">
<li class="fr-footer__bottom-item"><a class="fr-footer__bottom-link" href="#">Plan du site</a></li>
<li class="fr-footer__bottom-item"><a class="fr-footer__bottom-link" href="#">Accessibilité</a></li>
<li class="fr-footer__bottom-item"><a class="fr-footer__bottom-link" href="#">Mentions légales</a></li>
<li class="fr-footer__bottom-item"><a class="fr-footer__bottom-link" href="#">Données personnelles</a></li>
<li class="fr-footer__bottom-item"><a class="fr-footer__bottom-link" href="#">Gestion des cookies</a></li>
</ul>
<div class="fr-footer__bottom-copy">
<p>Sauf mention explicite, les contenus sont proposés sous licence etalab-2.0.</p>
</div>
</div>
</div>
</footer>
```
## Do's and Don'ts
### ✅ Do
- Reference **decision tokens** (`text-action-high-blue-france`, `background-default-grey`, `border-default-grey`) in components — never option tokens directly.
- Pair every interactive element with a **visible focus state** drawn with `focus-ring` as an outline.
- Use **`data-fr-scheme`** for theme switching; never hard-code colours per theme.
- Keep **touch targets ≥ 44×44 px** (button `--sm` is 32 px tall but its hit-area is padded to compensate).
- Choose **one illustrative family per page** when accenting editorial content.
- Use Marianne as the workhorse face; reserve Spectral (`fr-text--alt`) for secondary or distinguishing text.
- Lint the `DESIGN.md` against the Stitch CLI before publishing changes (`npx @google/design.md lint DESIGN.md`).
### ❌ Don't
- **Don't** apply Bleu France to titles or to any non-interactive text — it's reserved for actions.
- **Don't** introduce border-radius on buttons, cards, modals, alerts. Square corners are intentional.
- **Don't** mix light-theme and dark-theme tokens in the same context.
- **Don't** use the Marianne logo, the *République Française* lockup, or Bleu France as a brand mark outside an authorised .gouv.fr context.
- **Don't** add resting-state shadows to plain cards — reserve elevation for menus, modals, and hover lift.
- **Don't** use the illustrative palette for actions or system states.
- **Don't** treat Rouge Marianne as a destructive-action red — use the `error` token (`#ce0500`).
- **Don't** colour a heading with `primary` even if it looks attractive — accessibility audits and DSFR conformance both flag it.
---
> **References used to build this file.**
> DSFR documentation — <https://www.systeme-de-design.gouv.fr> ·
> DSFR source — <https://github.com/GouvernementFR/dsfr> ·
> `DESIGN.md` spec — <https://github.com/google-labs-code/design.md>.