layout-05-nav-pill-bar

Barre de menu en pilule flottante

Poser la navigation d'une page comme un OBJET posé dans la marge — un aplat arrondi qui flotte au lieu d'un bandeau collé au bord — et n'y employer qu'une seule ressource de hiérarchie : le rang de blanc, le badge de marque et le bouton d'action étant plus CLAIRS que la barre qui les porte.

layoutweb1326×69px16 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

En-tête d'une landing ou d'une application dont le fond est déjà clair, quand la navigation ne doit pas se lire comme un chrome de navigateur : la barre reste dans la marge de page, à la même distance des bords que les modules qui la suivent, et le premier module commence juste dessous.

Éviter quand

La page défile sous un en-tête collant (une pilule flottante qui se fige au bord perd exactement ce qui la justifie), le fond est sombre ou photographique (le rang de blanc n'a plus de sens : le blanc devient la couleur la plus forte de l'écran), ou la navigation compte plus de cinq entrées — au-delà, les liens rattrapent le bouton et la respiration qui fait l'objet disparaît.

Notes

Le geste qui fait ce pattern n'est ni le rayon ni la pilule : c'est le RANG DE BLANC. La barre est un gris de module, le badge et le bouton sont d'un blanc plus clair que la PAGE elle-même. Deux benchmarks le mesurent en luminance relative parce que c'est la seule formulation qui survive à un changement de charte — dire « le bouton est blanc » serait faux dès qu'on thème le pattern en sombre, dire « le bouton est plus clair que la barre » reste vrai partout. Le rayon est tenu entre 18 % et 45 % de la hauteur, et cette borne haute est le vrai contrôle : à 50 % la barre devient une pilule pleine, ce qui est une autre forme et un autre système (les liens semblent alors flotter dans un tube). Le bouton, lui, est bien une pilule — la dissymétrie entre les deux est délibérée. L'assertion « l'écart du badge au bord gauche égale son écart au bord haut » vaut mieux qu'une mesure de padding : elle attrape aussi le cas où le badge est centré verticalement mais collé à gauche, qui passe tous les contrôles de padding et casse pourtant la silhouette. L'encre des liens est posée à #5F5F5F et non au #6E6E6E relevé sur la source : sur le gris de module #EDEDED, le relevé plafonne à 4,09:1. Écart invisible à l'œil, faute de contraste évitée.

Mots-clés

navigationmenubarreheaderentetepilulelandinglogowordmarkboutonctamonochromewebapp

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

Rejouables par node bin/check.mjs layout-05-nav-pill-bar : le pattern se prouve, il ne se croit pas.

assertionmesureattendu
barre basse : sa hauteur vaut ≈ 5 % de sa largeurH / W0.052 ± 0.012
un RAYON, pas une pilule : le rayon reste sous 45 % de la hauteurnum('.vl-navbar','borderTopLeftRadius') / H0.18 → 0.45
le badge, LUI, est un disque parfaitbox('.vl-navbar__badge').w / box('.vl-navbar__badge').h1 ± 0.02
badge ≈ 70 % de la hauteur de barre — il loge dedans, il ne la déborde pasbox('.vl-navbar__badge').h / H0.696 ± 0.06
gouttière constante : l'écart du badge au bord gauche égale son écart au bord hautMath.abs(box('.vl-navbar__badge').left - box('.vl-navbar__badge').top)≤ 1.5
le bouton est PLUS CLAIR que la barre — c'est le seul geste de hiérarchie du systèmelum(parseRgb(cs('.vl-navbar__cta','backgroundColor'))) - lum(parseRgb(cs('.vl-navbar','backgroundColor')))≥ 0.01
le badge aussi est plus clair que la barrelum(parseRgb(cs('.vl-navbar__badge','backgroundColor'))) - lum(parseRgb(cs('.vl-navbar','backgroundColor')))≥ 0.01
le bouton EST une pilule : son rayon vaut au moins sa demi-hauteurnum('.vl-navbar__cta','borderTopLeftRadius') / box('.vl-navbar__cta').h≥ 0.5
le bloc de droite colle la marge intérieure de la barreW - num('.vl-navbar','paddingRight') - box('.vl-navbar__cta').right0 ± 1.5
le wordmark et les liens ne se touchent jamais : au moins deux hauteurs de barre les séparent(box('.vl-navbar__links').left - box('.vl-navbar__mark').right) / H≥ 2
saut typographique wordmark / lien ≥ 1,7num('.vl-navbar__mark','fontSize') / num('.vl-navbar__links','fontSize')≥ 1.7
aucun liseré : la barre se détache par son aplat, pas par un traitnum('.vl-navbar','borderTopWidth')0 ± 0
aucune ombre portéecs('.vl-navbar','boxShadow') === 'none' ? 1 : 01
contraste des liens ≥ 4,5 sur l'aplat de la barrecontrast('.vl-navbar__links')≥ 4.5
contraste du libellé de bouton ≥ 4,5 sur son blanccontrast('.vl-navbar__cta')≥ 4.5
aucun débordement du contenu hors de la barreoverflow('.vl-navbar')≤ 0

Le code

fragment — patterns/layout-05-nav-pill-bar.html Télécharger
<!-- layout-05-nav-pill-bar — la navigation comme OBJET posé dans la marge, pas comme bandeau.
     Couleurs via --vl-* uniquement. Trois zones dans un ordre FIXE : badge + wordmark à
     gauche, liens poussés à droite par `margin-left:auto`, bouton en bout de ligne.
     Le badge et le bouton sont d'un blanc PLUS CLAIR que la barre : c'est la seule ressource
     de hiérarchie du système, et elle ne fonctionne que dans ce sens-là. -->
<header class="vl-navbar">
  <span class="vl-navbar__badge" aria-hidden="true">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round">
      <path d="M4 10v4M8 6.5v11M12 3.5v17M16 6.5v11M20 10v4"/>
    </svg>
  </span>
  <span class="vl-navbar__mark">northbeam</span>
  <nav class="vl-navbar__links">
    <span>Services
      <svg class="vl-navbar__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>Case work</span>
    <span class="vl-navbar__phone">+33 1 84 80 44 55</span>
  </nav>
  <a class="vl-navbar__cta" href="#">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><circle cx="12" cy="8" r="3.4"/><path d="M4.8 20c.7-3.6 3.6-5.6 7.2-5.6s6.5 2 7.2 5.6"/></svg>
    Client area
  </a>
</header>

<style>
.vl-navbar {
  width: 1326px; height: 69px;
  box-sizing: border-box;
  /* La marge intérieure vaut la moitié de l'écart vertical du badge : c'est ce qui fait
     lire le badge comme LOGÉ dans la barre, et non posé dessus. */
  padding: 0 10px;
  display: flex; align-items: center;
  background: var(--vl-module);
  /* Un rayon franc, jamais 50 % : à demi-hauteur la barre devient un tube et les liens
     semblent y flotter. La borne haute est le vrai contrôle de ce pattern. */
  border-radius: var(--vl-radius-bar, 20px);
  font-family: Inter, "Helvetica Neue", Arial, sans-serif;
  color: var(--vl-ink);
}
.vl-navbar__badge {
  flex: 0 0 auto;
  width: 48px; height: 48px;
  border-radius: var(--vl-radius-pill, 999px);
  background: var(--vl-object);
  color: var(--vl-ink);
  display: flex; align-items: center; justify-content: center;
}
.vl-navbar__badge svg { width: 22px; height: 22px; }
.vl-navbar__mark {
  margin-left: 16px;
  font-size: 30px; font-weight: 700; letter-spacing: -0.02em;
}
/* `margin-left:auto` et rien d'autre : c'est le vide qui sépare la marque de la navigation,
   pas un filet ni un changement de fond. */
.vl-navbar__links {
  margin-left: auto;
  display: flex; align-items: center; gap: 44px;
  font-size: 16px; font-weight: 500;
  color: var(--vl-ink-soft);
}
.vl-navbar__links > span { display: inline-flex; align-items: center; gap: 7px; }
.vl-navbar__caret { width: 9px; height: 6px; }
/* Le numéro n'est pas un lien : il passe à l'encre pleine. Une seule information de la
   navigation a le droit à ce rang, sinon le rang ne veut plus rien dire. */
.vl-navbar__phone { color: var(--vl-ink); }
.vl-navbar__cta {
  margin-left: 44px;
  height: 49px; padding: 0 22px;
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--vl-object);
  color: var(--vl-ink);
  border-radius: var(--vl-radius-pill, 999px);
  font-size: 15px; font-weight: 500; text-decoration: none;
}
.vl-navbar__cta svg { width: 15px; height: 15px; }
</style>
tokens de la charte — systems/ref-17-mono-bento-legal.json Télécharger
:root {
  --vl-page: #FEFEFE;
  --vl-module: #EDEDED;
  --vl-object: #FFFFFF;
  --vl-invert: #000000;
  --vl-ink: #111111;
  --vl-ink-soft: #5F5F5F;
  --vl-on-invert: #FFFFFF;
  --vl-on-invert-soft: rgba(255,255,255,0.62);
  --vl-line: rgba(17,17,17,0.55);
  --vl-radius-page: 38px;
  --vl-radius-module: 24px;
  --vl-radius-bar: 20px;
  --vl-radius-pill: 999px;
  --vl-gutter: 26px;
  --vl-margin: 57px;
  --vl-bar-h: 69px;
  --vl-col-1: 343fr;
  --vl-col-2: 466fr;
  --vl-col-3: 464fr;
  --vl-row-1: 302fr;
  --vl-row-2: 232fr;
  --vl-row-3: 243fr;
}
les métadonnées — patterns/layout-05-nav-pill-bar.json (télécharger)
{
  "name": "Barre de menu en pilule flottante",
  "family": "layout",
  "ref": "ref-17-mono-bento-legal",
  "intent": "Poser la navigation d'une page comme un OBJET posé dans la marge — un aplat arrondi qui flotte au lieu d'un bandeau collé au bord — et n'y employer qu'une seule ressource de hiérarchie : le rang de blanc, le badge de marque et le bouton d'action étant plus CLAIRS que la barre qui les porte.",
  "when_to_use": "En-tête d'une landing ou d'une application dont le fond est déjà clair, quand la navigation ne doit pas se lire comme un chrome de navigateur : la barre reste dans la marge de page, à la même distance des bords que les modules qui la suivent, et le premier module commence juste dessous.",
  "avoid_when": "La page défile sous un en-tête collant (une pilule flottante qui se fige au bord perd exactement ce qui la justifie), le fond est sombre ou photographique (le rang de blanc n'a plus de sens : le blanc devient la couleur la plus forte de l'écran), ou la navigation compte plus de cinq entrées — au-delà, les liens rattrapent le bouton et la respiration qui fait l'objet disparaît.",
  "media": [
    "web"
  ],
  "tags": [
    "navigation",
    "menu",
    "barre",
    "header",
    "entete",
    "pilule",
    "landing",
    "logo",
    "wordmark",
    "bouton",
    "cta",
    "monochrome",
    "web",
    "app"
  ],
  "vars": [
    {
      "name": "--vl-module",
      "role": "aplat de la barre",
      "default": "#EDEDED"
    },
    {
      "name": "--vl-object",
      "role": "blanc des objets posés DANS la barre (badge, bouton) — plus clair que la page",
      "default": "#FFFFFF"
    },
    {
      "name": "--vl-ink",
      "role": "wordmark, téléphone, libellé du bouton",
      "default": "#111111"
    },
    {
      "name": "--vl-ink-soft",
      "role": "liens de menu (relevé #6E6E6E, posé #5F5F5F pour tenir 4,5:1)",
      "default": "#5F5F5F"
    },
    {
      "name": "--vl-radius-bar",
      "role": "rayon de la barre — un rayon franc, JAMAIS une demi-hauteur",
      "default": "20px"
    },
    {
      "name": "--vl-radius-pill",
      "role": "rayon du bouton et du badge, eux vraiment en pilule",
      "default": "999px"
    }
  ],
  "slots": [
    "badge",
    "mark",
    "links",
    "cta"
  ],
  "geometry": {
    "root": ".vl-navbar",
    "frame": [
      1326,
      69
    ],
    "ratios": {
      "height_over_width": 0.052,
      "radius_over_height": 0.29,
      "badge_over_height": 0.696,
      "cta_over_height": 0.71,
      "inset_over_height": 0.145,
      "link_gap_over_height": 0.638
    },
    "type_px": {
      "mark": 30,
      "link": 16,
      "cta": 15
    },
    "pad_ratio": {
      "top": 0,
      "x": 0.0075,
      "bottom": 0
    }
  },
  "benchmarks": [
    {
      "name": "barre basse : sa hauteur vaut ≈ 5 % de sa largeur",
      "measure": "H / W",
      "expect": 0.052,
      "tol": 0.012
    },
    {
      "name": "un RAYON, pas une pilule : le rayon reste sous 45 % de la hauteur",
      "measure": "num('.vl-navbar','borderTopLeftRadius') / H",
      "min": 0.18,
      "max": 0.45
    },
    {
      "name": "le badge, LUI, est un disque parfait",
      "measure": "box('.vl-navbar__badge').w / box('.vl-navbar__badge').h",
      "expect": 1,
      "tol": 0.02
    },
    {
      "name": "badge ≈ 70 % de la hauteur de barre — il loge dedans, il ne la déborde pas",
      "measure": "box('.vl-navbar__badge').h / H",
      "expect": 0.696,
      "tol": 0.06
    },
    {
      "name": "gouttière constante : l'écart du badge au bord gauche égale son écart au bord haut",
      "measure": "Math.abs(box('.vl-navbar__badge').left - box('.vl-navbar__badge').top)",
      "max": 1.5
    },
    {
      "name": "le bouton est PLUS CLAIR que la barre — c'est le seul geste de hiérarchie du système",
      "measure": "lum(parseRgb(cs('.vl-navbar__cta','backgroundColor'))) - lum(parseRgb(cs('.vl-navbar','backgroundColor')))",
      "min": 0.01
    },
    {
      "name": "le badge aussi est plus clair que la barre",
      "measure": "lum(parseRgb(cs('.vl-navbar__badge','backgroundColor'))) - lum(parseRgb(cs('.vl-navbar','backgroundColor')))",
      "min": 0.01
    },
    {
      "name": "le bouton EST une pilule : son rayon vaut au moins sa demi-hauteur",
      "measure": "num('.vl-navbar__cta','borderTopLeftRadius') / box('.vl-navbar__cta').h",
      "min": 0.5
    },
    {
      "name": "le bloc de droite colle la marge intérieure de la barre",
      "measure": "W - num('.vl-navbar','paddingRight') - box('.vl-navbar__cta').right",
      "expect": 0,
      "tol": 1.5
    },
    {
      "name": "le wordmark et les liens ne se touchent jamais : au moins deux hauteurs de barre les séparent",
      "measure": "(box('.vl-navbar__links').left - box('.vl-navbar__mark').right) / H",
      "min": 2
    },
    {
      "name": "saut typographique wordmark / lien ≥ 1,7",
      "measure": "num('.vl-navbar__mark','fontSize') / num('.vl-navbar__links','fontSize')",
      "min": 1.7
    },
    {
      "name": "aucun liseré : la barre se détache par son aplat, pas par un trait",
      "measure": "num('.vl-navbar','borderTopWidth')",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "aucune ombre portée",
      "measure": "cs('.vl-navbar','boxShadow') === 'none' ? 1 : 0",
      "expect": 1
    },
    {
      "name": "contraste des liens ≥ 4,5 sur l'aplat de la barre",
      "measure": "contrast('.vl-navbar__links')",
      "min": 4.5
    },
    {
      "name": "contraste du libellé de bouton ≥ 4,5 sur son blanc",
      "measure": "contrast('.vl-navbar__cta')",
      "min": 4.5
    },
    {
      "name": "aucun débordement du contenu hors de la barre",
      "measure": "overflow('.vl-navbar')",
      "max": 0
    }
  ],
  "notes": "Le geste qui fait ce pattern n'est ni le rayon ni la pilule : c'est le RANG DE BLANC. La barre est un gris de module, le badge et le bouton sont d'un blanc plus clair que la PAGE elle-même. Deux benchmarks le mesurent en luminance relative parce que c'est la seule formulation qui survive à un changement de charte — dire « le bouton est blanc » serait faux dès qu'on thème le pattern en sombre, dire « le bouton est plus clair que la barre » reste vrai partout.\n\nLe rayon est tenu entre 18 % et 45 % de la hauteur, et cette borne haute est le vrai contrôle : à 50 % la barre devient une pilule pleine, ce qui est une autre forme et un autre système (les liens semblent alors flotter dans un tube). Le bouton, lui, est bien une pilule — la dissymétrie entre les deux est délibérée.\n\nL'assertion « l'écart du badge au bord gauche égale son écart au bord haut » vaut mieux qu'une mesure de padding : elle attrape aussi le cas où le badge est centré verticalement mais collé à gauche, qui passe tous les contrôles de padding et casse pourtant la silhouette.\n\nL'encre des liens est posée à #5F5F5F et non au #6E6E6E relevé sur la source : sur le gris de module #EDEDED, le relevé plafonne à 4,09:1. Écart invisible à l'œil, faute de contraste évitée.",
  "id": "layout-05-nav-pill-bar"
}

À côté