card-11-corner-arrow-tile
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
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
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.
| assertion | mesure | attendu |
|---|---|---|
| TROIS colonnes, pas deux : l'écran n'est pas deux piles côte à côte | cs('.vl-bento','gridTemplateColumns').trim().split(/\s+/).length | 3 ± 0 |
| TROIS rangées | cs('.vl-bento','gridTemplateRows').trim().split(/\s+/).length | 3 ± 0 |
| gouttière UNIQUE : l'horizontale et la verticale sont la même valeur | Math.abs(num('.vl-bento','columnGap') - num('.vl-bento','rowGap')) | ≤ 0.5 |
| gouttière ≈ 2 % de la largeur | num('.vl-bento','columnGap') / W | 0.0196 ± 0.006 |
| les trois modules du haut partagent leur ligne supérieure | Math.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ère | box('.vl-bento__a').h / box('.vl-bento__b').h | 1.854 ± 0.08 |
| l'enjambement horizontal : le hero vaut deux colonnes + une gouttière | box('.vl-bento__hero').w / box('.vl-bento__b').w | 2.052 ± 0.08 |
| LES DEUX ENJAMBEMENTS SE CROISENT : le bas de A descend sous le haut du hero | box('.vl-bento__a').bottom - box('.vl-bento__hero').top | ≥ 40 |
| … mais ils ne se chevauchent JAMAIS : une gouttière pleine les sépare | box('.vl-bento__hero').left - box('.vl-bento__a').right | 26 ± 2 |
| le pied de grille est droit : D et le hero finissent sur la même ligne | Math.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 gauche | 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)) | ≥ 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).length | 1 ± 0 |
| aucun liseré sur les modules | num('.vl-bento__b','borderTopWidth') + num('.vl-bento__hero','borderTopWidth') | 0 ± 0 |
| aucune ombre portée | cs('.vl-bento__b','boxShadow') === 'none' ? 1 : 0 | 1 |
| contraste de l'encre sur le module dominant ≥ 4,5 | contrast('.vl-bento__hero') | ≥ 4.5 |
| aucun débordement de la grille | overflow('.vl-bento') | ≤ 0 |
Le code
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>
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é
layout-01-nested-bento
Bento à cartes imbriquées
layout-02-swiss-frame
Cadre suisse header / footer
layout-03-glass-board
Écran en verre dépoli — fond dégradé + modules
layout-04-graph-paper-cells
Papier millimétré et ses cases
layout-05-nav-pill-bar