Feature Grid React: Bento, Icon Grid and Alternating Layouts
Build bento grids, icon feature grids, and alternating layouts in React with Tailwind CSS. Practical patterns, real code, no fluff.
Why Feature Grids Still Run Landing Pages in 2026
Feature sections haven't gone anywhere. Every SaaS, every developer tool, every product page still needs a way to say "here's what this thing does" — and a well-built feature grid is the cleanest answer to that problem. It's visual, scannable, and it doesn't force your visitor to read a wall of copy.
Honestly, the challenge isn't whether to use a feature grid — it's picking the *right* layout for what you're building. A bento grid hits different than a 3-column icon grid. An alternating left-right layout tells a different story than a card wall. The choice shapes how users absorb information.
That said, most devs reach for the first pattern they find and never look back. This article walks through the three layouts you'll actually ship — bento, icon grid, and alternating — with real Tailwind + React code for each. If you want to see styled components across different visual systems, browse the components to get a sense of what's possible before you start writing CSS.
Bento Grid: The Layout That Took Over in 2024
Apple mainstreamed the bento grid at WWDC 2023 and the frontend world hasn't recovered. You know the pattern — asymmetric cards at different sizes arranged on a strict grid, each card spotlighting one feature with an icon, a headline, and maybe a small visual. It feels editorial without being annoying.
In React + Tailwind, you build this with CSS Grid and col-span/row-span utilities. The trick is defining a grid-cols-4 or grid-cols-6 base and then letting individual cards span columns deliberately — not randomly. Here's a minimal working example:
const features = [
{ title: 'Real-time Sync', desc: 'Changes propagate in under 50ms.', span: 'col-span-2 row-span-2' },
{ title: 'Offline Mode', desc: 'Works without a connection.', span: 'col-span-1 row-span-1' },
{ title: 'Access Control', desc: 'Role-based permissions out of the box.', span: 'col-span-1 row-span-1' },
{ title: 'Analytics', desc: 'Built-in dashboards, no third-party needed.', span: 'col-span-2 row-span-1' },
];
export function BentoGrid() {
return (
<div className="grid grid-cols-4 gap-4 p-6">
{features.map((f) => (
<div
key={f.title}
className={`${f.span} rounded-2xl bg-white/5 border border-white/10 p-6 flex flex-col gap-2`}
>
<h3 className="text-lg font-semibold text-white">{f.title}</h3>
<p className="text-sm text-zinc-400">{f.desc}</p>
</div>
))}
</div>
);
}Worth noting: on mobile, col-span values need overrides or you'll get a broken layout at 375px wide. Add grid-cols-1 md:grid-cols-4 and reset spans with col-span-full on small screens. Quick aside — the row-span-2 on the hero card is what creates visual weight; remove it and the layout immediately looks flat.
If you want a glassmorphism-styled bento grid, the glassmorphism generator will spit out the exact backdrop-filter and border values you need without guessing.
Icon Grid: Three Columns, Clean, Always Works
When you don't have time to design a bento layout — or when your PM hands you 9 features and says "make them all equal" — the 3-column icon grid is your move. It's predictable, it scans fast, and it works at every viewport width without much fuss.
The pattern is dead simple: icon on top, bold headline, 1-2 line description. Repeat 6 to 12 times. The only real design decisions are icon size (24px is usually right, 20px feels too small at body text sizes), card padding (32px is a safe floor), and whether cards have a visible border or just a background fill.
const items = [
{ icon: '⚡', title: 'Fast Builds', desc: 'Sub-second hot reload on any machine.' },
{ icon: '🔒', title: 'Secure by Default', desc: 'End-to-end encryption on every request.' },
{ icon: '🧩', title: 'Modular', desc: 'Swap pieces without rewriting the whole thing.' },
// ...more items
];
export function IconGrid() {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map((item) => (
<div key={item.title} className="flex flex-col gap-3 p-8 rounded-xl border border-zinc-800 bg-zinc-900">
<span className="text-3xl">{item.icon}</span>
<h3 className="text-base font-bold text-white">{item.title}</h3>
<p className="text-sm text-zinc-400 leading-relaxed">{item.desc}</p>
</div>
))}
</div>
);
}In practice, emoji icons look fine in prototypes but you'll want to swap them for SVG icons in production — they scale sharper, they're theme-consistent, and you won't hit rendering differences across operating systems. Libraries like Lucide or Heroicons drop straight into this pattern with zero configuration.
One more thing — if you're building for a design-forward product, check out the glassmorphism components section. The frosted-card aesthetic maps cleanly onto this icon grid pattern and makes the whole section feel a lot less generic.
Alternating Layouts: Feature Deep-Dives That Actually Convert
Alternating feature sections — image or graphic on the left, text on the right, then flip for the next feature — are the right call when you have something worth explaining. Not just a name and a description, but a real "here's what this looks like and why it matters" story. Marketing sites for tools that need a little education live here.
The React pattern is straightforward. Map over features, pass an index to each, and use index % 2 === 0 to flip the Flexbox direction. The flex-row-reverse Tailwind class is your friend.
const features = [
{
title: 'Visual Editor',
desc: 'Drag, resize, and configure components directly on the canvas. No context-switching between code and preview.',
visual: '/screens/editor.png',
},
{
title: 'Token System',
desc: 'Design tokens flow from your config file into every component automatically. Change one value, update everywhere.',
visual: '/screens/tokens.png',
},
];
export function AlternatingFeatures() {
return (
<div className="flex flex-col gap-24">
{features.map((f, i) => (
<div
key={f.title}
className={`flex flex-col md:flex-row items-center gap-12 ${
i % 2 !== 0 ? 'md:flex-row-reverse' : ''
}`}
>
<div className="flex-1">
<img src={f.visual} alt={f.title} className="rounded-2xl w-full shadow-xl" />
</div>
<div className="flex-1 flex flex-col gap-4">
<h3 className="text-2xl font-bold text-white">{f.title}</h3>
<p className="text-zinc-400 leading-relaxed">{f.desc}</p>
</div>
</div>
))}
</div>
);
}Look, this pattern breaks on mobile if you let flex-row-reverse persist below md. You're stacking vertically anyway at small sizes, so the reversal becomes meaningless — and sometimes creates weird ordering. The md:flex-row-reverse scoped breakpoint in the example above handles this correctly. Don't skip it.
The gap between sections matters a lot here. A gap-24 (96px) between alternating blocks gives each feature enough breathing room to feel like its own moment. Tighten it to gap-12 and the whole section feels rushed.
Responsive Grid Behaviour: The Part Most Devs Rush
Every one of these layouts has a mobile problem if you don't think about it upfront. The bento grid collapses in unexpected ways. The icon grid at 2 columns on a 375px screen means each card is barely 160px wide — which is too narrow for comfortable reading. The alternating layout can stack in the wrong order.
The safest mobile approach across all three patterns is grid-cols-1 as the base, then scale up with sm:grid-cols-2 and lg:grid-cols-3 or lg:grid-cols-4. Don't fight Tailwind's responsive system. Use it as intended and you won't be debugging layout at 11pm before a launch.
Worth noting: bento grids specifically need a fallback where all col-span and row-span values revert to 1 below md. The cleanest way to do this is to encode a mobileSpan alongside your regular span in the data array and switch between them with a breakpoint class.
For anything beyond basic responsive tweaks — animations, hover states, entrance effects — CSS scroll animations is worth reading. Pairing a feature grid with intersection-observer-triggered fade-ins is a very common pattern and the implementation is less complex than it looks.
Picking the Right Pattern for Your Product
So which layout should you actually ship? It depends on what you're selling and how much each feature needs to breathe. If all your features are roughly equal in importance and you have 6-12 of them, the icon grid is almost always the right call. Fast to build, familiar to users, easy to maintain.
If two or three features are significantly more important than the others — your main differentiators — a bento grid lets you size those cards up and signal their importance visually without writing a single word of hierarchy copy. The asymmetry does the work for you.
Alternating layouts are for when you have a visual story to tell. If your feature has a screenshot, a diagram, or a meaningful animation, use alternating. If it doesn't, don't fake it with a generic stock photo — that actually hurts credibility.
In practice, many landing pages combine all three. An alternating layout for the 2-3 hero features, then an icon grid for the full feature list lower on the page. The bento grid often lives in "Why us?" sections where you're contrasting capabilities. Mixing them isn't a design mistake — it's appropriate when each section has a different communication goal.
If you want a jumpstart rather than building from scratch, the templates section has full landing page compositions that already use these grid patterns in production-ready configurations.
FAQ
A bento grid is a visual design pattern that uses CSS Grid under the hood — the difference is intentional asymmetry. Cards span different column and row counts to create visual hierarchy, while a regular grid keeps everything uniform. Same CSS spec, different intent.
CSS Grid for two-dimensional layouts like bento and icon grids, Flexbox for alternating single-row feature sections. They're not competing tools — each pattern tells you which one to reach for.
Set grid-cols-1 as the base and use md:grid-cols-4 to activate the full layout. Encode a mobileSpan: 'col-span-full' alongside each card's desktop span and apply the right class conditionally per breakpoint.
Yes — CSS @keyframes with an animation-delay staggered by index covers most use cases. For scroll-triggered entrance animations, the Intersection Observer API gets you there without pulling in Framer Motion.