Les grilles bento sont partout — Apple les utilise pour ses pages produit, Linear pour son changelog, Vercel pour son dashboard. L'attrait est évident : des layouts de cartes asymétriques créent un intérêt visuel que les grilles uniformes ne peuvent pas égaler. Chaque carte reçoit exactement l'espace dont son contenu a besoin, et la composition globale paraît curatée plutôt que générée par un template.
Ce guide couvre six patterns de grilles bento en React avec Tailwind CSS, d'un layout basique à des variantes animées et interactives. Chacun est prêt à copier-coller.
1. Grille bento basique
La fondation : une CSS Grid avec grid-template-columns et des utilitaires de spanning. Le layout utilise une grille de base à 4 colonnes où les cartes occupent 1 ou 2 colonnes et rangées :
export function BentoBasic() {
return (
<section className="mx-auto max-w-6xl px-4 py-24">
<h2 className="mb-12 text-center text-3xl font-bold">Pourquoi les équipes nous choisissent</h2>
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
{/* Grande carte — 2x2 */}
<div className="col-span-2 row-span-2 rounded-2xl border border-border bg-card p-8">
<h3 className="text-2xl font-bold">Ultra rapide</h3>
<p className="mt-2 text-muted-foreground">
Des temps de réponse sous les 100ms sur chaque interaction.
</p>
</div>
{/* Petites cartes — 1x1 */}
<div className="rounded-2xl border border-border bg-card p-6">
<h3 className="font-semibold">99.9% uptime</h3>
</div>
<div className="rounded-2xl border border-border bg-card p-6">
<h3 className="font-semibold">Certifié SOC 2</h3>
</div>
<div className="rounded-2xl border border-border bg-card p-6">
<h3 className="font-semibold">50+ intégrations</h3>
</div>
<div className="rounded-2xl border border-border bg-card p-6">
<h3 className="font-semibold">Support 24/7</h3>
</div>
</div>
</section>
);
}
La clé est col-span-2 row-span-2 sur la carte principale. Elle occupe 4x la surface d'une carte normale, créant l'asymétrie signature du bento.
2. Layout asymétrique
Une composition plus éditoriale où la grille change de forme entre les rangées. Au lieu d'une grille de base uniforme, chaque rangée définit sa propre répartition de colonnes :
export function BentoAsymmetric() {
return (
<div className="mx-auto grid max-w-6xl gap-4 px-4 py-24">
{/* Rangée 1 : split 60/40 */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-[1.5fr_1fr]">
<div className="rounded-2xl border border-border bg-card p-8">
<h3 className="text-xl font-bold">Collaboration en temps réel</h3>
<p className="mt-2 text-muted-foreground">
Voyez les changements de votre équipe en direct. Pas de refresh nécessaire.
</p>
</div>
<div className="rounded-2xl border border-border bg-card p-8">
<h3 className="text-xl font-bold">Historique des versions</h3>
<p className="mt-2 text-muted-foreground">Chaque changement est tracé.</p>
</div>
</div>
{/* Rangée 2 : split 40/60 (inversé) */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-[1fr_1.5fr]">
<div className="rounded-2xl border border-border bg-card p-8">
<h3 className="text-xl font-bold">Accès API</h3>
<p className="mt-2 text-muted-foreground">API REST + GraphQL complète.</p>
</div>
<div className="rounded-2xl border border-border bg-card p-8">
<h3 className="text-xl font-bold">Analytics avancées</h3>
<p className="mt-2 text-muted-foreground">
Dashboards personnalisés, exports et rapports programmés.
</p>
</div>
</div>
{/* Rangée 3 : tiers égaux */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<div className="rounded-2xl border border-border bg-card p-6 text-center">
<p className="text-3xl font-bold">10M+</p>
<p className="text-sm text-muted-foreground">Requêtes par jour</p>
</div>
<div className="rounded-2xl border border-border bg-card p-6 text-center">
<p className="text-3xl font-bold">99.99%</p>
<p className="text-sm text-muted-foreground">SLA uptime</p>
</div>
<div className="rounded-2xl border border-border bg-card p-6 text-center">
<p className="text-3xl font-bold">150+</p>
<p className="text-sm text-muted-foreground">Pays desservis</p>
</div>
</div>
</div>
);
}
Cette approche est plus facile à raisonner que le spanning dans une grille unique parce que chaque rangée est indépendante. Elle se replie aussi élégamment en une seule colonne sur mobile.
3. Bento avec révélation animée
Combinez la grille basique avec des animations d'entrée stagger de Framer Motion :
"use client";
import { motion } from "motion/react";
const container = {
hidden: {},
visible: { transition: { staggerChildren: 0.06 } },
};
const card = {
hidden: { opacity: 0, scale: 0.95, y: 12 },
visible: {
opacity: 1,
scale: 1,
y: 0,
transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
},
};
export function BentoAnimated({ items }: { items: { title: string; span?: string }[] }) {
return (
<motion.div
variants={container}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-80px" }}
className="mx-auto grid max-w-6xl grid-cols-2 gap-4 px-4 md:grid-cols-4"
>
{items.map((item) => (
<motion.div
key={item.title}
variants={card}
className={`rounded-2xl border border-border bg-card p-6 ${item.span ?? ""}`}
>
<h3 className="font-semibold">{item.title}</h3>
</motion.div>
))}
</motion.div>
);
}
Passez span: "col-span-2 row-span-2" sur les items qui doivent être plus grands. Le délai de stagger de 0.06s crée un effet de cascade rapide qui paraît dynamique sans être lent.
4. Bento draggable
Une variante interactive où les utilisateurs peuvent réordonner les cartes. Utilise le composant Reorder de Framer Motion :
"use client";
import { Reorder } from "motion/react";
import { useState } from "react";
interface BentoItem {
id: string;
title: string;
description: string;
}
export function BentoDraggable({ initialItems }: { initialItems: BentoItem[] }) {
const [items, setItems] = useState(initialItems);
return (
<Reorder.Group
axis="y"
values={items}
onReorder={setItems}
className="mx-auto grid max-w-4xl gap-4 px-4"
>
{items.map((item) => (
<Reorder.Item
key={item.id}
value={item}
className="cursor-grab rounded-2xl border border-border bg-card p-6 active:cursor-grabbing"
>
<h3 className="font-semibold">{item.title}</h3>
<p className="mt-1 text-sm text-muted-foreground">{item.description}</p>
</Reorder.Item>
))}
</Reorder.Group>
);
}
Le bento draggable est idéal pour les dashboards, les flux d'onboarding ou les démos interactives sur les landing pages où vous voulez mettre en avant la personnalisation.
5. Bento avec glow néon
Pour les landing pages à thème sombre, ajoutez un effet de glow subtil sur les bordures des cartes au survol :
export function BentoNeon() {
return (
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
<div
className="group relative col-span-2 row-span-2 overflow-hidden rounded-2xl border border-neutral-800 bg-neutral-950 p-8 transition-colors hover:border-blue-500/50"
>
{/* Effet de glow */}
<div className="pointer-events-none absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100">
<div className="absolute -inset-1 bg-blue-500/10 blur-xl" />
</div>
<div className="relative">
<h3 className="text-xl font-bold text-white">Recherche propulsée par l'IA</h3>
<p className="mt-2 text-neutral-400">
Requêtes en langage naturel sur tout votre espace de travail.
</p>
</div>
</div>
{/* Cartes additionnelles avec différentes couleurs de glow */}
<div className="group relative overflow-hidden rounded-2xl border border-neutral-800 bg-neutral-950 p-6 transition-colors hover:border-purple-500/50">
<div className="pointer-events-none absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100">
<div className="absolute -inset-1 bg-purple-500/10 blur-xl" />
</div>
<div className="relative">
<h3 className="font-semibold text-white">Automatisation</h3>
</div>
</div>
<div className="group relative overflow-hidden rounded-2xl border border-neutral-800 bg-neutral-950 p-6 transition-colors hover:border-green-500/50">
<div className="pointer-events-none absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100">
<div className="absolute -inset-1 bg-green-500/10 blur-xl" />
</div>
<div className="relative">
<h3 className="font-semibold text-white">Intégrations</h3>
</div>
</div>
</div>
);
}
Le glow est un div flouté avec faible opacité qui apparaît au survol. Le pattern group / group-hover garde l'effet déclaratif — pas de JavaScript nécessaire.
6. Bento texte + image
Un layout hybride mélangeant cartes textuelles et images plein bord :
export function BentoMedia() {
return (
<div className="mx-auto grid max-w-6xl grid-cols-2 gap-4 px-4 md:grid-cols-4">
{/* Carte texte — 2 colonnes */}
<div className="col-span-2 rounded-2xl border border-border bg-card p-8">
<h3 className="text-xl font-bold">Conçu pour les développeurs</h3>
<p className="mt-2 text-muted-foreground">
Support TypeScript first-class, docs API complètes et une CLI qui ne vous gêne pas.
</p>
</div>
{/* Carte image — 2 colonnes */}
<div className="col-span-2 overflow-hidden rounded-2xl border border-border">
<img
src="/dashboard-screenshot.png"
alt="Screenshot du dashboard"
className="h-full w-full object-cover"
/>
</div>
{/* Rangée image pleine largeur */}
<div className="col-span-2 overflow-hidden rounded-2xl border border-border md:col-span-4">
<img
src="/workflow-diagram.png"
alt="Diagramme d'automatisation de workflow"
className="h-64 w-full object-cover"
/>
</div>
</div>
);
}
La combinaison de cartes texte et image casse la monotonie visuelle et permet de montrer le produit en contexte aux côtés des descriptions de fonctionnalités.
Découvrez plus de layouts bento
Ces six patterns couvrent les cas d'usage de grilles bento les plus courants, mais il existe bien d'autres variations — fonds dégradés, layouts riches en icônes, compteurs de stats et démos interactives. Le catalogue Incubator a une collection dédiée de sections grilles bento, toutes construites avec Tailwind CSS et prêtes à copier dans votre projet React. Chaque variante inclut le comportement responsive, le support dark mode et des animations Framer Motion optionnelles. Parcourez le catalogue complet pour trouver le layout qui correspond à votre landing page.