Page Sections

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.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1.04
60
500
Active edited usageDefault props
<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.json

Or, 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

PropTypeDefaultDescription
titleReactNode—Headline (required)
eyebrowReactNode—Small label above the headline, in the accent colour
subtitleReactNode—Supporting text below the headline
primaryActionImageTileGridHeroAction—Primary action with an accent background
secondaryActionImageTileGridHeroAction—Secondary action (outlined)
as"h1" | "h2""h1"Heading level
minHeightnumber560Minimum section height (px)
tilesImageGridTile[]9 built-in placeholdersTiles; any beyond the grid's capacity are not shown
grid"2x2" | "3x3" | "4x3""3x3"Grid layout (columns × rows)
hoverZoomnumber1.04Scale of the hovered/focused tile
staggernumber—Entrance delay per step of Manhattan distance (ms); falls back to --wb-image-tile-grid-hero-stagger (60ms)
durationnumber—Entrance duration (ms); falls back to --wb-image-tile-grid-hero-duration (500ms for tiles, 600ms for text)
activeIndexnumber | null—Controlled active tile index; managed internally when omitted
defaultActiveIndexnumber | nullnullInitial active tile index in uncontrolled mode
onActiveIndexChange(index: number | null) => void—Called when the active tile changes (hover, focus or leave)
galleryLabelstring"作品圖磚" (Work tiles)Accessible name of the tile grid
classNamestring—Classes merged into the root section

ImageGridTile

FieldTypeDefaultDescription
altstring—Alt text (required); also the placeholder's accessible name
srcstring—Image URL; omit to draw a gradient + geometric placeholder
captionstring—Small caption at the bottom of the tile, fades in on hover/focus
span1 | 21Set 2 to span 2×2; falls back to one cell on grids with fewer than 3 columns
hrefstring—When set, the tile renders as a focusable link

ImageTileGridHeroAction

FieldTypeDefaultDescription
labelstring—Button label
hrefstring—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: 2 tile 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 from scale(0.9), transparent and blur(6px) to rest. The text column rises in sequence, 80ms apart, on cubic-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 section with aria-labelledby pointing at the headline; switch the heading level to h2 with as
  • The tile grid is role="group" labelled by galleryLabel; tiles with href are a links you can Tab to, enlarging with a focus-visible ring on focus, and the rest are figure elements with a figcaption
  • alt is required: user images use it as the img alt, placeholders expose it via role="img" and aria-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