tag-03-action-pill-bar

Barre d'actions en pilules, une seule pleine

Aligner filtres et action principale sur une même ligne sans hiérarchie typographique : toutes les commandes sont des pilules de MÊME hauteur, les filtres portent un puits d'icône plus clair que la pilule qui le contient, et UNE SEULE pilule — la dernière — est pleine. C'est la position et le remplissage qui disent le rang, jamais la taille.

tagweb672×62px16 benchmarks

Télécharger le .zip

Le fragment RÉEL, dans son propre document, sur le sol de sa charte — pas une image. Ce que vous voyez est exactement ce que vous collerez.

Employer quand

Ligne de commandes d'un tableau de bord ou d'un rapport, posée à droite du titre de page : une à trois portées de filtre (période, segment, vue) suivies de l'action qui sort de l'écran (exporter, partager, créer). Un filtre peut porter deux lignes — son intitulé au-dessus de sa valeur — sans changer la hauteur de la barre.

Éviter quand

Il y a plus de quatre commandes (la ligne devient une barre d'outils et demande des groupes), deux actions sont de premier rang (deux pilules pleines annulent le seul geste de rang), ou les commandes doivent se replier sous le titre : la barre suppose qu'elle tient sur une ligne, et son alignement à droite n'a plus de sens dès qu'elle passe à la ligne.

Notes

Ce pattern est le seul du corpus où la hiérarchie ne passe NI par la taille NI par la typographie : toutes les pilules ont la même hauteur au pixel près, et c'est le remplissage plus la position finale qui désignent l'action de premier rang. Trois benchmarks tiennent cet équilibre — hauteurs identiques, une seule pilule pleine, et cette pilule est le dernier enfant. Sans la troisième, une pilule pleine posée en tête passerait les deux autres et inverserait la lecture de la ligne. Le puits d'icône reprend exactement le motif de layout-05-nav-pill-bar : un disque plus clair que la surface qui le contient, à gouttière intérieure constante (l'écart au bord gauche égale l'écart au bord haut). Les deux références n'ont rien à voir l'une avec l'autre, et pourtant la même règle y produit le même objet — c'est un motif de composition, pas une signature de charte. Le saut typographique intitulé / valeur est encadré des deux côtés : au moins 1,15 (en dessous, l'intitulé cesse d'être un cadrage et se met à concurrencer la valeur) et au plus 1,6 (au-delà, la pilule se met à ressembler à une carte de KPI, ce qu'elle n'est pas).

Mots-clés

actionfiltrepilulepillbarretoolbardashboardexportperiodesegmentctapuitsicone

Benchmarks — la géométrie, en assertions vérifiables

Rejouables par node bin/check.mjs tag-03-action-pill-bar : le pattern se prouve, il ne se croit pas.

assertionmesureattendu
toutes les pilules ont EXACTEMENT la même hauteur — la taille ne dit jamais le rang(() => { const h = [...document.querySelectorAll('.vl-actionbar > *')].map((e) => e.getBoundingClientRect().height); return Math.max(...h) - Math.min(...h); })()≤ 1
toutes sont des pilules : rayon ≥ demi-hauteur, sans exception(() => { const r = [...document.querySelectorAll('.vl-actionbar > *')].map((e) => parseFloat(getComputedStyle(e).borderTopLeftRadius) / e.getBoundingClientRect().height); return Math.min(...r); })()≥ 0.5
UNE SEULE pilule pleine dans la barre[...document.querySelectorAll('.vl-actionbar > *')].filter((e) => lum(parseRgb(getComputedStyle(e).backgroundColor)) < 0.2).length1 ± 0
… et c'est la DERNIÈRE : l'action de sortie ferme la lignedocument.querySelector('.vl-actionbar').lastElementChild.classList.contains('vl-actionbar__pill--solid') ? 1 : 01
le puits d'icône est un disquebox('.vl-actionbar__well').w / box('.vl-actionbar__well').h1 ± 0.02
le puits est PLUS CLAIR que la pilule qui le porte — le rang de blanc se dépense vers le hautlum(parseRgb(cs('.vl-actionbar__well','backgroundColor'))) - lum(parseRgb(cs('.vl-actionbar__pill','backgroundColor')))≥ 0.01
puits ≈ 74 % de la hauteur de pilule : il loge dedans, il ne la remplit pasbox('.vl-actionbar__well').h / box('.vl-actionbar__pill').h0.742 ± 0.07
gouttière intérieure constante : l'écart du puits au bord gauche égale son écart au bord hautMath.abs((box('.vl-actionbar__well').left - box('.vl-actionbar__pill').left) - (box('.vl-actionbar__well').top - box('.vl-actionbar__pill').top))≤ 1.5
l'intitulé est PLUS PETIT que la valeur qu'il coiffenum('.vl-actionbar__value','fontSize') / num('.vl-actionbar__label','fontSize')≥ 1.15
… mais le saut reste faible : ce n'est pas un titre, c'est un cadragenum('.vl-actionbar__value','fontSize') / num('.vl-actionbar__label','fontSize')≤ 1.6
la pilule pleine n'a PAS de puits : elle n'est pas un filtre déguisédocument.querySelectorAll('.vl-actionbar__pill--solid .vl-actionbar__well').length0 ± 0
la barre reste une ligne : aucune pilule ne descend sous la première(() => { const t = [...document.querySelectorAll('.vl-actionbar > *')].map((e) => e.getBoundingClientRect().top); return Math.max(...t) - Math.min(...t); })()≤ 1
contraste de la valeur d'un filtre ≥ 4,5contrast('.vl-actionbar__value')≥ 4.5
contraste de l'intitulé ≥ 4,5 — c'est lui qui plafonnecontrast('.vl-actionbar__label')≥ 4.5
contraste du libellé de l'action pleine ≥ 4,5contrast('.vl-actionbar__pill--solid')≥ 4.5
aucun débordement de la barreoverflow('.vl-actionbar')≤ 0

Le code

fragment — patterns/tag-03-action-pill-bar.html Télécharger
<!-- tag-03-action-pill-bar — filtres et action principale sur une même ligne.
     Couleurs via --vl-* uniquement.

     LA HIÉRARCHIE NE PASSE NI PAR LA TAILLE NI PAR LA TYPO : toutes les pilules ont la même
     hauteur au pixel près. Ce qui désigne l'action de premier rang, c'est son REMPLISSAGE et
     sa POSITION — dernière de la ligne. Une pilule pleine posée en tête inverserait la
     lecture sans qu'aucune mesure de hauteur ne bouge, d'où le benchmark sur `lastElementChild`. -->
<div class="vl-actionbar">

  <span class="vl-actionbar__pill">
    <span class="vl-actionbar__well" aria-hidden="true">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><rect x="4" y="5.5" width="16" height="14" rx="2.6"/><path d="M4 10h16M8.5 3.5v4M15.5 3.5v4"/></svg>
    </span>
    <span class="vl-actionbar__stack">
      <span class="vl-actionbar__label">Date</span>
      <span class="vl-actionbar__value">28 Jan – 29 Jan 2026</span>
    </span>
    <svg class="vl-actionbar__caret" viewBox="0 0 12 8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M1 1.5 6 6.5l5-5"/></svg>
  </span>

  <span class="vl-actionbar__pill">
    <span class="vl-actionbar__well" aria-hidden="true">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M4 8h16M4 16h16"/><circle cx="9" cy="8" r="2.2"/><circle cx="15" cy="16" r="2.2"/></svg>
    </span>
    <span class="vl-actionbar__stack">
      <span class="vl-actionbar__value">Funnel</span>
    </span>
    <svg class="vl-actionbar__caret" viewBox="0 0 12 8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M1 1.5 6 6.5l5-5"/></svg>
  </span>

  <span class="vl-actionbar__pill vl-actionbar__pill--solid">
    Export as CVS
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7M8.5 7H17v8.5"/></svg>
  </span>

</div>

<style>
.vl-actionbar {
  display: flex; align-items: center; gap: 12px;
  font-family: Montserrat, Poppins, Inter, "Helvetica Neue", Arial, sans-serif;
}
.vl-actionbar__pill {
  box-sizing: border-box;
  height: 62px; padding: 0 22px 0 8px;
  display: flex; align-items: center; gap: 14px;
  background: var(--vl-module);
  color: var(--vl-ink);
  border-radius: var(--vl-radius-pill, 999px);
}
/* Le puits : un disque PLUS CLAIR que la pilule qui le porte, à gouttière intérieure
   constante (son écart au bord gauche vaut son écart au bord haut). C'est exactement le
   motif de layout-05-nav-pill-bar, sur une tout autre référence — un motif de composition,
   pas une signature de charte. */
.vl-actionbar__well {
  flex: 0 0 auto;
  width: 46px; height: 46px; border-radius: 50%;
  background: var(--vl-app);
  display: flex; align-items: center; justify-content: center;
}
.vl-actionbar__well svg { width: 19px; height: 19px; }
.vl-actionbar__stack { display: flex; flex-direction: column; justify-content: center; }
.vl-actionbar__label { font-size: 13px; line-height: 1.25; color: var(--vl-ink-soft); }
.vl-actionbar__value { font-size: 16px; line-height: 1.25; font-weight: 500; white-space: nowrap; }
.vl-actionbar__caret { width: 12px; height: 8px; margin-left: 4px; }
/* L'unique pilule pleine. Elle n'a PAS de puits : un puits en ferait un filtre déguisé, et
   la ligne perdrait la seule chose qui distingue une commande d'une portée. */
.vl-actionbar__pill--solid {
  background: var(--vl-invert); color: var(--vl-on-invert);
  padding: 0 26px; gap: 10px;
  font-size: 17px; font-weight: 500;
}
.vl-actionbar__pill--solid svg { width: 15px; height: 15px; }
</style>
tokens de la charte — systems/ref-18-lime-sales-dashboard.json Télécharger
:root {
  --vl-app: #FEFEFE;
  --vl-module: #F2F2F2;
  --vl-module-warm: #FBF9EC;
  --vl-invert: #000000;
  --vl-accent: #5FE85C;
  --vl-ink: #0B0B0B;
  --vl-ink-soft: #6B6B6B;
  --vl-on-invert: #FFFFFF;
  --vl-on-invert-soft: rgba(255,255,255,0.66);
  --vl-hairline: #E6E6E6;
  --vl-ramp-1: #E3E3E3;
  --vl-ramp-2: #CCCCCC;
  --vl-ramp-3: #999999;
  --vl-ramp-4: #666666;
  --vl-ramp-5: #000000;
  --vl-radius-module: 29px;
  --vl-radius-tile: 20px;
  --vl-radius-pill: 999px;
  --vl-gutter: 16px;
  --vl-gutter-row: 11px;
  --vl-rail-w: 95px;
  --vl-margin: 30px;
  --vl-header-h: 69px;
}
les métadonnées — patterns/tag-03-action-pill-bar.json (télécharger)
{
  "name": "Barre d'actions en pilules, une seule pleine",
  "family": "tag",
  "ref": "ref-18-lime-sales-dashboard",
  "intent": "Aligner filtres et action principale sur une même ligne sans hiérarchie typographique : toutes les commandes sont des pilules de MÊME hauteur, les filtres portent un puits d'icône plus clair que la pilule qui le contient, et UNE SEULE pilule — la dernière — est pleine. C'est la position et le remplissage qui disent le rang, jamais la taille.",
  "when_to_use": "Ligne de commandes d'un tableau de bord ou d'un rapport, posée à droite du titre de page : une à trois portées de filtre (période, segment, vue) suivies de l'action qui sort de l'écran (exporter, partager, créer). Un filtre peut porter deux lignes — son intitulé au-dessus de sa valeur — sans changer la hauteur de la barre.",
  "avoid_when": "Il y a plus de quatre commandes (la ligne devient une barre d'outils et demande des groupes), deux actions sont de premier rang (deux pilules pleines annulent le seul geste de rang), ou les commandes doivent se replier sous le titre : la barre suppose qu'elle tient sur une ligne, et son alignement à droite n'a plus de sens dès qu'elle passe à la ligne.",
  "media": [
    "web"
  ],
  "tags": [
    "action",
    "filtre",
    "pilule",
    "pill",
    "barre",
    "toolbar",
    "dashboard",
    "export",
    "periode",
    "segment",
    "cta",
    "puits",
    "icone"
  ],
  "vars": [
    {
      "name": "--vl-module",
      "role": "aplat d'une pilule de filtre",
      "default": "#F2F2F2"
    },
    {
      "name": "--vl-app",
      "role": "aplat du puits d'icône — plus clair que la pilule qui le porte",
      "default": "#FEFEFE"
    },
    {
      "name": "--vl-invert",
      "role": "aplat de l'unique pilule pleine",
      "default": "#000000"
    },
    {
      "name": "--vl-ink",
      "role": "valeur d'un filtre, libellé de l'action",
      "default": "#0B0B0B"
    },
    {
      "name": "--vl-ink-soft",
      "role": "intitulé d'un filtre, au-dessus de sa valeur",
      "default": "#6B6B6B"
    },
    {
      "name": "--vl-on-invert",
      "role": "encre sur la pilule pleine",
      "default": "#FFFFFF"
    },
    {
      "name": "--vl-radius-pill",
      "role": "rayon — toujours une pilule pleine, jamais un rayon franc",
      "default": "999px"
    }
  ],
  "slots": [
    "filters",
    "action"
  ],
  "geometry": {
    "root": ".vl-actionbar",
    "frame": [
      672,
      62
    ],
    "ratios": {
      "pill_height_over_width": 0.092,
      "well_over_pill_height": 0.742,
      "gap_over_pill_height": 0.194,
      "value_over_label": 1.23
    },
    "type_px": {
      "value": 16,
      "label": 13,
      "action": 17
    },
    "pad_ratio": {
      "top": 0,
      "x": 0,
      "bottom": 0
    }
  },
  "benchmarks": [
    {
      "name": "toutes les pilules ont EXACTEMENT la même hauteur — la taille ne dit jamais le rang",
      "measure": "(() => { const h = [...document.querySelectorAll('.vl-actionbar > *')].map((e) => e.getBoundingClientRect().height); return Math.max(...h) - Math.min(...h); })()",
      "max": 1
    },
    {
      "name": "toutes sont des pilules : rayon ≥ demi-hauteur, sans exception",
      "measure": "(() => { const r = [...document.querySelectorAll('.vl-actionbar > *')].map((e) => parseFloat(getComputedStyle(e).borderTopLeftRadius) / e.getBoundingClientRect().height); return Math.min(...r); })()",
      "min": 0.5
    },
    {
      "name": "UNE SEULE pilule pleine dans la barre",
      "measure": "[...document.querySelectorAll('.vl-actionbar > *')].filter((e) => lum(parseRgb(getComputedStyle(e).backgroundColor)) < 0.2).length",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "… et c'est la DERNIÈRE : l'action de sortie ferme la ligne",
      "measure": "document.querySelector('.vl-actionbar').lastElementChild.classList.contains('vl-actionbar__pill--solid') ? 1 : 0",
      "expect": 1
    },
    {
      "name": "le puits d'icône est un disque",
      "measure": "box('.vl-actionbar__well').w / box('.vl-actionbar__well').h",
      "expect": 1,
      "tol": 0.02
    },
    {
      "name": "le puits est PLUS CLAIR que la pilule qui le porte — le rang de blanc se dépense vers le haut",
      "measure": "lum(parseRgb(cs('.vl-actionbar__well','backgroundColor'))) - lum(parseRgb(cs('.vl-actionbar__pill','backgroundColor')))",
      "min": 0.01
    },
    {
      "name": "puits ≈ 74 % de la hauteur de pilule : il loge dedans, il ne la remplit pas",
      "measure": "box('.vl-actionbar__well').h / box('.vl-actionbar__pill').h",
      "expect": 0.742,
      "tol": 0.07
    },
    {
      "name": "gouttière intérieure constante : l'écart du puits au bord gauche égale son écart au bord haut",
      "measure": "Math.abs((box('.vl-actionbar__well').left - box('.vl-actionbar__pill').left) - (box('.vl-actionbar__well').top - box('.vl-actionbar__pill').top))",
      "max": 1.5
    },
    {
      "name": "l'intitulé est PLUS PETIT que la valeur qu'il coiffe",
      "measure": "num('.vl-actionbar__value','fontSize') / num('.vl-actionbar__label','fontSize')",
      "min": 1.15
    },
    {
      "name": "… mais le saut reste faible : ce n'est pas un titre, c'est un cadrage",
      "measure": "num('.vl-actionbar__value','fontSize') / num('.vl-actionbar__label','fontSize')",
      "max": 1.6
    },
    {
      "name": "la pilule pleine n'a PAS de puits : elle n'est pas un filtre déguisé",
      "measure": "document.querySelectorAll('.vl-actionbar__pill--solid .vl-actionbar__well').length",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "la barre reste une ligne : aucune pilule ne descend sous la première",
      "measure": "(() => { const t = [...document.querySelectorAll('.vl-actionbar > *')].map((e) => e.getBoundingClientRect().top); return Math.max(...t) - Math.min(...t); })()",
      "max": 1
    },
    {
      "name": "contraste de la valeur d'un filtre ≥ 4,5",
      "measure": "contrast('.vl-actionbar__value')",
      "min": 4.5
    },
    {
      "name": "contraste de l'intitulé ≥ 4,5 — c'est lui qui plafonne",
      "measure": "contrast('.vl-actionbar__label')",
      "min": 4.5
    },
    {
      "name": "contraste du libellé de l'action pleine ≥ 4,5",
      "measure": "contrast('.vl-actionbar__pill--solid')",
      "min": 4.5
    },
    {
      "name": "aucun débordement de la barre",
      "measure": "overflow('.vl-actionbar')",
      "max": 0
    }
  ],
  "notes": "Ce pattern est le seul du corpus où la hiérarchie ne passe NI par la taille NI par la typographie : toutes les pilules ont la même hauteur au pixel près, et c'est le remplissage plus la position finale qui désignent l'action de premier rang. Trois benchmarks tiennent cet équilibre — hauteurs identiques, une seule pilule pleine, et cette pilule est le dernier enfant. Sans la troisième, une pilule pleine posée en tête passerait les deux autres et inverserait la lecture de la ligne.\n\nLe puits d'icône reprend exactement le motif de layout-05-nav-pill-bar : un disque plus clair que la surface qui le contient, à gouttière intérieure constante (l'écart au bord gauche égale l'écart au bord haut). Les deux références n'ont rien à voir l'une avec l'autre, et pourtant la même règle y produit le même objet — c'est un motif de composition, pas une signature de charte.\n\nLe saut typographique intitulé / valeur est encadré des deux côtés : au moins 1,15 (en dessous, l'intitulé cesse d'être un cadrage et se met à concurrencer la valeur) et au plus 1,6 (au-delà, la pilule se met à ressembler à une carte de KPI, ce qu'elle n'est pas).",
  "id": "tag-03-action-pill-bar"
}

À côté