Image Tile Grid Hero
A hero with the headline on the left and a grid of image tiles on the right that enters outward from the centre, enlarging the hovered tile while the rest dim slightly.
An editorial two-column hero: eyebrow, headline, supporting text and CTAs on the left, and a grid of square tiles (2×2, 3×3 or 4×3) on the right, where one or two tiles can span 2×2 for an irregular layout. On entrance the tiles ripple outward from the centre of the grid by Manhattan distance — scaling up, fading in and un-blurring together. Hover or focus a tile and it springs larger with a shadow while its caption fades in and the other tiles dim and desaturate. Without images, every tile draws a built-in gradient + geometric placeholder, so no external images are needed.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add image-tile-grid-hero.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ImageTileGridHero/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/image-tile-grid-hero.en.jsonOr, once registries are configured in components.json, install it as @webberui/image-tile-grid-hero.
Usage
import { ImageTileGridHero } from "@/components/ui/image-tile-grid-hero";
<ImageTileGridHero
eyebrow="Isle & Clay · Autumn picks"
title="Everyday objects, arranged into a quiet wall"
subtitle="Twenty handmade ceramics and textiles, shipped straight from the studio."
primaryAction={{ label: "Shop the season", href: "/shop" }}
secondaryAction={{ label: "Book a visit", href: "/visit" }}
grid="3x3"
tiles={[
{ alt: "Matte clay vase", caption: "Matte clay vase", href: "/p/vase" },
{ src: "/images/arch.jpg", alt: "Arch-shaped wall hanging", caption: "Featured", span: 2 },
{ alt: "Sandstone globe table lamp", caption: "Sandstone lamp" },
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | — | Headline (required) |
eyebrow | ReactNode | — | Small label above the headline, in the accent colour |
subtitle | ReactNode | — | Supporting text below the headline |
primaryAction | ImageTileGridHeroAction | — | Primary action with an accent background |
secondaryAction | ImageTileGridHeroAction | — | Secondary action (outlined) |
as | "h1" | "h2" | "h1" | Heading level |
minHeight | number | 560 | Minimum section height (px) |
tiles | ImageGridTile[] | 9 built-in placeholders | Tiles; any beyond the grid's capacity are not shown |
grid | "2x2" | "3x3" | "4x3" | "3x3" | Grid layout (columns × rows) |
hoverZoom | number | 1.04 | Scale of the hovered/focused tile |
stagger | number | — | Entrance delay per step of Manhattan distance (ms); falls back to --wb-image-tile-grid-hero-stagger (60ms) |
duration | number | — | Entrance duration (ms); falls back to --wb-image-tile-grid-hero-duration (500ms for tiles, 600ms for text) |
activeIndex | number | null | — | Controlled active tile index; managed internally when omitted |
defaultActiveIndex | number | null | null | Initial active tile index in uncontrolled mode |
onActiveIndexChange | (index: number | null) => void | — | Called when the active tile changes (hover, focus or leave) |
galleryLabel | string | "作品圖磚" (Work tiles) | Accessible name of the tile grid |
className | string | — | Classes merged into the root section |
ImageGridTile
| Field | Type | Default | Description |
|---|---|---|---|
alt | string | — | Alt text (required); also the placeholder's accessible name |
src | string | — | Image URL; omit to draw a gradient + geometric placeholder |
caption | string | — | Small caption at the bottom of the tile, fades in on hover/focus |
span | 1 | 2 | 1 | Set 2 to span 2×2; falls back to one cell on grids with fewer than 3 columns |
href | string | — | When set, the tile renders as a focusable link |
ImageTileGridHeroAction
| Field | Type | Default | Description |
|---|---|---|---|
label | string | — | Button label |
href | string | — | Renders a link when set, otherwise a type="button" button |
onClick | () => void | — | Click callback |
How it works
- Layout: tiles go into the first free slot that fits (row-major). A
span: 2tile that does not fit falls back to a single cell, and is skipped if even that does not fit. The component computes coordinates itself because the entrance delay needs each tile's actual position. - Entrance timing: each tile's delay = its Manhattan distance from the grid centre ×
--wb-image-tile-grid-hero-stagger, returning fromscale(0.9), transparent andblur(6px)to rest. The text column rises in sequence, 80ms apart, oncubic-bezier(0.22,1,0.36,1). - Accent:
--wb-image-tile-grid-hero-accent(light default#0d9488, dark default#2dd4bf) drives the eyebrow, the primary button background and focus rings. - Entrances are CSS keyframes, so they apply on first paint without waiting for hydration; the hover zoom is a motion spring (stiffness 300, damping 22).
Accessibility
- The root is a
sectionwitharia-labelledbypointing at the headline; switch the heading level toh2withas - The tile grid is
role="group"labelled bygalleryLabel; tiles withhrefarealinks you can Tab to, enlarging with a focus-visible ring on focus, and the rest arefigureelements with afigcaption altis required: user images use it as theimgalt, placeholders expose it viarole="img"andaria-label- Captions only fade visually and always stay in the DOM, so screen readers read them without hovering
- When the system "reduce motion" setting is on, every entrance becomes a 150ms fade (no movement, scale or blur) and hover no longer enlarges the tile; only the dimming of the other tiles remains
Film Strip Hero
A hero with a sprocketed film strip looping endlessly beneath the headline; the faster the page scrolls, the faster the film runs, and hovered frames enlarge.
Ink Ridgeline Hero
Layered ink ridgelines are generated and drawn in one by one while the headline strokes in before filling, with the ridges separating in parallax as the page scrolls.