layout-06-bento-l-span

Bento 3 × 3 à deux enjambements en L

Tenir cinq modules de tailles très différentes sur une VRAIE grille 3 × 3, en n'employant que deux enjambements — un vertical à gauche, un horizontal à droite — qui se CROISENT sans jamais se chevaucher : c'est ce croisement qui supprime toute frontière horizontale continue et fait qu'un écran dense cesse de se lire comme un tableau.

layoutwebslide1326×830px17 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

Accueil, sommaire de services, page d'entrée d'application : un contenu principal qui doit dominer sans occuper la moitié de l'écran, un contenu long à gauche (liste, nuage, sommaire), et deux entrées secondaires de même rang en haut. Le module dominant prend l'aplat inversé, et il est le SEUL.

Éviter quand

Les modules sont de même rang (une grille régulière dit alors la vérité et se refond sans douleur), le contenu du module haut à gauche varie beaucoup en longueur (l'enjambement vertical fige sa hauteur), ou l'écran doit se replier en une colonne sur mobile — les deux enjambements y perdent tout sens et il faut une autre composition, pas une adaptation de celle-ci.

Notes

Ce pattern n'est PAS une variante de layout-01-nested-bento : celui-ci emboîte des niveaux (carte → sous-carte → tuile) en faisant décroître rayon et padding, celui-là ne compte qu'UN niveau et met tout son propos dans le découpage de la grille. Aucun des deux ne se déduit de l'autre. LE PIÈGE DU LOT, ET IL EST DOUBLE. Les pistes se déclarent en `minmax(0, …fr)` et la grille en `min-height: 0` : le minimum automatique d'une piste `fr` ET celui d'un item flex valent tous deux `auto`, si bien qu'un module trop plein pousse d'abord sa rangée, puis la page entière — mesuré au premier rendu, 669 px de rangée pour 560 attendus, sans qu'aucun contrôle de pattern ne crie. Les deux gardes sont nécessaires ; aucune ne remplace l'autre. L'assertion qui porte la signature est « les deux enjambements se CROISENT » : c'est elle qui distingue cette composition d'un bento en deux colonnes indépendantes, lequel passerait tous les autres benchmarks. Elle est doublée d'une assertion négative — « aucune frontière horizontale continue » — parce qu'une grille où le bas de B tomberait sur le bas de A redeviendrait un tableau sans qu'aucune mesure de largeur ne bouge. Le comptage des aplats sombres est écrit en luminance et non sur une classe : un second module noir posé plus tard par quelqu'un d'autre est exactement le genre de régression qu'un contrôle par classe laisse passer.

Mots-clés

bentogrillegridlayoutaccueillandingdashboardenjambementspanmodulemonochromehierarchiehero

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

Rejouables par node bin/check.mjs layout-06-bento-l-span : le pattern se prouve, il ne se croit pas.

assertionmesureattendu
TROIS colonnes, pas deux : l'écran n'est pas deux piles côte à côtecs('.vl-bento','gridTemplateColumns').trim().split(/\s+/).length3 ± 0
TROIS rangéescs('.vl-bento','gridTemplateRows').trim().split(/\s+/).length3 ± 0
gouttière UNIQUE : l'horizontale et la verticale sont la même valeurMath.abs(num('.vl-bento','columnGap') - num('.vl-bento','rowGap'))≤ 0.5
gouttière ≈ 2 % de la largeurnum('.vl-bento','columnGap') / W0.0196 ± 0.006
les trois modules du haut partagent leur ligne supérieureMath.abs(box('.vl-bento__a').top - box('.vl-bento__c').top) + Math.abs(box('.vl-bento__b').top - box('.vl-bento__c').top)≤ 1.5
l'enjambement vertical : A vaut deux rangées + une gouttièrebox('.vl-bento__a').h / box('.vl-bento__b').h1.854 ± 0.08
l'enjambement horizontal : le hero vaut deux colonnes + une gouttièrebox('.vl-bento__hero').w / box('.vl-bento__b').w2.052 ± 0.08
LES DEUX ENJAMBEMENTS SE CROISENT : le bas de A descend sous le haut du herobox('.vl-bento__a').bottom - box('.vl-bento__hero').top≥ 40
… mais ils ne se chevauchent JAMAIS : une gouttière pleine les séparebox('.vl-bento__hero').left - box('.vl-bento__a').right26 ± 2
le pied de grille est droit : D et le hero finissent sur la même ligneMath.abs(box('.vl-bento__d').bottom - box('.vl-bento__hero').bottom)≤ 1.5
aucune frontière horizontale continue : le bas de B ne coïncide avec le bas d'aucun de ses voisins de gaucheMath.min(Math.abs(box('.vl-bento__b').bottom - box('.vl-bento__a').bottom), Math.abs(box('.vl-bento__b').bottom - box('.vl-bento__d').top))≥ 20
le module dominant est au moins DEUX fois l'aire d'un module secondaire(box('.vl-bento__hero').w * box('.vl-bento__hero').h) / (box('.vl-bento__b').w * box('.vl-bento__b').h)≥ 2
UN SEUL aplat sombre dans toute la grille[...document.querySelectorAll('.vl-bento > *')].filter((e) => lum(parseRgb(getComputedStyle(e).backgroundColor)) < 0.2).length1 ± 0
aucun liseré sur les modulesnum('.vl-bento__b','borderTopWidth') + num('.vl-bento__hero','borderTopWidth')0 ± 0
aucune ombre portéecs('.vl-bento__b','boxShadow') === 'none' ? 1 : 01
contraste de l'encre sur le module dominant ≥ 4,5contrast('.vl-bento__hero')≥ 4.5
aucun débordement de la grilleoverflow('.vl-bento')≤ 0

Le code

fragment — patterns/layout-06-bento-l-span.html Télécharger
<!-- layout-06-bento-l-span — cinq modules sur une VRAIE grille 3 × 3, deux enjambements en L.
     Couleurs via --vl-* uniquement. Ce que le fragment porte, ce n'est pas la décoration des
     modules (elle est vide à dessein, cf. card-11-corner-arrow-tile pour la vignette) : c'est
     le DÉCOUPAGE. A enjambe les rangées 1-2 de la colonne 1, le hero enjambe les colonnes 2-3
     des rangées 2-3 ; les deux se croisent sans se toucher, et c'est ce croisement qui
     supprime toute frontière horizontale continue. -->
<div class="vl-bento">
  <article class="vl-bento__a"><h3>Legal services</h3><p>colonne 1 · rangées 1-2</p></article>
  <article class="vl-bento__b"><h3>Video consultations</h3><p>colonne 2 · rangée 1</p></article>
  <article class="vl-bento__c"><h3>Learning material</h3><p>colonne 3 · rangée 1</p></article>
  <article class="vl-bento__hero"><h3>We make life simpler.</h3><p>colonnes 2-3 · rangées 2-3</p></article>
  <article class="vl-bento__d"><h3>Your steady guide</h3><p>colonne 1 · rangée 3</p></article>
</div>

<style>
.vl-bento {
  width: 1326px; height: 830px;
  box-sizing: border-box;
  display: grid;
  /* Les colonnes portent le rapport relevé (269 / 366 / 364 millièmes de la largeur utile) ;
     `minmax(0, …fr)` et pas `…fr` seul, parce que le minimum automatique d'une piste `fr`
     est `auto` : un module trop plein POUSSERAIT sa rangée et l'écran déborderait en
     silence — la grille cesserait d'être la grille relevée sans qu'aucun contrôle ne crie. */
  grid-template-columns: minmax(0, 343fr) minmax(0, 466fr) minmax(0, 464fr);
  grid-template-rows: minmax(0, 302fr) minmax(0, 232fr) minmax(0, 243fr);
  gap: var(--vl-gutter, 26px);
  font-family: Inter, "Helvetica Neue", Arial, sans-serif;
}
/* LES DEUX ENJAMBEMENTS. Ils se croisent — le bas de A descend sous le haut du hero — sans
   jamais se chevaucher : une gouttière pleine les sépare. C'est toute la composition. */
.vl-bento__a    { grid-column: 1;        grid-row: 1 / span 2; }
.vl-bento__b    { grid-column: 2;        grid-row: 1; }
.vl-bento__c    { grid-column: 3;        grid-row: 1; }
.vl-bento__hero { grid-column: 2 / span 2; grid-row: 2 / span 2; }
.vl-bento__d    { grid-column: 1;        grid-row: 3; }

.vl-bento > * {
  margin: 0; padding: 28px;
  background: var(--vl-module);
  color: var(--vl-ink);
  border-radius: var(--vl-radius-module, 24px);
  display: flex; flex-direction: column; justify-content: space-between;
  overflow: hidden;
}
/* UN SEUL aplat sombre par écran. Le module dominant l'est deux fois — par le fond ET par
   l'aire : un second noir annulerait le seul point d'entrée d'un système sans couleur. */
.vl-bento__hero { background: var(--vl-invert); color: var(--vl-on-invert); padding: 40px; }

.vl-bento h3 { margin: 0; font-size: 23px; line-height: 1.24; font-weight: 500; letter-spacing: -0.01em; }
.vl-bento__hero h3 { font-size: 46px; line-height: 1.14; letter-spacing: -0.02em; max-width: 46%; }
.vl-bento p { margin: 0; font-size: 16px; font-weight: 500; color: var(--vl-ink-soft); }
.vl-bento__hero p { color: var(--vl-on-invert-soft); }
</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-06-bento-l-span.json (télécharger)
{
  "name": "Bento 3 × 3 à deux enjambements en L",
  "family": "layout",
  "ref": "ref-17-mono-bento-legal",
  "intent": "Tenir cinq modules de tailles très différentes sur une VRAIE grille 3 × 3, en n'employant que deux enjambements — un vertical à gauche, un horizontal à droite — qui se CROISENT sans jamais se chevaucher : c'est ce croisement qui supprime toute frontière horizontale continue et fait qu'un écran dense cesse de se lire comme un tableau.",
  "when_to_use": "Accueil, sommaire de services, page d'entrée d'application : un contenu principal qui doit dominer sans occuper la moitié de l'écran, un contenu long à gauche (liste, nuage, sommaire), et deux entrées secondaires de même rang en haut. Le module dominant prend l'aplat inversé, et il est le SEUL.",
  "avoid_when": "Les modules sont de même rang (une grille régulière dit alors la vérité et se refond sans douleur), le contenu du module haut à gauche varie beaucoup en longueur (l'enjambement vertical fige sa hauteur), ou l'écran doit se replier en une colonne sur mobile — les deux enjambements y perdent tout sens et il faut une autre composition, pas une adaptation de celle-ci.",
  "media": [
    "web",
    "slide"
  ],
  "tags": [
    "bento",
    "grille",
    "grid",
    "layout",
    "accueil",
    "landing",
    "dashboard",
    "enjambement",
    "span",
    "module",
    "monochrome",
    "hierarchie",
    "hero"
  ],
  "vars": [
    {
      "name": "--vl-module",
      "role": "aplat des modules neutres",
      "default": "#EDEDED"
    },
    {
      "name": "--vl-invert",
      "role": "aplat du module dominant — UN SEUL par écran",
      "default": "#000000"
    },
    {
      "name": "--vl-ink",
      "role": "encre sur module neutre",
      "default": "#111111"
    },
    {
      "name": "--vl-on-invert",
      "role": "encre sur le module dominant",
      "default": "#FFFFFF"
    },
    {
      "name": "--vl-gutter",
      "role": "gouttière UNIQUE, horizontale et verticale",
      "default": "26px"
    },
    {
      "name": "--vl-radius-module",
      "role": "rayon d'un module",
      "default": "24px"
    }
  ],
  "slots": [
    "a",
    "b",
    "c",
    "hero",
    "d"
  ],
  "geometry": {
    "root": ".vl-bento",
    "frame": [
      1326,
      830
    ],
    "ratios": {
      "gutter_over_width": 0.0196,
      "col_1_over_inner": 0.269,
      "col_2_over_inner": 0.366,
      "col_3_over_inner": 0.364,
      "row_1_over_inner": 0.389,
      "row_2_over_inner": 0.298,
      "row_3_over_inner": 0.313,
      "a_over_b_height": 1.854,
      "hero_over_b_width": 2.052,
      "radius_over_width": 0.018
    },
    "type_px": {},
    "pad_ratio": {
      "top": 0,
      "x": 0,
      "bottom": 0
    }
  },
  "benchmarks": [
    {
      "name": "TROIS colonnes, pas deux : l'écran n'est pas deux piles côte à côte",
      "measure": "cs('.vl-bento','gridTemplateColumns').trim().split(/\\s+/).length",
      "expect": 3,
      "tol": 0
    },
    {
      "name": "TROIS rangées",
      "measure": "cs('.vl-bento','gridTemplateRows').trim().split(/\\s+/).length",
      "expect": 3,
      "tol": 0
    },
    {
      "name": "gouttière UNIQUE : l'horizontale et la verticale sont la même valeur",
      "measure": "Math.abs(num('.vl-bento','columnGap') - num('.vl-bento','rowGap'))",
      "max": 0.5
    },
    {
      "name": "gouttière ≈ 2 % de la largeur",
      "measure": "num('.vl-bento','columnGap') / W",
      "expect": 0.0196,
      "tol": 0.006
    },
    {
      "name": "les trois modules du haut partagent leur ligne supérieure",
      "measure": "Math.abs(box('.vl-bento__a').top - box('.vl-bento__c').top) + Math.abs(box('.vl-bento__b').top - box('.vl-bento__c').top)",
      "max": 1.5
    },
    {
      "name": "l'enjambement vertical : A vaut deux rangées + une gouttière",
      "measure": "box('.vl-bento__a').h / box('.vl-bento__b').h",
      "expect": 1.854,
      "tol": 0.08
    },
    {
      "name": "l'enjambement horizontal : le hero vaut deux colonnes + une gouttière",
      "measure": "box('.vl-bento__hero').w / box('.vl-bento__b').w",
      "expect": 2.052,
      "tol": 0.08
    },
    {
      "name": "LES DEUX ENJAMBEMENTS SE CROISENT : le bas de A descend sous le haut du hero",
      "measure": "box('.vl-bento__a').bottom - box('.vl-bento__hero').top",
      "min": 40
    },
    {
      "name": "… mais ils ne se chevauchent JAMAIS : une gouttière pleine les sépare",
      "measure": "box('.vl-bento__hero').left - box('.vl-bento__a').right",
      "expect": 26,
      "tol": 2
    },
    {
      "name": "le pied de grille est droit : D et le hero finissent sur la même ligne",
      "measure": "Math.abs(box('.vl-bento__d').bottom - box('.vl-bento__hero').bottom)",
      "max": 1.5
    },
    {
      "name": "aucune frontière horizontale continue : le bas de B ne coïncide avec le bas d'aucun de ses voisins de gauche",
      "measure": "Math.min(Math.abs(box('.vl-bento__b').bottom - box('.vl-bento__a').bottom), Math.abs(box('.vl-bento__b').bottom - box('.vl-bento__d').top))",
      "min": 20
    },
    {
      "name": "le module dominant est au moins DEUX fois l'aire d'un module secondaire",
      "measure": "(box('.vl-bento__hero').w * box('.vl-bento__hero').h) / (box('.vl-bento__b').w * box('.vl-bento__b').h)",
      "min": 2
    },
    {
      "name": "UN SEUL aplat sombre dans toute la grille",
      "measure": "[...document.querySelectorAll('.vl-bento > *')].filter((e) => lum(parseRgb(getComputedStyle(e).backgroundColor)) < 0.2).length",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "aucun liseré sur les modules",
      "measure": "num('.vl-bento__b','borderTopWidth') + num('.vl-bento__hero','borderTopWidth')",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "aucune ombre portée",
      "measure": "cs('.vl-bento__b','boxShadow') === 'none' ? 1 : 0",
      "expect": 1
    },
    {
      "name": "contraste de l'encre sur le module dominant ≥ 4,5",
      "measure": "contrast('.vl-bento__hero')",
      "min": 4.5
    },
    {
      "name": "aucun débordement de la grille",
      "measure": "overflow('.vl-bento')",
      "max": 0
    }
  ],
  "notes": "Ce pattern n'est PAS une variante de layout-01-nested-bento : celui-ci emboîte des niveaux (carte → sous-carte → tuile) en faisant décroître rayon et padding, celui-là ne compte qu'UN niveau et met tout son propos dans le découpage de la grille. Aucun des deux ne se déduit de l'autre.\n\nLE PIÈGE DU LOT, ET IL EST DOUBLE. Les pistes se déclarent en `minmax(0, …fr)` et la grille en `min-height: 0` : le minimum automatique d'une piste `fr` ET celui d'un item flex valent tous deux `auto`, si bien qu'un module trop plein pousse d'abord sa rangée, puis la page entière — mesuré au premier rendu, 669 px de rangée pour 560 attendus, sans qu'aucun contrôle de pattern ne crie. Les deux gardes sont nécessaires ; aucune ne remplace l'autre.\n\nL'assertion qui porte la signature est « les deux enjambements se CROISENT » : c'est elle qui distingue cette composition d'un bento en deux colonnes indépendantes, lequel passerait tous les autres benchmarks. Elle est doublée d'une assertion négative — « aucune frontière horizontale continue » — parce qu'une grille où le bas de B tomberait sur le bas de A redeviendrait un tableau sans qu'aucune mesure de largeur ne bouge.\n\nLe comptage des aplats sombres est écrit en luminance et non sur une classe : un second module noir posé plus tard par quelqu'un d'autre est exactement le genre de régression qu'un contrôle par classe laisse passer.",
  "id": "layout-06-bento-l-span"
}

À côté