02 / 15
All 15 in 15 Tailwind Card Grid Layouts
- 01Tailwind Stats Dashboard KPI Grid
- 02Tailwind Hero Photo Collage Grid
- 03Tailwind Pricing Comparison Card Grid
- 04Tailwind Testimonial Wall Grid
- 05Tailwind Editorial Tabloid Bento Layout
- 06Tailwind Featured Article Magazine Grid
- 07Tailwind Asymmetric Bento Box Feature Grid
- 08Tailwind Stratified Bento Grid Layout
- 09Tailwind Equal-Height Product Grid
- 10Tailwind Fluid Masonry Card Grid
- 11Tailwind Auto-Fit Blog Archive Grid
- 12Tailwind Team Directory Face Grid
- 13Tailwind 3D Hover Flip Card Grid
- 14Tailwind Use-Case Showcase Card Grid
- 15Tailwind Glass Bento Grid Layout
Tailwind Hero Photo Collage Grid
A landing hero with narrative copy on the left and a five-tile asymmetric photo collage on the right. Every tile states its own placement — col-start-1 col-span-2 row-start-1 row-span-2 — so the collage rhythm is readable in the markup, and the readability overlay every photo grid ships is one after: chain instead of a pseudo-element block.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcg-12 w-full min-h-svh flex items-center justify-center bg-tcg12-bg font-tcg12-sans text-tcg12-ink px-[clamp(20px,3vw,48px)] py-[clamp(28px,4vw,56px)]" aria-label="Hero photo collage grid demo">
<div class="w-full max-w-[1200px] grid [grid-template-columns:1fr_1.2fr] max-[900px]:[grid-template-columns:1fr] items-center gap-[clamp(24px,3.4vw,56px)] max-[900px]:gap-8">
<div class="flex flex-col gap-4.5 max-w-[520px] max-[900px]:max-w-none max-[900px]:items-center max-[900px]:text-center">
<span class="w-fit px-3 py-1.5 rounded-full text-[.72rem] font-bold tracking-[0.16em] uppercase text-tcg12-accent bg-tcg12-accentsoft">New · 2026 collection</span>
<h2 class="text-[clamp(2.2rem,4.5vw,3.6rem)] font-black tracking-[-0.03em] leading-[1.02]">Stay places that<br><em class="font-tcg12-serif italic font-bold text-tcg12-accent">feel like home.</em></h2>
<p class="max-w-[46ch] text-[clamp(.96rem,1.2vw,1.1rem)] leading-[1.55] text-tcg12-muted text-pretty">Hand-picked stays in 47 cities. Verified by humans. Booked by people who care where they sleep.</p>
<div class="mt-1.5 flex flex-wrap items-center gap-4">
<a href="#stays" class="inline-flex items-center min-h-11 px-5.5 py-3.5 rounded-[10px] bg-tcg12-ink text-white text-[.96rem] font-bold no-underline transition-[background,translate] duration-200 hover:bg-tcg12-accent hover:-translate-y-0.5 focus-visible:outline-2 focus-visible:outline-tcg12-accent focus-visible:outline-offset-3 motion-reduce:transition-none motion-reduce:hover:translate-y-0">Browse stays →</a>
<a href="#how" class="inline-flex items-center min-h-11 text-[.94rem] font-semibold text-tcg12-ink underline decoration-1 underline-offset-4 hover:text-tcg12-accent focus-visible:outline-2 focus-visible:outline-tcg12-accent focus-visible:outline-offset-3">How it works</a>
</div>
<dl class="mt-4 pt-6 flex gap-[clamp(20px,3vw,40px)] max-[560px]:gap-3.5 border-t border-tcg12-line">
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">4.9 ★</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Avg rating</dt>
</div>
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">12K+</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Verified stays</dt>
</div>
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">47</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Cities</dt>
</div>
</dl>
</div>
<div class="grid [grid-template-columns:repeat(3,1fr)] [grid-template-rows:repeat(3,1fr)] gap-[clamp(8px,1vw,14px)] aspect-[1.05/1] min-h-[clamp(320px,42vw,560px)] max-[900px]:aspect-[1.4/1] max-[900px]:min-h-70 max-[560px]:aspect-square" aria-label="Featured stays">
<div class="relative isolate col-start-1 col-span-2 row-start-1 row-span-2 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#1e3a5f,#0f1c2e] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Iceland</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Black Sand Cabin</span>
</div>
<div class="relative isolate col-start-3 row-start-1 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#d97a99,#7a3855] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Tokyo</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Shibuya Loft</span>
</div>
<div class="relative isolate col-start-3 row-start-2 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#f4a572,#d97f4f] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Lisbon</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Alfama Studio</span>
</div>
<div class="relative isolate col-start-1 row-start-3 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#e6c54a,#a8732d] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Marrakech</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Medina Riad</span>
</div>
<div class="relative isolate col-start-2 col-span-2 row-start-3 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#7aa852,#3d6a2a] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Brooklyn</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Williamsburg Flat</span>
</div>
</div>
</div>
</section><section class="tcg-12 w-full min-h-svh flex items-center justify-center bg-tcg12-bg font-tcg12-sans text-tcg12-ink px-[clamp(20px,3vw,48px)] py-[clamp(28px,4vw,56px)]" aria-label="Hero photo collage grid demo">
<div class="w-full max-w-[1200px] grid [grid-template-columns:1fr_1.2fr] max-[900px]:[grid-template-columns:1fr] items-center gap-[clamp(24px,3.4vw,56px)] max-[900px]:gap-8">
<div class="flex flex-col gap-4.5 max-w-[520px] max-[900px]:max-w-none max-[900px]:items-center max-[900px]:text-center">
<span class="w-fit px-3 py-1.5 rounded-full text-[.72rem] font-bold tracking-[0.16em] uppercase text-tcg12-accent bg-tcg12-accentsoft">New · 2026 collection</span>
<h2 class="text-[clamp(2.2rem,4.5vw,3.6rem)] font-black tracking-[-0.03em] leading-[1.02]">Stay places that<br><em class="font-tcg12-serif italic font-bold text-tcg12-accent">feel like home.</em></h2>
<p class="max-w-[46ch] text-[clamp(.96rem,1.2vw,1.1rem)] leading-[1.55] text-tcg12-muted text-pretty">Hand-picked stays in 47 cities. Verified by humans. Booked by people who care where they sleep.</p>
<div class="mt-1.5 flex flex-wrap items-center gap-4">
<a href="#stays" class="inline-flex items-center min-h-11 px-5.5 py-3.5 rounded-[10px] bg-tcg12-ink text-white text-[.96rem] font-bold no-underline transition-[background,translate] duration-200 hover:bg-tcg12-accent hover:-translate-y-0.5 focus-visible:outline-2 focus-visible:outline-tcg12-accent focus-visible:outline-offset-3 motion-reduce:transition-none motion-reduce:hover:translate-y-0">Browse stays →</a>
<a href="#how" class="inline-flex items-center min-h-11 text-[.94rem] font-semibold text-tcg12-ink underline decoration-1 underline-offset-4 hover:text-tcg12-accent focus-visible:outline-2 focus-visible:outline-tcg12-accent focus-visible:outline-offset-3">How it works</a>
</div>
<dl class="mt-4 pt-6 flex gap-[clamp(20px,3vw,40px)] max-[560px]:gap-3.5 border-t border-tcg12-line">
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">4.9 ★</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Avg rating</dt>
</div>
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">12K+</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Verified stays</dt>
</div>
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">47</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Cities</dt>
</div>
</dl>
</div>
<div class="grid [grid-template-columns:repeat(3,1fr)] [grid-template-rows:repeat(3,1fr)] gap-[clamp(8px,1vw,14px)] aspect-[1.05/1] min-h-[clamp(320px,42vw,560px)] max-[900px]:aspect-[1.4/1] max-[900px]:min-h-70 max-[560px]:aspect-square" aria-label="Featured stays">
<div class="relative isolate col-start-1 col-span-2 row-start-1 row-span-2 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#1e3a5f,#0f1c2e] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Iceland</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Black Sand Cabin</span>
</div>
<div class="relative isolate col-start-3 row-start-1 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#d97a99,#7a3855] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Tokyo</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Shibuya Loft</span>
</div>
<div class="relative isolate col-start-3 row-start-2 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#f4a572,#d97f4f] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Lisbon</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Alfama Studio</span>
</div>
<div class="relative isolate col-start-1 row-start-3 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#e6c54a,#a8732d] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Marrakech</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Medina Riad</span>
</div>
<div class="relative isolate col-start-2 col-span-2 row-start-3 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#7aa852,#3d6a2a] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Brooklyn</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Williamsburg Flat</span>
</div>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcg12-bg: #fafaf7;
--color-tcg12-bg: oklch(0.98 0.005 100);
--color-tcg12-ink: #0f172a;
--color-tcg12-ink: oklch(0.21 0.04 260);
--color-tcg12-muted: #64748b;
--color-tcg12-muted: oklch(0.55 0.03 257);
--color-tcg12-line: #e2e8f0;
--color-tcg12-line: oklch(0.92 0.01 255);
--color-tcg12-accent: #dc2626;
--color-tcg12-accent: oklch(0.58 0.22 27);
--color-tcg12-accentsoft: #fbe3e3;
--color-tcg12-accentsoft: oklch(0.93 0.04 27);
--font-tcg12-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcg12-serif: 'Playfair Display', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tcg-12 {
width: 100%;
min-height: 100svh;
}
.tcg-12 ::selection {
background: #0f172a;
color: #fff;
}@import "tailwindcss";
@theme {
--color-tcg12-bg: #fafaf7;
--color-tcg12-bg: oklch(0.98 0.005 100);
--color-tcg12-ink: #0f172a;
--color-tcg12-ink: oklch(0.21 0.04 260);
--color-tcg12-muted: #64748b;
--color-tcg12-muted: oklch(0.55 0.03 257);
--color-tcg12-line: #e2e8f0;
--color-tcg12-line: oklch(0.92 0.01 255);
--color-tcg12-accent: #dc2626;
--color-tcg12-accent: oklch(0.58 0.22 27);
--color-tcg12-accentsoft: #fbe3e3;
--color-tcg12-accentsoft: oklch(0.93 0.04 27);
--font-tcg12-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcg12-serif: 'Playfair Display', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tcg-12 {
width: 100%;
min-height: 100svh;
}
.tcg-12 ::selection {
background: #0f172a;
color: #fff;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tcg12-bg: #fafaf7;
--color-tcg12-bg: oklch(0.98 0.005 100);
--color-tcg12-ink: #0f172a;
--color-tcg12-ink: oklch(0.21 0.04 260);
--color-tcg12-muted: #64748b;
--color-tcg12-muted: oklch(0.55 0.03 257);
--color-tcg12-line: #e2e8f0;
--color-tcg12-line: oklch(0.92 0.01 255);
--color-tcg12-accent: #dc2626;
--color-tcg12-accent: oklch(0.58 0.22 27);
--color-tcg12-accentsoft: #fbe3e3;
--color-tcg12-accentsoft: oklch(0.93 0.04 27);
--font-tcg12-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcg12-serif: 'Playfair Display', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tcg-12 {
width: 100%;
min-height: 100svh;
}
.tcg-12 ::selection {
background: #0f172a;
color: #fff;
}@import "tailwindcss";
@theme {
--color-tcg12-bg: #fafaf7;
--color-tcg12-bg: oklch(0.98 0.005 100);
--color-tcg12-ink: #0f172a;
--color-tcg12-ink: oklch(0.21 0.04 260);
--color-tcg12-muted: #64748b;
--color-tcg12-muted: oklch(0.55 0.03 257);
--color-tcg12-line: #e2e8f0;
--color-tcg12-line: oklch(0.92 0.01 255);
--color-tcg12-accent: #dc2626;
--color-tcg12-accent: oklch(0.58 0.22 27);
--color-tcg12-accentsoft: #fbe3e3;
--color-tcg12-accentsoft: oklch(0.93 0.04 27);
--font-tcg12-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcg12-serif: 'Playfair Display', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tcg-12 {
width: 100%;
min-height: 100svh;
}
.tcg-12 ::selection {
background: #0f172a;
color: #fff;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Playfair+Display:ital,wght@1,600;1,700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Playfair+Display:ital,wght@1,600;1,700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Five
.photo--Nplacement rules becomecol-start-*/row-start-*/*-span-*pairs on the tiles. This is the clearest win in the whole collection: a bento's rhythm belongs in the markup, because that is where you edit it. - The readability wash is an
after:chain, so a tile is one element again instead of an element plus a stylesheet block. Tailwind's automaticcontent:""is what makes that possible. - The metrics row is marked up as a
<dl>rather than three divs — the port was the moment to fix the semantics, and the flex utilities are identical. - The CTA is a real
<a>withmin-h-11and afocus-visible:ring; the CSS version styled hover only. - Both media queries survive as
max-[900px]:andmax-[560px]:variants, stated on the elements they reshape rather than in a block that has to re-select all of them. - The fonts load as
<link>tags insetup, not as an@importin the stylesheet: Tailwind emits user@importrules after its own@layerblocks, where the browser drops them.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Hero Photo Collage Grid
Source: https://codefronts.com/layouts/tailwind-card-grid-layout/tailwind-hero-photo-collage-grid/
A landing hero with narrative copy on the left and a five-tile asymmetric photo collage on the right. Every tile states its own placement — col-start-1 col-span-2 row-start-1 row-span-2 — so the collage rhythm is readable in the markup, and the readability overlay every photo grid ships is one after: chain instead of a pseudo-element block.
## HTML
```html
<section class="tcg-12 w-full min-h-svh flex items-center justify-center bg-tcg12-bg font-tcg12-sans text-tcg12-ink px-[clamp(20px,3vw,48px)] py-[clamp(28px,4vw,56px)]" aria-label="Hero photo collage grid demo">
<div class="w-full max-w-[1200px] grid [grid-template-columns:1fr_1.2fr] max-[900px]:[grid-template-columns:1fr] items-center gap-[clamp(24px,3.4vw,56px)] max-[900px]:gap-8">
<div class="flex flex-col gap-4.5 max-w-[520px] max-[900px]:max-w-none max-[900px]:items-center max-[900px]:text-center">
<span class="w-fit px-3 py-1.5 rounded-full text-[.72rem] font-bold tracking-[0.16em] uppercase text-tcg12-accent bg-tcg12-accentsoft">New · 2026 collection</span>
<h2 class="text-[clamp(2.2rem,4.5vw,3.6rem)] font-black tracking-[-0.03em] leading-[1.02]">Stay places that<br><em class="font-tcg12-serif italic font-bold text-tcg12-accent">feel like home.</em></h2>
<p class="max-w-[46ch] text-[clamp(.96rem,1.2vw,1.1rem)] leading-[1.55] text-tcg12-muted text-pretty">Hand-picked stays in 47 cities. Verified by humans. Booked by people who care where they sleep.</p>
<div class="mt-1.5 flex flex-wrap items-center gap-4">
<a href="#stays" class="inline-flex items-center min-h-11 px-5.5 py-3.5 rounded-[10px] bg-tcg12-ink text-white text-[.96rem] font-bold no-underline transition-[background,translate] duration-200 hover:bg-tcg12-accent hover:-translate-y-0.5 focus-visible:outline-2 focus-visible:outline-tcg12-accent focus-visible:outline-offset-3 motion-reduce:transition-none motion-reduce:hover:translate-y-0">Browse stays →</a>
<a href="#how" class="inline-flex items-center min-h-11 text-[.94rem] font-semibold text-tcg12-ink underline decoration-1 underline-offset-4 hover:text-tcg12-accent focus-visible:outline-2 focus-visible:outline-tcg12-accent focus-visible:outline-offset-3">How it works</a>
</div>
<dl class="mt-4 pt-6 flex gap-[clamp(20px,3vw,40px)] max-[560px]:gap-3.5 border-t border-tcg12-line">
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">4.9 ★</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Avg rating</dt>
</div>
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">12K+</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Verified stays</dt>
</div>
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">47</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Cities</dt>
</div>
</dl>
</div>
<div class="grid [grid-template-columns:repeat(3,1fr)] [grid-template-rows:repeat(3,1fr)] gap-[clamp(8px,1vw,14px)] aspect-[1.05/1] min-h-[clamp(320px,42vw,560px)] max-[900px]:aspect-[1.4/1] max-[900px]:min-h-70 max-[560px]:aspect-square" aria-label="Featured stays">
<div class="relative isolate col-start-1 col-span-2 row-start-1 row-span-2 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#1e3a5f,#0f1c2e] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Iceland</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Black Sand Cabin</span>
</div>
<div class="relative isolate col-start-3 row-start-1 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#d97a99,#7a3855] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Tokyo</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Shibuya Loft</span>
</div>
<div class="relative isolate col-start-3 row-start-2 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#f4a572,#d97f4f] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Lisbon</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Alfama Studio</span>
</div>
<div class="relative isolate col-start-1 row-start-3 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#e6c54a,#a8732d] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Marrakech</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Medina Riad</span>
</div>
<div class="relative isolate col-start-2 col-span-2 row-start-3 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#7aa852,#3d6a2a] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Brooklyn</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Williamsburg Flat</span>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcg12-bg: #fafaf7;
--color-tcg12-bg: oklch(0.98 0.005 100);
--color-tcg12-ink: #0f172a;
--color-tcg12-ink: oklch(0.21 0.04 260);
--color-tcg12-muted: #64748b;
--color-tcg12-muted: oklch(0.55 0.03 257);
--color-tcg12-line: #e2e8f0;
--color-tcg12-line: oklch(0.92 0.01 255);
--color-tcg12-accent: #dc2626;
--color-tcg12-accent: oklch(0.58 0.22 27);
--color-tcg12-accentsoft: #fbe3e3;
--color-tcg12-accentsoft: oklch(0.93 0.04 27);
--font-tcg12-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcg12-serif: 'Playfair Display', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tcg-12 {
width: 100%;
min-height: 100svh;
}
.tcg-12 ::selection {
background: #0f172a;
color: #fff;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Hero Photo Collage Grid
Source: https://codefronts.com/layouts/tailwind-card-grid-layout/tailwind-hero-photo-collage-grid/
A landing hero with narrative copy on the left and a five-tile asymmetric photo collage on the right. Every tile states its own placement — col-start-1 col-span-2 row-start-1 row-span-2 — so the collage rhythm is readable in the markup, and the readability overlay every photo grid ships is one after: chain instead of a pseudo-element block.
## HTML
```html
<section class="tcg-12 w-full min-h-svh flex items-center justify-center bg-tcg12-bg font-tcg12-sans text-tcg12-ink px-[clamp(20px,3vw,48px)] py-[clamp(28px,4vw,56px)]" aria-label="Hero photo collage grid demo">
<div class="w-full max-w-[1200px] grid [grid-template-columns:1fr_1.2fr] max-[900px]:[grid-template-columns:1fr] items-center gap-[clamp(24px,3.4vw,56px)] max-[900px]:gap-8">
<div class="flex flex-col gap-4.5 max-w-[520px] max-[900px]:max-w-none max-[900px]:items-center max-[900px]:text-center">
<span class="w-fit px-3 py-1.5 rounded-full text-[.72rem] font-bold tracking-[0.16em] uppercase text-tcg12-accent bg-tcg12-accentsoft">New · 2026 collection</span>
<h2 class="text-[clamp(2.2rem,4.5vw,3.6rem)] font-black tracking-[-0.03em] leading-[1.02]">Stay places that<br><em class="font-tcg12-serif italic font-bold text-tcg12-accent">feel like home.</em></h2>
<p class="max-w-[46ch] text-[clamp(.96rem,1.2vw,1.1rem)] leading-[1.55] text-tcg12-muted text-pretty">Hand-picked stays in 47 cities. Verified by humans. Booked by people who care where they sleep.</p>
<div class="mt-1.5 flex flex-wrap items-center gap-4">
<a href="#stays" class="inline-flex items-center min-h-11 px-5.5 py-3.5 rounded-[10px] bg-tcg12-ink text-white text-[.96rem] font-bold no-underline transition-[background,translate] duration-200 hover:bg-tcg12-accent hover:-translate-y-0.5 focus-visible:outline-2 focus-visible:outline-tcg12-accent focus-visible:outline-offset-3 motion-reduce:transition-none motion-reduce:hover:translate-y-0">Browse stays →</a>
<a href="#how" class="inline-flex items-center min-h-11 text-[.94rem] font-semibold text-tcg12-ink underline decoration-1 underline-offset-4 hover:text-tcg12-accent focus-visible:outline-2 focus-visible:outline-tcg12-accent focus-visible:outline-offset-3">How it works</a>
</div>
<dl class="mt-4 pt-6 flex gap-[clamp(20px,3vw,40px)] max-[560px]:gap-3.5 border-t border-tcg12-line">
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">4.9 ★</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Avg rating</dt>
</div>
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">12K+</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Verified stays</dt>
</div>
<div class="flex flex-col gap-0.5">
<dd class="text-2xl max-[560px]:text-xl font-black tracking-[-0.02em] tabular-nums">47</dd>
<dt class="text-[.78rem] font-semibold text-tcg12-muted">Cities</dt>
</div>
</dl>
</div>
<div class="grid [grid-template-columns:repeat(3,1fr)] [grid-template-rows:repeat(3,1fr)] gap-[clamp(8px,1vw,14px)] aspect-[1.05/1] min-h-[clamp(320px,42vw,560px)] max-[900px]:aspect-[1.4/1] max-[900px]:min-h-70 max-[560px]:aspect-square" aria-label="Featured stays">
<div class="relative isolate col-start-1 col-span-2 row-start-1 row-span-2 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#1e3a5f,#0f1c2e] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Iceland</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Black Sand Cabin</span>
</div>
<div class="relative isolate col-start-3 row-start-1 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#d97a99,#7a3855] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Tokyo</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Shibuya Loft</span>
</div>
<div class="relative isolate col-start-3 row-start-2 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#f4a572,#d97f4f] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Lisbon</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Alfama Studio</span>
</div>
<div class="relative isolate col-start-1 row-start-3 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#e6c54a,#a8732d] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Marrakech</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Medina Riad</span>
</div>
<div class="relative isolate col-start-2 col-span-2 row-start-3 flex items-end p-3 rounded-[14px] overflow-hidden bg-linear-[135deg,#7aa852,#3d6a2a] after:absolute after:inset-0 after:z-0 after:bg-linear-[180deg,transparent_45%,oklch(0_0_0/0.55)]">
<span class="absolute top-2.5 right-2.5 z-2 px-2.25 py-1 rounded-full bg-white/92 backdrop-blur-[8px] text-tcg12-ink text-[.62rem] font-bold tracking-[0.1em] uppercase">Brooklyn</span>
<span class="relative z-1 text-white text-[.86rem] font-bold [text-shadow:0_1px_3px_oklch(0_0_0/0.4)]">Williamsburg Flat</span>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcg12-bg: #fafaf7;
--color-tcg12-bg: oklch(0.98 0.005 100);
--color-tcg12-ink: #0f172a;
--color-tcg12-ink: oklch(0.21 0.04 260);
--color-tcg12-muted: #64748b;
--color-tcg12-muted: oklch(0.55 0.03 257);
--color-tcg12-line: #e2e8f0;
--color-tcg12-line: oklch(0.92 0.01 255);
--color-tcg12-accent: #dc2626;
--color-tcg12-accent: oklch(0.58 0.22 27);
--color-tcg12-accentsoft: #fbe3e3;
--color-tcg12-accentsoft: oklch(0.93 0.04 27);
--font-tcg12-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tcg12-serif: 'Playfair Display', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tcg-12 {
width: 100%;
min-height: 100svh;
}
.tcg-12 ::selection {
background: #0f172a;
color: #fff;
}
```How this works
The collage is nine cells and five tiles. The container is grid [grid-template-columns:repeat(3,1fr)] [grid-template-rows:repeat(3,1fr)] and each tile places itself:
<div class="col-start-1 col-span-2 row-start-1 row-span-2 …">The CSS version wrote those five placements as five modifier classes in a stylesheet. As utilities they sit on the tiles, so rearranging the collage is a matter of editing the tile you are looking at — and the bento reads correctly in source order.
The overlay is a variant, not an element. White labels on unknown photo brightness need a wash; after: supplies content:"" automatically, so the whole treatment is four utilities. The label sits at z-1 and the pill at z-2, both above the wash at z-0.
Two breakpoints, both reshaping. At max-[900px]: the outer grid becomes one column and the copy centres; the collage keeps all five tiles but flattens to aspect-[1.4/1], and at max-[560px]: back to square. The point is that the collage never restacks into a photo list — on a phone it is still a collage.
Swapping in real photography is one utility per tile. bg-linear-[135deg,#1e3a5f,#0f1c2e] becomes bg-[url(hero-1.jpg)] bg-cover bg-center; the labels, pills and wash are unaffected because none of them depends on the background type.
Make it yours
- Rebalance copy against collage by editing the outer
[grid-template-columns:1fr_1.2fr]—1fr_1.6fris collage-led,1.2fr_1fris copy-led. - Swap any gradient for a photo: replace the tile's
bg-linear-[…]withbg-[url(…)] bg-cover bg-center. - Add a sixth tile by widening the collage to
[grid-template-columns:repeat(4,1fr)]and re-stating the spans on the tiles you want to grow. - Retheme every CTA and pill through
--color-tcg12-accentin@theme. - Lighten the wash by moving the stop:
after:bg-linear-[180deg,transparent_65%,oklch(0_0_0/0.45)].
Gotchas — read before shipping
- Arbitrary values take no spaces: the track list must be typed
[grid-template-columns:1fr_1.2fr], and every gradient stop pair uses the underscore escape. col-start-*androw-start-*are 1-indexed grid lines, not cell indexes — a tile starting at column 3 iscol-start-3, andcol-span-2from there overflows a 3-column grid.- Tiles spanning 2×2 of a 3×3 collage need source images of at least 800×600, or they read blocky next to the small tiles.
- The pill needs
z-2: withbackdrop-blurbut no stacking order it renders under theafter:wash and looks muddy. - Keep
isolation-isolate(orisolate) on the tile so the wash's z-index cannot escape into the page's stacking context.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Grid placement and gradients are baseline; backdrop-blur on the pills needs Safari 9+ with the -webkit- prefix, which Tailwind emits automatically. The floor quoted is Tailwind v4's own.