02 / 22
All 22 in 22 Tailwind CSS Image Gallery
- 01Tailwind Hexagon Honeycomb Image Gallery with clip-path
- 02Tailwind Justified Image Gallery with Aspect-Ratio Flex Rows
- 03Tailwind Expanding Panel Image Gallery with Flex-Grow Transitions
- 04Tailwind :has() Hover Spotlight Image Grid
- 05Tailwind Radial Orbit Image Gallery with Counter-Rotating Thumbnails
- 06Tailwind Interactive Pointer Trail Image Gallery
- 07Tailwind Masonry Image Gallery with CSS Columns (No JavaScript)
- 08Tailwind Metro Tile Grid Gallery with Dense Auto-Flow
- 09Tailwind Editorial Magazine Grid Gallery with Blend-Mode Headline
- 10Tailwind Pure CSS Lightbox Gallery with :target (No JavaScript)
- 11Tailwind Filmstrip Carousel Gallery with CSS Scroll-Snap
- 12Tailwind Accordion Image Gallery with Flex-Grow on Hover & Focus
- 13Tailwind Mosaic Grid Gallery with Contained Zoom on Hover
- 14Tailwind Glassmorphism Image Cards with backdrop-blur
- 15Tailwind Diagonal Slice Reveal Gallery with clip-path Halves
- 16Tailwind 3D Tilt Parallax Image Cards with Pointer-Driven Variables
- 17Tailwind 3D Cube Image Viewer with Radio-Driven Rotation
- 18Tailwind 3D Perspective Depth Stack Gallery That Fans on Focus
- 19Tailwind Infinite Scrolling Image Marquee with Seamless Loop
- 20Tailwind Ken Burns Slideshow Gallery with Staggered Cross-Fade
- 21Tailwind Polaroid Photo Stack Gallery with Rotate-on-Hover
- 22Tailwind Scattered Photo Collage with :target Spotlight
Tailwind Justified Image Gallery with Aspect-Ratio Flex Rows
The Google-Images / Flickr look: fluid rows where every photo keeps its native aspect ratio — nothing is cropped — yet each row stretches flush to both edges. No JS measuring pass, and in Tailwind the whole trick reduces to two arbitrary values that both read the same per-item variable.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tgl-02 w-full min-h-svh grid place-items-center bg-tgl02-bg font-tgl02-sans px-5 py-10" aria-label="Justified aspect-ratio gallery">
<div class="w-[min(920px,100%)] flex flex-col gap-2">
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:1.5" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=600&h=400&auto=format&fit=crop" alt="Wide sandy beach" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:0.75" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?q=80&w=450&h=600&auto=format&fit=crop" alt="Coastal lighthouse" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.33" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1475721027785-f74eccf877e2?q=80&w=600&h=450&auto=format&fit=crop" alt="Wooden pier over water" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:1.78" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1483412033650-1015ddeb83d1?q=80&w=640&h=360&auto=format&fit=crop" alt="Green valley panorama" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1466692476868-aef1dfb1e735?q=80&w=500&h=500&auto=format&fit=crop" alt="Close-up of a wildflower" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.4" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1490645935967-10de6ba17061?q=80&w=560&h=400&auto=format&fit=crop" alt="River through a forest" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:0.8" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=480&h=600&auto=format&fit=crop" alt="Tall narrow waterfall" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.6" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1502082553048-f009c37129b9?q=80&w=640&h=400&auto=format&fit=crop" alt="Sunlit sand dunes" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.2" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1522542550221-31fd19575a2d?q=80&w=540&h=450&auto=format&fit=crop" alt="Alpine meadow in bloom" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
</div>
</section><section class="tgl-02 w-full min-h-svh grid place-items-center bg-tgl02-bg font-tgl02-sans px-5 py-10" aria-label="Justified aspect-ratio gallery">
<div class="w-[min(920px,100%)] flex flex-col gap-2">
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:1.5" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=600&h=400&auto=format&fit=crop" alt="Wide sandy beach" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:0.75" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?q=80&w=450&h=600&auto=format&fit=crop" alt="Coastal lighthouse" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.33" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1475721027785-f74eccf877e2?q=80&w=600&h=450&auto=format&fit=crop" alt="Wooden pier over water" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:1.78" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1483412033650-1015ddeb83d1?q=80&w=640&h=360&auto=format&fit=crop" alt="Green valley panorama" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1466692476868-aef1dfb1e735?q=80&w=500&h=500&auto=format&fit=crop" alt="Close-up of a wildflower" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.4" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1490645935967-10de6ba17061?q=80&w=560&h=400&auto=format&fit=crop" alt="River through a forest" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:0.8" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=480&h=600&auto=format&fit=crop" alt="Tall narrow waterfall" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.6" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1502082553048-f009c37129b9?q=80&w=640&h=400&auto=format&fit=crop" alt="Sunlit sand dunes" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.2" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1522542550221-31fd19575a2d?q=80&w=540&h=450&auto=format&fit=crop" alt="Alpine meadow in bloom" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tgl02-bg: #fafafa;
--color-tgl02-bg: oklch(0.98 0 0);
--color-tgl02-well: #e6e8ea;
--color-tgl02-well: oklch(0.92 0.004 220);
--color-tgl02-acc: #2f9e6e;
--color-tgl02-acc: oklch(0.62 0.13 160);
--font-tgl02-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-02 {
width: 100%;
min-height: 100svh;
}
.tgl-02 ::selection {
background: var(--color-tgl02-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tgl02-bg: #fafafa;
--color-tgl02-bg: oklch(0.98 0 0);
--color-tgl02-well: #e6e8ea;
--color-tgl02-well: oklch(0.92 0.004 220);
--color-tgl02-acc: #2f9e6e;
--color-tgl02-acc: oklch(0.62 0.13 160);
--font-tgl02-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-02 {
width: 100%;
min-height: 100svh;
}
.tgl-02 ::selection {
background: var(--color-tgl02-acc);
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-tgl02-bg: #fafafa;
--color-tgl02-bg: oklch(0.98 0 0);
--color-tgl02-well: #e6e8ea;
--color-tgl02-well: oklch(0.92 0.004 220);
--color-tgl02-acc: #2f9e6e;
--color-tgl02-acc: oklch(0.62 0.13 160);
--font-tgl02-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-02 {
width: 100%;
min-height: 100svh;
}
.tgl-02 ::selection {
background: var(--color-tgl02-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tgl02-bg: #fafafa;
--color-tgl02-bg: oklch(0.98 0 0);
--color-tgl02-well: #e6e8ea;
--color-tgl02-well: oklch(0.92 0.004 220);
--color-tgl02-acc: #2f9e6e;
--color-tgl02-acc: oklch(0.62 0.13 160);
--font-tgl02-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-02 {
width: 100%;
min-height: 100svh;
}
.tgl-02 ::selection {
background: var(--color-tgl02-acc);
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&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&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
flex: var(--r) 1 0ports as a single arbitrary value. This is the demo where Tailwind changes the least and reads the best: the two declarations that ARE the technique now sit next to each other on one element.- Per-item ratios stay inline
styleattributes, not@themeentries. They are data about a specific photograph, and every real implementation writes them from image metadata. - UI change from the CSS original: the hover zoom gained a
motion-reduce:transition-nonetwin it was missing, and each figure is its owngroupso the zoom no longer depends on a:hover imgdescendant selector.
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 Justified Image Gallery with Aspect-Ratio Flex Rows
Source: https://codefronts.com/snippets/tailwind-css-image-gallery/tailwind-justified-image-gallery-aspect-ratio-flex/
The Google-Images / Flickr look: fluid rows where every photo keeps its native aspect ratio — nothing is cropped — yet each row stretches flush to both edges. No JS measuring pass, and in Tailwind the whole trick reduces to two arbitrary values that both read the same per-item variable.
## HTML
```html
<section class="tgl-02 w-full min-h-svh grid place-items-center bg-tgl02-bg font-tgl02-sans px-5 py-10" aria-label="Justified aspect-ratio gallery">
<div class="w-[min(920px,100%)] flex flex-col gap-2">
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:1.5" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=600&h=400&auto=format&fit=crop" alt="Wide sandy beach" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:0.75" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?q=80&w=450&h=600&auto=format&fit=crop" alt="Coastal lighthouse" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.33" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1475721027785-f74eccf877e2?q=80&w=600&h=450&auto=format&fit=crop" alt="Wooden pier over water" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:1.78" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1483412033650-1015ddeb83d1?q=80&w=640&h=360&auto=format&fit=crop" alt="Green valley panorama" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1466692476868-aef1dfb1e735?q=80&w=500&h=500&auto=format&fit=crop" alt="Close-up of a wildflower" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.4" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1490645935967-10de6ba17061?q=80&w=560&h=400&auto=format&fit=crop" alt="River through a forest" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:0.8" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=480&h=600&auto=format&fit=crop" alt="Tall narrow waterfall" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.6" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1502082553048-f009c37129b9?q=80&w=640&h=400&auto=format&fit=crop" alt="Sunlit sand dunes" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.2" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1522542550221-31fd19575a2d?q=80&w=540&h=450&auto=format&fit=crop" alt="Alpine meadow in bloom" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tgl02-bg: #fafafa;
--color-tgl02-bg: oklch(0.98 0 0);
--color-tgl02-well: #e6e8ea;
--color-tgl02-well: oklch(0.92 0.004 220);
--color-tgl02-acc: #2f9e6e;
--color-tgl02-acc: oklch(0.62 0.13 160);
--font-tgl02-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-02 {
width: 100%;
min-height: 100svh;
}
.tgl-02 ::selection {
background: var(--color-tgl02-acc);
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 Justified Image Gallery with Aspect-Ratio Flex Rows
Source: https://codefronts.com/snippets/tailwind-css-image-gallery/tailwind-justified-image-gallery-aspect-ratio-flex/
The Google-Images / Flickr look: fluid rows where every photo keeps its native aspect ratio — nothing is cropped — yet each row stretches flush to both edges. No JS measuring pass, and in Tailwind the whole trick reduces to two arbitrary values that both read the same per-item variable.
## HTML
```html
<section class="tgl-02 w-full min-h-svh grid place-items-center bg-tgl02-bg font-tgl02-sans px-5 py-10" aria-label="Justified aspect-ratio gallery">
<div class="w-[min(920px,100%)] flex flex-col gap-2">
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:1.5" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=600&h=400&auto=format&fit=crop" alt="Wide sandy beach" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:0.75" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?q=80&w=450&h=600&auto=format&fit=crop" alt="Coastal lighthouse" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.33" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1475721027785-f74eccf877e2?q=80&w=600&h=450&auto=format&fit=crop" alt="Wooden pier over water" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:1.78" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1483412033650-1015ddeb83d1?q=80&w=640&h=360&auto=format&fit=crop" alt="Green valley panorama" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1466692476868-aef1dfb1e735?q=80&w=500&h=500&auto=format&fit=crop" alt="Close-up of a wildflower" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.4" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1490645935967-10de6ba17061?q=80&w=560&h=400&auto=format&fit=crop" alt="River through a forest" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
<div class="flex gap-2 max-[600px]:flex-wrap">
<figure style="--r:0.8" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=480&h=600&auto=format&fit=crop" alt="Tall narrow waterfall" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.6" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1502082553048-f009c37129b9?q=80&w=640&h=400&auto=format&fit=crop" alt="Sunlit sand dunes" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
<figure style="--r:1.2" class="group flex-[var(--r)_1_0] [aspect-ratio:var(--r)] rounded-lg overflow-hidden bg-tgl02-well m-0 max-[600px]:flex-[1_1_40%]"><img src="https://images.unsplash.com/photo-1522542550221-31fd19575a2d?q=80&w=540&h=450&auto=format&fit=crop" alt="Alpine meadow in bloom" loading="lazy" decoding="async" class="block w-full h-full object-cover transition-transform duration-[400ms] group-hover:scale-105 motion-reduce:transition-none"></figure>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tgl02-bg: #fafafa;
--color-tgl02-bg: oklch(0.98 0 0);
--color-tgl02-well: #e6e8ea;
--color-tgl02-well: oklch(0.92 0.004 220);
--color-tgl02-acc: #2f9e6e;
--color-tgl02-acc: oklch(0.62 0.13 160);
--font-tgl02-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-02 {
width: 100%;
min-height: 100svh;
}
.tgl-02 ::selection {
background: var(--color-tgl02-acc);
color: #fff;
}
```How this works
One variable, two utilities, zero JavaScript. Every item declares its true ratio once:
<figure style="--r:1.5" class="flex-[var(--r)_1_0] [aspect-ratio:var(--r)]">flex-grow: var(--r) hands each photo a share of the row proportional to its width; flex-basis: 0 makes that share the whole width calculation rather than a bonus on top of an intrinsic size. Because every item in the row then resolves to the same height, the row justifies edge to edge with no cropping — the pure-CSS equivalent of a JS justified-gallery pass.
Rows are explicit, and that is the honest limitation. A JS library reflows items between rows as the viewport changes; CSS cannot. Grouping three photos per row is the trade, and it is the same trade the CSS original made.
Make it yours
- Add a photo by dropping in a figure with its true
--r(width ÷ height); nothing else changes. - Control row height indirectly: fewer items per row means a taller row.
- Generate
--rserver-side from stored image dimensions for a real feed — it is the one number this layout needs.
Gotchas — read before shipping
- The
--rvalue must match the real image ratio or the photo letterboxes or crops.object-coverhere is a safety net, not the plan. - Keep
flex-basis: 0(the_0inflex-[var(--r)_1_0]). A non-zero basis mixes intrinsic width into the weighting and the justification skews. - Arbitrary values cannot contain spaces —
flex-[var(--r)_1_0]uses underscores, and Tailwind converts them back.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
aspect-ratio on flex items needs Chrome 88+, Safari 15+, Firefox 89+; the quoted floor is Tailwind v4's generated sheet. Older engines fall back to intrinsic image sizes.