République
française
dsfr-data
Documentation — Web Components DSFR pour la dataviz
Affiche une valeur clé (KPI) avec formatage, couleurs conditionnelles basees sur des seuils, et icones optionnelles.
| Attribut | Type | Description |
|---|---|---|
source | String | Id de la source (ou du transformateur) dont ce KPI consomme les données. Facultatif si value est un littéral (value="=667"). |
value | String | Expression de valeur — convention cible anglaise (#300). Grammaire commune "champ:fn" (#303), ex. value="population:sum". champ:distinct (alias count-distinct, #672) : nombre de valeurs distinctes, null et chaîne vide exclus, calculé sur les lignes reçues. meta:total (#659) : total publié par l'amont (total serveur en server-side, lignes avant limit derrière un query) — count ne compte que les lignes reçues. Total inconnu de l'amont (page serveur ou lot tronqué sans total, #1046) : « — ». Ratio (#673) : value="count:statut:ouvert / count", chaque côté dans la grammaire ci-dessus (meta:total compris). Résultat = fraction (0,35) ; format="pourcentage" la rend en pourcentage (35 %) — les seuils s'expriment alors en pourcentage aussi. Division par zéro : « — ». count:champ:valeur accepte un champ tableau (un élément égal suffit). Depuis #953, le where ci-dessous et le filtre entre accolades count{tags:eq:urgent} font PAREIL : value="count:tags:urgent" et value="count{tags:eq:urgent}" rendent le MÊME chiffre. L'asymétrie de #842 — deux chiffres sur le même jeu, et c'était « voulu » — a disparu : l'égalité client est alignée sur celle du portail, qui lit déjà = sur un champ multivalué comme un « contient » (mesuré le 2026-09-19). Seul count accepte une valeur de filtre : sum:champ:valeur est une erreur de configuration (#764). Filtre propre à une expression (#776), dialecte du where entre accolades : value="effectif:sum{sexe:eq:F} / effectif:sum" rend une part de SOMMES — le filtre ne vaut que pour son côté du ratio, là où where filtre les deux. Marche aussi pour count{…} et les autres fonctions ; un filtre non reconnu est une erreur de configuration. Le contenu des accolades est lu d'un bloc : une valeur qui contient / ne coupe pas le ratio (#839). champ:first / champ:last : valeur du champ sur la première / la dernière ligne, DANS L'ORDRE COURANT — poser un order-by en amont (ex. dernière valeur d'une série datée). Propres au KPI : absentes de l'aggregate de dsfr-data-query. champ:evolution (#675) : (dernière − première) / première sur les lignes DANS LEUR ORDRE COURANT — poser un order-by chronologique en amont. Fraction, rendue en pourcentage par format="pourcentage", trend et lines ; « — » si moins de deux valeurs ou première = 0. |
heading | String | Titre affiché AU-DESSUS de la valeur (surtitre, style majuscules grises). Nommé heading et non title : ce dernier entrerait en collision avec la propriété DOM native HTMLElement.title (infobulle). |
label | String | Libellé affiché sous le chiffre (et sous les lines) |
description | String | Description détaillée pour l'accessibilité |
trend | String | RACCOURCI HERITE — pour une ligne d'evolution riche (signe, suffixe, couleur, repli n.d.), preferez lines. Conserve pour compatibilite. Expression d'agrégation pour la tendance, évaluée sur les données de la source (grammaire commune "champ:fn", ex. "evolution:avg") — PAS un litteral : l'ancienne doc ("+3.2") laissait croire qu'on passait une valeur, la chaîne etait interpretee comme nom de champ (#303). Rendue avec une fleche (↑/↓) en pourcentage fr-FR ("↑ 5,2 %"). trend="recettes:evolution" (#675) : taux d'évolution entre la première et la dernière ligne, rendu en pourcentage. |
lines | String | Lignes secondaires declaratives (JSON), rendues ENTRE la valeur et le label. Chaque item est soit data-driven (value = expression "champ:fn"), soit texte statique (text), avec couleur declarative. Ex. [{"value":"evol:avg","sign":true,"suffix":"vs mai 2025","color":"auto"}]. Schema complet : packages/core/src/utils/kpi-lines.ts (KpiLineSpec). |
format | FormatType | Format d'affichage : nombre (défaut), pourcentage, euro, decimal, compact (14 785 684 → « 14,8 M »), date (chaîne ISO → « 09/09/2026 », #667). Les décimales passent par decimals, jamais par le format (euro:3 est refusé et affiché comme erreur de configuration, #665). |
unit | String | Unité accolée après la valeur (espace insécable), ex. format="compact" unit="€" → « 44,9 Md € ». Surtout utile avec nombre, decimal et compact — euro et pourcentage portent déjà leur symbole (#665). |
decimals | Number | Nombre de décimales affichées (entier 0 à 20), ex. format="euro" decimals="3" → « 1,749 € ». Fixe pour nombre, pourcentage, euro et decimal ; plafond pour compact ; sans effet sur date. Absent : défaut historique du format (#665). |
icon | String | Classe d'icône DSFR (fr-icon-leaf-line) ou Remix (ri-global-line). Une CLASSE, jamais du balisage : la valeur doit suivre ^(fr-icon|ri)-[a-z0-9-]+$, sinon elle est ignorée avec un avertissement console qui la nomme (une fois par valeur). Placement et taille : icon-position, icon-size. Remplacée par picto si les deux sont posés. |
icon-position | IconPosition | string | Où se place l'icône (ou le pictogramme) : label (défaut, rendu historique : entre le surtitre et la valeur, en gris), top (en tête de la carte, dans la couleur de l'accent — vignette 1a), right (à droite, alignée en haut, le texte garde sa marge — vignette 1b). Sans effet sans icon ni picto. |
icon-size | IconSize | string | Taille de l'icône : sm = 1,5 rem (24 px — sans attribut, c'est le rendu historique, inchangé) ou md = 2 rem (32 px). L'échelle s'arrête là : l'échelle documentée du DSFR s'arrête à fr-icon--lg = 2 rem, et au-delà le DSFR ne parle plus d'icône mais de PICTOGRAMME — pour une illustration de 48 ou 80 px, poser picto, pas une icône agrandie. Ni lg, ni valeur en pixels : une autre valeur est ignorée avec un avertissement. Vaut aussi pour picto, sur l'échelle des tuiles DSFR : sm = 3,5 rem, md = 5 rem (sans attribut : md, la tuile DSFR standard). Pour une icône fr-icon-*, la taille passe par --icon-size (le glyphe est un ::before masqué, indifférent à font-size). |
picto | String | Pictogramme DSFR illustratif (fr-artwork), par son NOM : environment/leaf, buildings/city-hall… (dossier de catégorie + fichier, sans .svg). Contraint à ^[a-z0-9-]+(/[a-z0-9-]+)*$ — ce motif exclut ../ et tout schéma sans assainisseur. Le composant rend le SVG canonique à trois <use> (#artwork-decorative, #artwork-minor, #artwork-major) dont l'adresse est picto-base + nom + .svg : picto-base est OBLIGATOIRE (sans lui, rien n'est rendu, avec un avertissement). Les couleurs viennent des classes fr-artwork-* du DSFR — le mode sombre suit sans travail — et une couleur illustrative (color-token) est reportée en fr-artwork--<nom>. ⚠️ <use href> vers un AUTRE domaine n'est pas rendu par les navigateurs (pas de CORS sur use) : picto-base doit servir les SVG depuis l'origine de la page (copie locale de dist/artwork/pictograms/), pas depuis un CDN. Prime sur icon. Mêmes icon-position et icon-size que l'icône. |
picto-field | String | Même chose que picto, mais le nom est lu dans un CHAMP de la première ligne reçue (picto-field="theme_picto") — utile dans un répéteur. Même motif, même refus. picto prime s'il est posé. |
picto-base | String | Préfixe d'adresse des pictogrammes, écrit par l'intégrateur : picto-base="/dsfr/artwork/pictograms/". Le nom (picto) y est concaténé (barre finale ajoutée si absente). C'est ce découpage nom / base qui rend picto sûr par construction. Même origine que la page, voir picto. |
image | String | Image libre (photo, logo) par son URL. Passée par la même liste blanche de schémas que le format {{champ:url}} des gabarits (http:, https:, mailto:, tel: ou relative) : une URL refusée (javascript:, data:…) n'affiche rien et avertit. Placement : image-position. Texte alternatif : image-alt (vide = décorative). Rendue seulement quand la donnée est là. |
image-alt | String | Texte alternatif de image. Vide (défaut) : image décorative (alt=""). |
image-position | ImagePosition | string | Placement de image : top (défaut, bandeau 16:9 bord à bord au-dessus du contenu — vignette 1d), left (colonne de 10 rem pleine hauteur, le liseré reste à gauche de l'image — 2a), right (vignette carrée de 7,5 rem dans la marge, à droite du texte — 2b). |
orientation | horizontal · vertical | horizontal (défaut, carte à liseré gauche) ou vertical : tuile à liseré HAUT (sauf border explicite). Avec une icône, un pictogramme ou une image, tout passe au-dessus du surtitre et le texte est centré (vignette 1f) ; sans média, le KPI reste aligné à gauche — la version sobre des chiffres-clés éditoriaux (1g). Sur dsfr-data-kpi-group, le même attribut empile les KPI (voir le groupe). |
border | BorderMode | string | Tracé du liseré ; sa couleur reste celle de color-token ou des seuils. left (défaut, 4 px, rendu historique), top (4 px), bottom (filet de 2 px, comme les champs DSFR), outline (contour de 1 px), left-short (4 px à hauteur de la valeur), none. Autre valeur : ignorée, avertissement. |
tint | String | Fond teinté dans la couleur du token : tint (ou tint="true") prend le fond 950 (--background-contrast-<nom>) ; tint="975" le fond le plus clair (--background-alt-<nom>) ; tint="925" le plus soutenu (--<nom>-925-125). Les 4 tokens sémantiques n'ont pas de 925 dans le DSFR : replié sur 950 avec un avertissement. La VALEUR reste en gris titre (--text-title-grey) : les teintes pleines claires (tournesol, café-crème, galet) ne tiennent pas le contraste pour du texte — planche 3b. Le surtitre et le libellé passent en --text-default-grey pour la même raison. Se combine avec border (souvent border="none"). |
color-token | KpiColor | '' | Couleur forcée. Deux familles, deux SENS : - les 4 tokens sémantiques vert, orange, rouge, bleu disent un ÉTAT (bon, attention, critique, neutre) — c'est aussi ce que posent les seuils, et ce que le libellé accessible annonce (« etat bon ») ; - les 17 couleurs illustratives DSFR (green-emeraude, blue-cumulus, purple-glycine, orange-terre-battue… liste : ILLUSTRATIVE_COLOR_TOKENS) disent une CATÉGORIE — thème, ministère, famille de données — et n'annoncent aucun état. Un KPI en rouge illustratif (pink-tuile) qui ne veut pas dire « mauvais » est un contresens de lecture : l'état reste aux tokens sémantiques. Une couleur illustrative pose le liseré et l'icône en teinte pleine via var(--border-plain-<nom>) et, avec tint, le fond via var(--background-contrast-<nom>) — tokens DSFR existants, aucun hexadécimal : le mode sombre suit. Nom inconnu : ignoré avec avertissement, repli sur les seuils puis bleu. |
threshold-green | Number | Seuil au-dessus duquel la valeur est verte |
threshold-orange | Number | Seuil au-dessus duquel la valeur est orange |
span | String | Largeur sur la grille de 12 colonnes (1-12), dans un <dsfr-data-kpi-group> : span="6" occupe la moitié de la ligne. Remplace col, même sens (#790) ; prime sur col s'ils sont posés ensemble. Sans valeur par défaut, et pour la même raison que col : la propriété est reflétée, donc une valeur initiale '' poserait span="" sur CHAQUE KPI. La largeur par défaut du groupe est portée par une règle ::slotted(*:not([col]):not([span])) — un attribut vide, mais présent, la désactive et tous les KPI retombent en grid-column: auto (#822). |
col | Number | Largeur en colonnes DSFR (1-12). Significatif uniquement dans un <dsfr-data-kpi-group>. Même rôle que span, qui est préféré (#790) ; toujours accepté. |
where | String | Filtre des lignes AVANT le calcul (#674), dialecte colon de dsfr-data-query : where="categorie:eq:Actif, montant:gte:1000" — mêmes 12 opérateurs (eq, neq, gt, gte, lt, lte, contains, notcontains, in, notin, isnull, isnotnull), même égalité lâche, chemins imbriqués acceptés. Appliqué à value, trend et lines. CÔTÉ CLIENT SEULEMENT : le KPI ne délègue rien au serveur, le filtre porte sur les lignes reçues (derrière un limit ou une page, poser le where sur la source ou une query amont). meta:total n'en tient pas compte. Une clause non reconnue est une erreur de configuration. CHAMP TABLEAU (#953, ex-#842) : eq / in regardent DANS le tableau. where="tags:eq:urgent" retient une ligne dont tags vaut ['urgent','social'], exactement comme value="count:tags:urgent" la compte, et comme le portail la retiendrait sur une clause déléguée. Le repli textuel est gardé en OU : ['a','b'] matche encore 'a,b' côté client, là où le portail rend 0 — le client ne peut donc que gagner des lignes, jamais en perdre. neq / notin, étant la négation, en perdent (le portail aussi : son != est la négation stricte de son =). ⚠️ Le KPI ne délègue jamais : son where évalue toujours la voie client. Depuis l'alignement, c'est sans conséquence sur un champ tableau — un KPI et un graphique portant le même where rendent le même chiffre, au repli textuel près. Le booléen dérivé en amont (dsfr-data-normalize compute="a_urgent = when contains(tags,'urgent') then 1 else 0", puis where="a_urgent:eq:1") reste valide et garde un intérêt — le filtre final porte sur un scalaire, donc regroupable et délégable — mais il n'est plus NÉCESSAIRE. |
idle-message | String | Message rendu quand l'amont attend un filtre (require-where, #690). Distinct de « aucune donnée » : aucune requête n'a été faite. Vide, le libellé par défaut est utilisé. |
Les anciens noms francais (valeur, icone, couleur, seuil-vert, seuil-orange, tendance) restent lus en alias deprecies (#300).
Un chiffre est le pire endroit où afficher un zéro qui n'en est pas un. Quand l'amont porte
require-where (<dsfr-data-source> ou <dsfr-data-query>),
aucune requête n'est encore partie : le KPI rend le texte de idle-message
(« Choisissez un filtre pour afficher les données » par défaut) au lieu d'une valeur. Distinct de
« aucune donnée », qui signalerait une requête revenue vide. Le mécanisme est décrit sur
dsfr-data-source.
| Attribut | Type | Description |
|---|---|---|
aria-label | String | Label accessible du groupe |
per-row | String | Nombre de KPI par ligne à partir de 768 px (en dessous : un par ligne) — 1, 2, 3, 4, 6 ou 12, les diviseurs de la grille. Échelle mobile-first (#789) : per-row="2 md:4" — deux KPI par ligne sur téléphone, quatre à partir de 768 px ; avec un terme de base ou un palier sm, le repli forcé sur une colonne ne s'applique plus. Remplace cols, même sens (#790) ; prime sur cols s'ils sont posés ensemble. Un KPI qui porte span (ou col) garde sa propre largeur. |
cols | Number | Nombre de KPI par ligne par défaut (1-12). Chaque enfant occupe Math.floor(12/cols) colonnes. Même rôle que per-row, qui est préféré : cols désigne une LARGEUR sur dsfr-data-facets (#790). Toujours accepté, avec le même sens. |
gap | sm · md · lg | Espacement entre KPIs : sm (0.5rem), md (1rem), lg (1.5rem) |
orientation | horizontal · vertical | horizontal (défaut : la grille 12 colonnes) ou vertical : les KPI sont EMPILÉS en colonne dans un seul cadre — un liseré gauche continu porté par le groupe, un filet entre les items, valeur à 2 rem, icône ou pictogramme à 2,5 rem à gauche du texte (planche kpi-evolutions, 1e). Compact, pour une barre latérale ou un encart. per-row, cols, span et gap sont sans effet dans ce mode. Les KPI enfants perdent leur propre liseré (règle portée par dsfr-data-kpi, sur le sélecteur dsfr-data-kpi-group[orientation="vertical"]) — ils sont lus au rendu : un changement d'orientation APRÈS le montage ne les remet pas en forme. Le liseré du groupe est bleu info ; il ne suit pas le color-token des enfants. |
champ : Valeur directe d'un champ (pour objet unique)champ:avg : Moyenne des valeurs du champchamp:sum : Somme des valeurschamp:min / champ:max : Min/maxcount : Compte tous les enregistrementscount:champ:valeur : Compte les occurrences ou champ = valeur=littéral : Valeur littérale affichée telle quelle, sans source ni calcul. Un littéral numérique (value="=667", virgule décimale française acceptée) passe par le format ; une chaîne (value="=87 %") est affichée sans formatage.Grammaire commune du pipeline champ:fn (#303) — l'ancienne forme inversee fn:champ reste lue mais est depreciee.
| Format | Exemple (valeur : 32073247.5) | Description |
|---|---|---|
nombre | 32 073 248 | Entier avec separateurs de milliers (arrondi) |
euro | 32 073 248 € | Montant en euros avec separateurs de milliers |
pourcentage | 75,5 % | Pourcentage (divise par 100, 1 decimale max) |
decimal | 32 073 247,50 | Nombre decimal (1 a 2 decimales) |
compact | 32,1 M | Notation compacte fr-FR (v0.16.0) : 14 800 000 → « 14,8 M », 6 700 → « 6,7 k » (Intl compact, 1 décimale max) |
Tous les formats utilisent la locale francaise (separateur de milliers = espace, decimales = virgule).
<dsfr-data-kpi source="meta" value="total" label="Sites suivis" icon="ri-global-line" format="nombre"> </dsfr-data-kpi>
<dsfr-data-kpi source="sites" value="score_rgaa:avg" label="Score RGAA moyen" format="pourcentage" threshold-green="80" threshold-orange="50"> </dsfr-data-kpi>
<dsfr-data-kpi source="sites" value="count:statut:actif" label="Sites actifs" icon="ri-checkbox-circle-line" color-token="vert"> </dsfr-data-kpi>
<dsfr-data-kpi source="sites" value="score_rgaa:max" label="Meilleur score" format="pourcentage" color-token="vert"></dsfr-data-kpi> <dsfr-data-kpi source="sites" value="score_rgaa:min" label="Plus bas score" format="pourcentage" color-token="rouge"></dsfr-data-kpi>
heading = surtitre au-dessus de la valeur ; lines = ligne secondaire data-driven (signe +, suffixe, couleur « auto » verte si hausse) ; label = légende en bas. Source mono-objet (un seul enregistrement courant) supportée.
<dsfr-data-source id="baro" data='[{"immat_ve":37849,"immat_ve_evol":92.5}]'></dsfr-data-source> <dsfr-data-kpi source="baro" heading="Immat. VE — véhicules particuliers" value="immat_ve" format="nombre" lines='[{"value":"immat_ve_evol","sign":true,"suffix":"vs mai 2025","color":"auto"}]' label="Donnée mai 2026"> </dsfr-data-kpi>
<dsfr-data-kpi-group cols="4"> <dsfr-data-kpi source="meta" value="total" label="Sites suivis"></dsfr-data-kpi> <dsfr-data-kpi source="sites" value="score_rgaa:avg" label="RGAA moyen"></dsfr-data-kpi> <dsfr-data-kpi source="sites" value="score_dsfr:avg" label="DSFR moyen"></dsfr-data-kpi> <dsfr-data-kpi source="sites" value="count:certificat_valide:false" label="Expires"></dsfr-data-kpi> </dsfr-data-kpi-group>
<dsfr-data-kpi-group> <dsfr-data-kpi source="meta" value="total" label="Sites suivis" col="6"></dsfr-data-kpi> <dsfr-data-kpi source="sites" value="score_rgaa:avg" label="RGAA moyen" col="3"></dsfr-data-kpi> <dsfr-data-kpi source="sites" value="score_rgaa:max" label="Meilleur RGAA" col="3"></dsfr-data-kpi> </dsfr-data-kpi-group>
<dsfr-data-kpi-group cols="2" gap="lg"> <dsfr-data-kpi source="sites" value="score_rgaa:min" label="Plus bas RGAA" color-token="rouge"></dsfr-data-kpi> <dsfr-data-kpi source="sites" value="score_rgaa:max" label="Meilleur RGAA" color-token="vert"></dsfr-data-kpi> </dsfr-data-kpi-group>
Évolutions d'affichage de la planche « kpi-evolutions » (tours 1 à 3). Rien côté données,
et rien sans les nouveaux attributs : sans eux, le rendu est celui des exemples 1 à 9,
byte à byte (tests/kpi-rendu-retrocompat.test.ts). Les pictogrammes sont servis depuis
specs/assets/pictograms/ — même origine que la page : un <use href> vers
un CDN n'est pas rendu par les navigateurs.
1a et 1b : la planche montrait une icône de 48 et 64 px ; l'échelle d'icon-size s'arrête à
md = 2 rem (la plus grande icône documentée du DSFR), donc ces deux vignettes se rendent avec
un pictogramme. 1c : l'icône dans le libellé est le rendu par défaut, inchangé.
1a · picto="environment/leaf" picto-base="…" icon-position="top" icon-size="sm"
1b · picto="buildings/city-hall" icon-position="right"
1c · icon="ri-truck-line" (rendu par défaut)
1d · image="…" image-alt="" (bandeau 16:9)
<dsfr-data-kpi source="evo" heading="Immatriculations" value="immat" label="…" picto="environment/leaf" picto-base="../assets/pictograms/" icon-position="top" icon-size="sm"></dsfr-data-kpi> <dsfr-data-kpi source="evo" heading="Part de marché" value="pdm" format="pourcentage" label="…" picto="buildings/city-hall" picto-base="../assets/pictograms/" icon-position="right"></dsfr-data-kpi> <dsfr-data-kpi source="evo" heading="Poids lourds" value="pl" format="pourcentage" label="…" icon="fr-icon-truck-line" color-token="vert"></dsfr-data-kpi> <dsfr-data-kpi source="evo" heading="Pompes à chaleur" value="pac" label="…" image="../assets/kpi-image-demo.svg" image-alt=""></dsfr-data-kpi>
1e · <dsfr-data-kpi-group orientation="vertical">
1f · orientation="vertical" picto="environment/sun" (tuile centrée)
1g · orientation="vertical" (sans picto : aligné à gauche)
2a · image-position="left"
2b · image-position="right"
2c · border="left"
2d · border="top"
2e · border="bottom"
2f · border="outline"
2g · border="left-short"
2h · border="none" tint
2i · border="none"
Une couleur illustrative dit une catégorie (thème, ministère, famille de données) ;
l'état reste aux quatre tokens sémantiques et aux seuils. Liseré et icône :
var(--border-plain-<nom>) ; fond teinté : var(--background-contrast-<nom>).
Aucun hexadécimal, le mode sombre suit. Sous tint, la valeur reste en gris titre.
3a · color-token="green-emeraude" icon="fr-icon-leaf-line" icon-position="top"
3b · color-token="green-emeraude" tint border="none"
3c · les 17 valeurs acceptées — liseré (teinte pleine) et fond (‑950)
<dsfr-data-kpi source="evo" heading="Environnement" value="env" format="pourcentage" label="…" color-token="green-emeraude" icon="fr-icon-leaf-line" icon-position="top"></dsfr-data-kpi> <dsfr-data-kpi … color-token="green-emeraude" tint border="none"></dsfr-data-kpi> <dsfr-data-kpi … color-token="brown-cafe-creme" tint="975"></dsfr-data-kpi>