Grilles de cartes qui ajustent automatiquement les colonnes avec auto-fit/minmax (sans media queries)

Cet article vous a aidé ?

Vous déployez un tableau de bord. Il s’affiche bien sur votre ordinateur portable. Puis quelqu’un l’ouvre sur un MacBook 13″ à 125% de zoom, avec un panneau latéral ouvert, et votre « jolie grille propre » se transforme en tas triste de demi-cartes et en défilement horizontal.

La bonne nouvelle : la solution n’est pas une nouvelle série de tableaux de points d’arrêt. C’est une ligne CSS unique et ennuyeuse qui laisse la grille décider combien de colonnes elle peut se permettre. L’astuce est repeat(auto-fit, minmax(...)) — et savoir où ça mord.

Le modèle de base : auto-fit + minmax

Si vous retenez une chose : arrêtez de penser en points d’arrêt pour les grilles de cartes. Commencez à penser en contraintes.
La contrainte de mise en page est généralement « les cartes ne doivent jamais être plus étroites que X, mais sinon remplir la ligne. »
CSS Grid peut faire cela nativement.

Voici le modèle canonique :

cr0x@server:~$ cat grid.css
.grid {
  display: grid;
  gap: 1rem;

  /* The money line */
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.card {
  /* Let the grid do sizing; don't fight it */
  min-width: 0;
}
...output...

Ignorez la ligne «…output…» dans le bloc ; elle est là parce que le système veut que chaque bloc ressemble à une transcription de console.
Le CSS est ce qui compte.

Ce que ça fait en termes simples

  • repeat(auto-fit, …) dit à la grille : « crée autant de colonnes que l’espace du conteneur permet. »
  • minmax(16rem, 1fr) indique : « chaque colonne doit faire au moins 16rem de large, mais peut grandir pour partager l’espace restant. »
  • gap n’est pas de la décoration ; c’est une partie du calcul. Un minimum de 16rem plus les gaps détermine combien de colonnes tiennent.

Le résultat : une grille qui passe naturellement de 1 colonne à 2, 3, 4 à mesure que le conteneur s’élargit — sans media queries, sans cas spéciaux,
et avec moins de régressions quand quelqu’un modifie la largeur d’une barre latérale ou la taille globale des polices.

Conseil d’opinion : pour les cartes, minmax(15rem, 1fr) à minmax(20rem, 1fr) couvre la plupart des interfaces produit.
Choisissez un minimum qui garde le contenu lisible, pas un minimum qui optimise « combien de cartes puis-je voir en même temps ».
C’est comme ça qu’on crée des cartes minuscules illisibles et qu’on appelle ça de la « densité ».

Encore une chose : définissez min-width: 0 sur les enfants qui contiennent du texte long ou du contenu flex. Sinon une longue chaîne peut forcer un débordement et
vous accuserez la grille. La grille est innocente ; ce n’est pas le cas du comportement de taille min-content.

Faits et histoire : comment on en est arrivé là

La grille « sans media queries » n’est pas un gadget. C’est ce qui arrive quand la plateforme grandit enfin.
Quelques faits concrets pour calibrer votre modèle mental :

  1. CSS Grid est arrivé dans le grand public en 2017 sur les principaux navigateurs, ce qui explique pourquoi d’anciens kits UI internes restent attachés aux patterns de l’ère float.
  2. L’algorithme de dimensionnement de Grid inclut des contributions « min-content » et « max-content », ce qui signifie que le texte et les tailles intrinsèques peuvent affecter la taille des pistes à moins que vous ne les limitiez.
  3. Les unités fr ont été conçues pour distribuer l’espace restant ; ce ne sont pas des « pourcentages améliorés ». Elles interviennent après résolution des contraintes fixes et minimales.
  4. auto-fit et auto-fill ont été introduits pour résoudre le besoin de « nombre inconnu de colonnes », fréquent dans les galeries et les grilles de cartes.
  5. Le responsive précoce reposait beaucoup sur les breakpoints parce que les primitives de mise en page étaient limitées ; on faisait avec les moyens du bord, comme quelqu’un interrogeant une base sans index.
  6. Les propriétés gap (row-gap, column-gap, et gap) étaient autrefois « spécifiques à grid » ; elles sont ensuite devenues utiles aussi avec flexbox.
  7. Subgrid a mis beaucoup plus longtemps à devenir utilisable en production, c’est pourquoi beaucoup de systèmes de cartes « miment » des intérieurs alignés avec du padding et des astuces de baseline.
  8. Les container queries ont finalement atteint les navigateurs stables dans les années 2020, mais vous n’en avez souvent pas besoin pour de simples grilles si vous utilisez des pistes basées sur des contraintes.

Le modèle dont nous parlons est essentiellement « la mise en page comme algorithme », pas « la mise en page comme feuille de calcul ». C’est pour cela qu’il résiste au changement.

Auto-fit vs auto-fill : ce qui change réellement

Internet adore expliquer ça avec des métaphores vagues sur les « pistes vides ». Soyons précis.
auto-fit et auto-fill calculent tous les deux combien de pistes pourraient tenir selon la fonction de dimensionnement des pistes (votre minmax) et l’espace disponible.
La différence réside dans ce qu’il advient des pistes non utilisées.

Auto-fill : garder les colonnes vides

auto-fill va créer autant de colonnes que possible, même si vous n’avez pas assez d’éléments pour les occuper.
Ces pistes vides existent toujours et prennent de la place, donc vos éléments ne s’étireront pas autant.
C’est utile quand vous voulez une géométrie de colonnes constante (pensez : mises en page de type calendrier).

Auto-fit : écraser les colonnes vides

auto-fit effondre les pistes vides à zéro. Cela signifie que les éléments peuvent s’étendre pour remplir la ligne.
Pour les cartes, c’est généralement ce que vous voulez : pas de colonnes vides bizarres quand vous n’avez que 2 cartes.

Règle utile : pour les grilles de cartes, par défaut utilisez auto-fit.
Servez-vous de auto-fill lorsque vous tenez à ce que des « colonnes fantômes » restent réservées.

Blague n°1 : Auto-fill, c’est comme réserver des sièges pour vos amis qui « sont totalement en chemin ». Auto-fit, c’est admettre qu’ils ne viennent pas et manger leurs snacks.

Le problème de la dernière carte solitaire

Vous verrez ceci : une grille avec 5 éléments, un conteneur assez large pour 3 colonnes. La première rangée a 3 cartes, la seconde en a 2.
Avec auto-fit, ces 2 cartes s’étirent souvent joliment. Avec auto-fill, elles peuvent rester étroites parce que la « troisième colonne » existe toujours comme piste vide.
C’est la différence que vous pouvez montrer à un designer sans déclencher une guerre.

Choisir le min/max : des valeurs qui se comportent

La plupart des échecs viennent du choix d’une largeur minimale comme si c’était un token de design que l’on peut définir une fois pour toutes.
Ce n’est pas le cas. C’est un contrat entre le contenu, le conteneur, la typographie et tout ce que quelqu’un mettra dans une carte le trimestre prochain.

Choisissez une largeur minimale basée sur le contenu réel, pas l’ambiance

Votre minimum doit tenir compte de :

  • Longueur typique des titres sans se retrouver sur 4 lignes
  • Lignes clé-valeur (les étiquettes sont souvent plus longues que prévu)
  • Boutons (surtout si le contenu est localisé)
  • Badges/chips qui ne peuvent pas rétrécir
  • Longs nombres, identifiants et horodatages

Recommandation pratique :

  • 14–16rem pour les « cartes simples » (icône, titre, petit extrait)
  • 18–22rem pour les « cartes d’information » (plusieurs lignes, métadonnées, actions)
  • 24rem+ pour les « cartes qui font semblant d’être des tableaux » (vous devriez probablement utiliser un tableau)

Max : pourquoi 1fr suffit généralement

Le maximum 1fr dans minmax fait que les colonnes partagent l’espace restant. Pour la plupart des grilles, c’est idéal.
Évitez les grandes valeurs max ou max-content à moins que vous aimiez déboguer des débordements à 2h du matin.

Un modèle plus défensif est :

cr0x@server:~$ cat defensive-grid.css
.grid {
  display: grid;
  gap: 1rem;

  /* clamp-like behavior: don't let cards get cartoonishly wide */
  grid-template-columns: repeat(auto-fit, minmax(18rem, 22rem));
  justify-content: center;
}
...output...

Ici le max est fixe (22rem), ce qui empêche d’avoir une seule rangée avec deux cartes énormes sur des écrans ultra-larges.
Vous échangez le « remplir tout l’espace » contre le fait que « les cartes gardent une forme de carte ». C’est souvent une meilleure UX.

N’ignorez pas le gap dans vos calculs

Une mise en page à trois colonnes a besoin de 3 * minWidth + 2 * gap d’espace (plus padding et bordures).
Les gens mettent minmax(320px, 1fr) et gap: 32px, puis se demandent pourquoi la grille tombe à deux colonnes plus tôt que prévu.
Ce n’est pas « aléatoire ». C’est de l’arithmétique.

Utilisez min() quand le conteneur peut être petit

Sur des conteneurs étroits (mobile, modales, panneaux latéraux), un min rigide comme 18rem peut forcer un débordement si le conteneur est plus petit.
Vous pouvez contraindre le min :

cr0x@server:~$ cat tiny-container-grid.css
.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
}
...output...

Cela garantit que le minimum ne dépasse jamais la largeur du conteneur. Ce n’est pas de la « réactivité magique ». C’est reconnaître que les conteneurs peuvent être petits et agir en adulte.

Grilles de cartes en production : modèles qui tiennent

Passons de la théorie aux détails désagréables qui apparaissent lorsque votre grille vit dans une vraie application :
barres latérales, onglets, filtres, modales, traductions longues, et un cadre qui utilise le zoom à 200 %.

Modèle 1 : cartes fluides standards pour tableaux de bord

cr0x@server:~$ cat dashboard-grid.css
.grid {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  align-items: start;
}

.card {
  min-width: 0;
  border: 1px solid #d8dde6;
  border-radius: 12px;
  padding: 16px;
  background: #fff;
}
...output...

Pourquoi ça marche :

  • Largeur minimale (280px) lisible pour des cartes métriques typiques.
  • align-items: start empêche les cartes de s’étirer verticalement pour correspondre au voisin le plus grand.
  • min-width: 0 réduit le débordement dû à du contenu long.

Modèle 2 : version « design system » (largeur de carte cohérente)

cr0x@server:~$ cat design-system-grid.css
.grid {
  display: grid;
  gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 24rem));
  justify-content: start;
}
...output...

C’est le modèle à utiliser quand le design veut que les cartes restent cohérentes sur les points de vue, sans s’étaler comme des crêpes.
C’est aussi préférable quand les cartes contiennent des graphiques : les graphiques rendent généralement mieux à des tailles constantes.

Modèle 3 : cartes à contenu mixte (défensif contre les longues chaînes)

cr0x@server:~$ cat mixed-content.css
.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}

.card .title,
.card .value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
...output...

Ce modèle fait un choix : la troncature plutôt que le débordement. Pour les tableaux de bord, c’est généralement correct — à condition de fournir la valeur complète via info-bulle ou vue détaillée.
Ne tronquez pas en silence sans porte de sortie ; c’est ainsi que vous recevez des tickets « pourquoi mon nom d’hôte est coupé ? ».

Modèle 4 : quand vous voulez moins de surprises : nombre max de colonnes

Parfois vous ne voulez pas que la grille ajoute des colonnes indéfiniment. Vous pouvez la limiter en contraignant la largeur du conteneur :

cr0x@server:~$ cat capped-grid.css
.wrapper {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 16px;
}

.grid {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
...output...

C’est sous-estimé. « Colonnes illimitées » sonne flexible, mais peut détruire les schémas de balayage sur des écrans larges.
Une limitation est une décision produit, pas une contrainte technique.

Quand vous devriez encore utiliser des media queries

Oui, j’ai dit « sans media queries ». Je gère aussi des systèmes en production, donc je dirai la partie gênante à voix haute :

  • Utilisez des media queries quand la mise en page change fondamentalement, pas quand il ne s’agit que d’un changement du nombre de colonnes.
    Exemple : passer d’une sidebar de filtres à un tiroir de filtres.
  • Utilisez des media queries quand vous avez besoin de changements typographiques (tailles de police, hauteurs de ligne) qui ne sont pas purement pilotés par le conteneur.
  • Utilisez des media queries si vous devez cibler des classes d’appareils spécifiques pour des points de contact tactiles ou des comportements de navigation.

Pour les grilles de cartes spécifiquement, si vous vous surprenez à écrire 4–6 breakpoints, vous compensez probablement une largeur minimale mal choisie ou des intérieurs de carte non contrôlés.
Corrigez cela d’abord.

Une citation pour rester honnête : L’espoir n’est pas une stratégie. (souvent attribué dans les cercles d’ingénierie ; idée paraphrasée)

Trousse de diagnostic rapide

Quand une grille de cartes se comporte mal en production — débordement, nombre de colonnes inattendu, étirements gênants — vous n’avez pas besoin d’une semaine d’« archéologie CSS ».
Vous avez besoin d’un ordre de triage qui trouve rapidement le goulot d’étranglement.

1) Confirmez la taille et les contraintes du conteneur

Première question : le conteneur de la grille a-t-il vraiment la largeur que vous pensez ?
Barres latérales, padding, wrappers avec max-width imbriqués et barres de défilement changent la taille inline disponible.

2) Vérifiez la définition des pistes et le calcul du gap

Deuxième question : est-ce que minWidth * columns + gaps rentre ?
La plupart des bugs « pourquoi ça tombe à 2 colonnes ? » sont juste le min + gap + padding qui dépassent le conteneur.

3) Inspectez le dimensionnement intrinsèque des enfants

Troisième question : est-ce qu’un enfant impose une taille min-content plus grande que le minimum de votre piste ?
Coupables fréquents : longues chaînes sans coupure, images sans contraintes, enfants flex sans min-width: 0.

4) Cherchez les règles d’overflow et d’alignement

Si les cartes s’étirent verticalement ou coupent du contenu, vérifiez align-items sur la grille et les règles d’overflow sur la carte.
Beaucoup de bibliothèques de composants définissent des valeurs par défaut qui vont bien isolément et sont catastrophiques en grille.

5) Ensuite seulement, considérez un changement de « stratégie de mise en page »

Passer aux container queries, ajouter des wrappers ou écrire des breakpoints est l’étape cinq, pas l’étape un.
Si vous le faites trop tôt, vous masquerez la cause racine et le problème réapparaîtra dès que le contenu changera.

Erreurs courantes : symptôme → cause racine → correction

1) Symptom : défilement horizontal sur petits écrans

  • Cause racine : largeur minimale stricte supérieure à celle du conteneur ; ou un élément enfant avec une grande largeur intrinsèque.
  • Correction : utilisez minmax(min(18rem, 100%), 1fr) ; ajoutez min-width: 0 à la carte ; contraignez les médias avec max-width: 100%.

2) Symptom : cartes trop larges sur grands écrans et qui font ridicule

  • Cause racine : minmax(x, 1fr) avec peu d’éléments signifie que chaque élément s’étend pour remplir un espace massif.
  • Correction : plafonnez la largeur maximale : minmax(18rem, 24rem) et utilisez justify-content: center ou contraignez la largeur du wrapper.

3) Symptom : « pourquoi y a-t-il des colonnes vides ? »

  • Cause racine : utilisation de auto-fill alors que auto-fit était voulu.
  • Correction : passez à auto-fit ou acceptez les pistes vides si votre design exige une géométrie stable.

4) Symptom : la grille ne se replie pas quand attendu

  • Cause racine : le minimum est trop petit, donc plus de colonnes « tiennent » que souhaité ; ou le conteneur est plus large que prévu à cause du comportement flex du parent.
  • Correction : augmentez la largeur minimale ; plafonnez la largeur du wrapper ; vérifiez les contraintes du parent (flex et règles de largeur).

5) Symptom : une carte force toute la rangée à s’élargir et provoque un débordement

  • Cause racine : dimensionnement intrinsèque dû à de longues chaînes sans coupure, tables larges ou enfants flex qui ne rétrécissent pas.
  • Correction : min-width: 0 sur la carte et les enfants flex concernés ; ajoutez overflow-wrap: anywhere pour les chaînes hostiles ; contraignez les médias.

6) Symptom : hauteurs de cartes incohérentes qui cassent la scannabilité

  • Cause racine : cartes avec contenu variable ; la grille aligne au start mais le contenu varie fortement.
  • Correction : décidez si la normalisation des hauteurs est souhaitée ; si oui, utilisez des blocs de contenu cohérents, clamp des lignes de texte, ou utilisez la mise en page interne pour aligner les actions en bas.

7) Symptom : le nombre de colonnes change quand une barre de défilement apparaît

  • Cause racine : la barre de défilement consomme de la taille inline ; votre seuil min+gap est sur un fil.
  • Correction : réduisez légèrement le min ; réduisez le gap ; ajoutez du padding au wrapper pour éviter les seuils exacts ; envisagez scrollbar-gutter: stable si approprié.

8) Symptom : les cartes se superposent ou s’effondrent de manière étrange

  • Cause racine : mélange de positionnement absolu, hauteurs fixes ou marges négatives avec des éléments de grille.
  • Correction : arrêtez cela. Si vous avez besoin d’une UI en couches, superposez à l’intérieur de la carte, pas en cassant la géométrie des éléments de la grille.

Blague n°2 : Si votre grille a besoin de six breakpoints, ce n’est pas « responsive ». C’est une négociation sous prise d’otage.

Trois mini-histoires d’entreprise (parce que c’est toujours « juste du CSS »)

Mini-histoire 1 : l’incident causé par une mauvaise hypothèse

Une équipe a déployé une nouvelle page « catalogue de services »: des cartes pour chaque service interne, avec propriétaire, niveau, liens et un badge de statut.
Ça semblait parfait en staging et sur toutes les captures d’écran de la PR.
La grille utilisait repeat(auto-fit, minmax(320px, 1fr)), gaps à 24px, et un conteneur avec padding.

Lundi matin, des tickets support sont arrivés : « La page est inutilisable sur les petits portables. » Certains utilisateurs avaient un défilement horizontal ; d’autres ne voyaient qu’une carte par ligne alors que deux auraient dû tenir.
Le bug n’était pas aléatoire. Il a été déclenché par deux conditions réelles en production : le zoom du navigateur et un widget de chat à droite persistant.
Ensemble ils ont réduit la largeur du conteneur juste en dessous du seuil des « deux colonnes ».

L’hypothèse erronée était subtile : ils supposaient que la largeur du conteneur équivalait à la largeur du viewport moins la sidebar.
En réalité, l’app avait un wrapper max-width, plus du padding, plus le widget de chat, plus la barre de défilement.
Le calcul min+gap était exactement sur le fil, donc toute petite réduction faisait tomber le nombre de colonnes et créait des comportements d’overflow gênants.

La correction a été ennuyeuse et immédiate : réduire le minimum de 320px à 296px, diminuer le gap à 16px, et basculer le min vers min(18rem, 100%) pour éviter le débordement aux petites largeurs.
Puis ils ont ajouté un max-width au wrapper pour que les écrans ultra-larges n’étirent pas les cartes en affiches.
Pas de nouveaux breakpoints. Juste des contraintes qui correspondaient à la réalité.

La leçon : si votre grille est « sur le seuil », traitez-la comme une dépendance de production avec un lien réseau instable.
Intégrez de la marge dans le calcul.

Mini-histoire 2 : l’optimisation qui s’est retournée contre eux

Une autre équipe voulait améliorer la perception de performance sur une page analytique lourde.
Ils ont remarqué un déplacement de mise en page pendant le chargement des données : des skeletons de cartes s’affichaient, puis le contenu réel arrivait et les hauteurs changeaient.
Quelqu’un a proposé « d’optimiser » : verrouiller les hauteurs et largeurs des cartes pour que la grille ne change jamais. Ça semblait fiable.

Ils ont implémenté des hauteurs fixes et forcé une mise en page stricte à trois colonnes via des media queries.
Le layout shift a diminué, oui. Mais ils ont discrètement introduit un nouveau mode d’échec : sur des conteneurs étroits (filtres ouverts, utilisateur en zoom), les trois colonnes fixes causaient un débordement persistant.
Les skeletons « tombaient bien » parce qu’ils étaient courts ; le vrai contenu débordait car les chaînes traduites et les graphiques avaient des minimums intrinsèques.

Le pire : le bug n’apparaissait que pour certaines locales. Les libellés traduits des boutons étaient plus longs, et les cartes à hauteur fixe coupaient le contenu.
Le support l’a rapporté comme « boutons manquants », ce qui équivaut en UI à une « perte de données ».
L’équipe a dû choisir entre déplacement visible et troncature invisible. Aucun n’était agréable.

Ils ont annulé le sizing fixe et ont plutôt utilisé une grille intrinsèque avec auto-fit/minmax, plus un clamp de lignes pour les quelques champs qui créaient des hauteurs extrêmes.
Les skeletons correspondaient aux tailles de contenu typiques, pas aux tailles idéalisées.
La page est devenue un peu plus « vivante » pendant le chargement, mais elle a cessé de casser.

La leçon : optimiser la stabilité en figeant la géométrie peut se retourner contre vous quand le contenu est la vraie source de variabilité.
Mieux vaut contraindre la variabilité que la nier.

Mini-histoire 3 : la pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe plateforme maintenait une bibliothèque de composants partagée utilisée par plusieurs équipes produit.
Les grilles de cartes étaient partout : listes d’incidents, rapports de coûts, tuiles de service, flags de fonctionnalités, tout.
Ils n’autorisaient pas de breakpoints personnalisés par page. Les gens se sont plaints. Fortement.

L’équipe plateforme a insisté sur une primitive de grille unique : repeat(auto-fit, minmax(min(20rem, 100%), 1fr)), gaps standards, et une règle documentée :
chaque carte doit définir min-width: 0, et toute rangée flex interne doit aussi définir min-width: 0 sur les enfants rétrécissables.
Ils exigeaient aussi un comportement de troncature pour les identifiants longs et ont fourni un composant dédié.

Puis une « urgence » est arrivée : une équipe produit majeure a introduit une nouvelle carte avec une longue chaîne de token sans coupure provenant d’un système en amont.
Sur les anciennes pages, ce type de contenu provoquait le classique débordement et le cauchemar de défilement horizontal.
Cette fois, la chaîne du token s’est enroulée ou tronquée comme prévu ; la grille est restée intacte ; seul ce champ était moche — ce qui est l’endroit correct pour la laideur.

Personne n’a écrit de hotfix à minuit. Personne n’a ajouté de breakpoint. Personne n’a discuté avec le design pendant une semaine.
Les contraintes ennuyeuses ont tenu, même sous un contenu hostile. C’est ce que vous voulez.

La leçon : si vous standardisez la primitive de grille et appliquez les règles de shrink/overflow, vous avez moins d’« incidents CSS ».
Ce n’est pas glamour. C’est opérationnellement sain.

Tâches pratiques : commandes, sorties, décisions

Vous avez demandé du pragmatique orienté production. Voilà ce que cela signifie : vous ne touchez pas seulement au CSS ; vous mesurez les conditions qui déclenchent les échecs.
Ces tâches sont volontairement « ops-y » parce que les régressions UI sont des pannes en tenue de business.

Task 1: Confirm your CSS actually includes the grid rule in the shipped bundle

cr0x@server:~$ rg -n "grid-template-columns: repeat\(auto-fit, minmax" dist/assets/*.css | head
dist/assets/app.4c9b1f0.css:1123:.grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}

What the output means: your built CSS contains the exact rule, minified.
Decision: if it’s missing, you’re debugging the wrong environment; fix build pipeline, tree-shaking, or CSS import order first.

Task 2: Detect whether a later rule overrides your grid definition

cr0x@server:~$ rg -n "grid-template-columns" dist/assets/*.css | head -n 12
dist/assets/app.4c9b1f0.css:1123:.grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
dist/assets/app.4c9b1f0.css:20988:.grid{grid-template-columns:1fr}

What the output means: you have at least two definitions; the latter may win depending on specificity and order.
Decision: fix selector scope (e.g., .card-grid not .grid), or adjust cascade layers so your component rule wins consistently.

Task 3: Inspect computed styles quickly with Playwright in headless mode

cr0x@server:~$ node -e 'const { chromium } = require("playwright"); (async()=>{const b=await chromium.launch();const p=await b.newPage();await p.goto("http://localhost:3000");const v=await p.$eval(".grid", el=>getComputedStyle(el).gridTemplateColumns);console.log(v);await b.close();})();'
280px 280px 280px

What the output means: at that viewport and container size, you’re getting three tracks of 280px.
Decision: if it prints none or a single 1fr, your selector didn’t match or layout is being overridden.

Task 4: Confirm the grid container width at runtime

cr0x@server:~$ node -e 'const { chromium } = require("playwright"); (async()=>{const b=await chromium.launch();const p=await b.newPage({viewport:{width:1024,height:768}});await p.goto("http://localhost:3000");const w=await p.$eval(".grid", el=>el.getBoundingClientRect().width);console.log(w);await b.close();})();'
944

What the output means: container width is 944px, not 1024px (padding/sidebars/etc.).
Decision: do the math: can 3 columns fit? 3*280 + 2*16 = 872. Yes. If you expected 4, you need a smaller min width, smaller gap, or a wider container.

Task 5: Reproduce the breakpoint-free behavior across viewports (automated)

cr0x@server:~$ node -e 'const { chromium } = require("playwright"); (async()=>{const b=await chromium.launch();const p=await b.newPage();for (const width of [375,480,768,1024,1440]){await p.setViewportSize({width,height:800});await p.goto("http://localhost:3000");const cols=await p.$eval(".grid", el=>getComputedStyle(el).gridTemplateColumns.split(" ").length);console.log(width, cols);}await b.close();})();'
375 1
480 1
768 2
1024 3
1440 4

What the output means: columns scale naturally without explicit breakpoints.
Decision: if the count jumps unpredictably, examine container width variability, not just viewport width.

Task 6: Find overflow sources by scanning for horizontal scroll in a screenshot run

cr0x@server:~$ node -e 'const { chromium } = require("playwright"); (async()=>{const b=await chromium.launch();const p=await b.newPage({viewport:{width:390,height:844}});await p.goto("http://localhost:3000");const hasOverflow=await p.evaluate(()=>document.documentElement.scrollWidth>document.documentElement.clientWidth);console.log("overflow",hasOverflow,"scrollWidth",document.documentElement.scrollWidth,"clientWidth",document.documentElement.clientWidth);await b.close();})();'
overflow true scrollWidth 428 clientWidth 390

What the output means: something is forcing horizontal overflow.
Decision: inspect grid children for long strings, images, or fixed-width elements; apply min-width: 0 and wrapping rules.

Task 7: Locate the worst offender element for overflow

cr0x@server:~$ node -e 'const { chromium } = require("playwright"); (async()=>{const b=await chromium.launch();const p=await b.newPage({viewport:{width:390,height:844}});await p.goto("http://localhost:3000");const id=await p.evaluate(()=>{const els=[...document.querySelectorAll(".grid *")];let worst={w:0,sel:""};for(const el of els){const r=el.getBoundingClientRect();if(r.width>worst.w){worst={w:r.width,sel:el.tagName.toLowerCase()+"."+[...el.classList].join(".")};}}return worst;});console.log(id);await b.close();})();'
{ w: 612.345703125, sel: 'div.value' }

What the output means: an element inside the grid (here div.value) is wider than the viewport.
Decision: apply truncation/wrapping rules to that component; don’t “fix” it by shrinking the whole grid.

Task 8: Confirm text wrapping behavior for hostile unbroken strings

cr0x@server:~$ cat wrap-test.css
.value { overflow-wrap: anywhere; word-break: break-word; }
...output...

What the output means: you’re allowing breaks even in long tokens.
Decision: if tokens must remain copyable, prefer truncation with copy-to-clipboard UI; wrapping a 64-char token across 5 lines is technically correct and emotionally harmful.

Task 9: Validate the column threshold math with a quick script

cr0x@server:~$ node -e 'const min=280, gap=16; for (const cols of [1,2,3,4,5]){console.log(cols, cols*min + (cols-1)*gap);} '
1 280
2 576
3 872
4 1168
5 1464

What the output means: the minimum container widths for each column count.
Decision: compare with actual container widths (Task 4). If you’re hovering around 872px, expect flapping between 2 and 3 columns as UI chrome changes.

Task 10: Check whether your cards are stretching vertically due to alignment

cr0x@server:~$ node -e 'const { chromium } = require("playwright"); (async()=>{const b=await chromium.launch();const p=await b.newPage({viewport:{width:1200,height:800}});await p.goto("http://localhost:3000");const ai=await p.$eval(".grid", el=>getComputedStyle(el).alignItems);console.log(ai);await b.close();})();'
stretch

What the output means: items may stretch to match row height.
Decision: set align-items: start on the grid if you want natural card heights.

Task 11: Detect whether images are the intrinsic-width culprit

cr0x@server:~$ node -e 'const { chromium } = require("playwright"); (async()=>{const b=await chromium.launch();const p=await b.newPage({viewport:{width:390,height:844}});await p.goto("http://localhost:3000");const imgs=await p.$$eval(".grid img", els=>els.map(i=>({w:i.getBoundingClientRect().width, css:getComputedStyle(i).maxWidth, src:i.getAttribute("src")})));console.log(imgs.slice(0,3));await b.close();})();'
[
  { w: 420, css: 'none', src: '/assets/chart.png' }
]

What the output means: an image is wider than it should be and unconstrained (max-width: none).
Decision: apply max-width: 100% and ensure responsive sizing; otherwise one chart screenshot can wreck your entire grid.

Task 12: Ensure your grid uses gap, not margins that collapse unpredictably

cr0x@server:~$ rg -n "\.card\{[^}]*margin" src/components | head
src/components/Card.css:.card{margin:12px}

What the output means: cards are adding margins that interfere with the grid’s own spacing.
Decision: remove external margins on grid items; use gap on the grid container for consistent, predictable spacing.

Task 13: Confirm that long flex rows inside cards can shrink

cr0x@server:~$ rg -n "display:\s*flex" -S src/components/Card* | head
src/components/CardMeta.css:.metaRow{display:flex;gap:8px}

What the output means: you likely have flex children that can create min-content overflow.
Decision: add min-width: 0 on the flex item that should shrink (often the text container), and truncation where appropriate.

Task 14: Smoke test for “empty track” behavior by switching auto-fit/auto-fill

cr0x@server:~$ perl -0777 -pe 's/repeat\(auto-fit,/repeat(auto-fill,/g' -i src/styles/grid.css
...output...

What the output means: you temporarily swapped behavior.
Decision: if the “lonely last card” suddenly stops stretching, you’ve proven that track collapsing is part of your UX choice. Switch back and decide intentionally.

Ces tâches paraissent excessives jusqu’à ce qu’une régression de mise en page casse une démo commerciale. Ensuite, elles ressemblent à une assurance.

Listes de vérification / plan étape par étape

Étape par étape : implémenter une grille de cartes auto-fit sûre pour la production

  1. Définissez la largeur minimale lisible de la carte.
    Utilisez du contenu réel. Si vous n’en avez pas, utilisez les pires chaînes réalistes (IDs, noms, boutons localisés).
  2. Commencez par la règle de base :
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  3. Ajoutez un clamp défensif du minimum si la grille peut vivre dans des conteneurs étroits :
    minmax(min(18rem, 100%), 1fr).
  4. Utilisez gap sur la grille, retirez les marges des cartes dans ce contexte.
  5. Définissez min-width: 0 sur la carte et sur les enfants flex rétrécissables à l’intérieur des cartes.
  6. Décidez d’une largeur max de carte.
    Si les cartes semblent bizarres étirées, utilisez minmax(18rem, 24rem) et alignez avec justify-content.
  7. Gérez explicitement le contenu hostile.
    Tronquez les longues chaînes, clamp des lignes, contraignez images et graphiques.
  8. Testez avec zoom et panneaux latéraux.
    Traitez la variabilité de la largeur du conteneur comme une entrée de première classe.
  9. Automatisez une balayage de viewports (comme la Task 5) et échouez les builds si un débordement apparaît (Task 6).
  10. Documentez le contrat pour les auteurs de cartes : pas de largeurs fixes, médias bornés, et comment gérer les textes longs.

Checklist : avant d’ajouter une media query

  • Avez‑vous vérifié la largeur réelle du conteneur (pas la largeur du viewport) ?
  • Avez‑vous fait le calcul min+gap pour les comptes de colonnes attendus ?
  • Avez‑vous ajouté min-width: 0 là où c’est nécessaire ?
  • Avez‑vous contraint images et graphiques à la largeur de la carte ?
  • Avez‑vous décidé si les cartes doivent s’étirer (1fr) ou garder une largeur cohérente (max plafonné) ?
  • Avez‑vous testé avec les longues traductions et identifiants ?
  • Avez‑vous reproduit le bug avec des contrôles automatisés viewport/overflow ?

Si vous ne pouvez pas cocher ces cases, une media query est un pansement sur une fuite derrière le mur.
Ça marchera jusqu’à la prochaine rénovation.

FAQ

1) Est-ce que repeat(auto-fit, minmax()) est vraiment « responsive » sans media queries ?

Oui, pour les changements de nombre de colonnes pilotés par l’espace disponible. Elle répond à la taille du conteneur, qui est ce qui compte réellement dans des applications complexes.
Les media queries répondent à la taille du viewport, qui n’est que faiblement corrélée à l’espace dont dispose votre grille.

2) Dois‑je utiliser auto-fit ou auto-fill pour des cartes ?

Par défaut, utilisez auto-fit. Il effondre les pistes vides pour que les cartes restantes s’étendent naturellement.
Utilisez auto-fill quand vous voulez intentionnellement réserver des colonnes vides (rare pour les cartes, courant pour les layouts de type calendrier).

3) Pourquoi ma grille déborde alors que j’ai défini une largeur minimale ?

Parce que les pistes de la grille respectent le dimensionnement intrinsèque. Un enfant peut avoir une largeur min-content plus grande que votre minimum de piste.
Corrigez‑le avec min-width: 0 sur les éléments de la grille et les enfants flex, plus des règles de wrapping/troncature pour les longues chaînes et des contraintes pour les médias.

4) Quelle largeur minimale dois‑je choisir ?

Choisissez la plus petite largeur à laquelle le contenu de la carte reste lisible et utilisable. Pour des tableaux de bord typiques, 280–360px est courant.
Puis testez avec des chaînes pires et un conteneur étroit, pas seulement un viewport de téléphone.

5) Mes designers détestent les cartes étirées. Quel est le modèle ?

Utilisez une largeur maximale plafonnée dans minmax, comme minmax(20rem, 24rem), et définissez justify-content: center ou start.
Sinon, plafonnez la largeur du wrapper.

6) Les container queries rendent‑elles cela obsolète ?

Non. Les container queries aident quand les changements de mise en page dépendent de la taille du composant (par ex. remplacer l’intérieur d’une carte).
Pour « combien de colonnes tiennent », auto-fit/minmax reste la primitive la plus simple et la plus robuste.

7) Comment prévenir le layout shift quand les données se chargent ?

Faites correspondre les skeletons aux tailles de contenu typiques, pas aux tailles idéalisées. Contraignez le contenu variable (clamp des lignes, ratio fixe pour les graphiques).
Ne figez pas trop les tailles pour éviter que le contenu réel ne déborde ou soit coupé.

8) Pourquoi le nombre de colonnes change quand une barre de défilement apparaît ?

Les barres de défilement consomment de la taille inline, poussant le conteneur sous le seuil où une colonne de moins tient.
Donnez‑vous de la marge : réduisez légèrement le min ou le gap, ou stabilisez l’espace de la barre de défilement avec le CSS approprié.

9) Puis‑je faire un layout en masonry avec ça ?

Pas vraiment. Les rangées en grid s’alignent ; le masonry a besoin d’un placement vertical indépendant.
Vous pouvez simuler un aspect masonry avec un packing dense dans certains cas, mais pour une UI de production où l’ordre et la scannabilité comptent, évitez le masonry pour des cartes avec actions.

10) Quel est le correctif « une ligne » le plus important pour les anomalies de grille ?

min-width: 0 sur les éléments de la grille (et les enfants flex rétrécissables). Cela empêche le contenu de forcer les pistes à être plus larges que prévu.
C’est l’équivalent CSS de « avez‑vous vérifié le DNS ? » — agaçant, mais souvent la bonne réponse.

Conclusion : prochaines étapes à livrer

L’ère des breakpoints a appris aux gens à traiter la mise en page comme un ensemble de régimes codés en dur. Ça marche jusqu’à ce que votre app ait de la vraie chrome, du vrai contenu, de vrais niveaux de zoom et de vrais utilisateurs.
Les grilles de cartes sont l’endroit idéal pour passer à une mise en page basée sur des contraintes.

Faites ceci ensuite, dans l’ordre :

  1. Implémentez repeat(auto-fit, minmax(min(X, 100%), 1fr)) avec un X sensé basé sur la lisibilité du contenu.
  2. Définissez min-width: 0 sur les cartes et les enfants flex rétrécissables.
  3. Décidez si vous voulez des cartes étirées (1fr) ou des largeurs cohérentes (max plafonné).
  4. Ajoutez des vérifications automatisées pour l’overflow et le nombre de colonnes sur différents viewports — traitez les régressions de mise en page comme des régressions opérationnelles.

Si vous faites ces quatre choses, vous écrirez moins de media queries, livrerez moins de mises en page « ça marche sur ma machine », et passerez moins de temps à déboguer des fantômes causés par des largeurs de conteneur que vous n’avez pas mesurées.
Ce qui est le vrai luxe.

← Précédent
E‑mail : confusion entre S/MIME et TLS — ce qui améliore vraiment la sécurité
Suivant →
WordPress bloqué par le WAF : ajuster les règles sans créer de faille de sécurité

Laisser un commentaire