Component

Bento Grid Hero

Full-viewport bento grid hero block — large featured image left (2×2), stacked side images right, with headline, eyebrow, and frosted-glass CTA overlaid on the main cell.

Preview

Columbia River Gorge

Documentary Photography

The World
Through Glass

View the Work
Studio Window

Studio Window

Starting Block

Starting Block

Grid anatomy

The grid is a 3-column × 2-row CSS grid. The main image always spans col 1–2, row 1–2. Right-column cells each occupy col 3, one row. Height is fluid: clamp(480px, 68vh, 720px).

mainImage — 2×2
gridImages[0]
gridImages[1]
tsx
// CSS grid setup
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))'
gridTemplateRows:    'repeat(2, minmax(0, 1fr))'
height:              'clamp(480px, 68vh, 720px)'

// Main cell
gridColumn: '1 / 3'
gridRow:    '1 / 3'

// Each right-column cell
gridColumn: '3 / 4'   // auto-placed into rows

Image sizing

CellCDN widthnext/image sizes hint
mainImage1800px(max-width: 768px) 100vw, 66vw
gridImages[n]800px(max-width: 768px) 100vw, 34vw

Full-bleed variant

Toggle Full bleed in Studio to remove the outer padding — the grid extends edge-to-edge. Cells keep their rounded-2xl corners.

Standard

px-3 sm:px-4 py-3 sm:py-4

Full bleed

no padding — edge-to-edge

Bottom strip variant

Add 1–4 images to Bottom strip images in Studio. They render as an equal-width flex row below the main grid, separated by the standard gap. Height scales down as more images are added.

ImagesStrip height
1 image360px
2 images300px
3 images260px
4 images220px
main 2×2
grid[0]
grid[1]
strip[0]
strip[1]
strip[2]

Sanity fields

Added to the Home Page singleton as a block type. Drag to position among other blocks.

PropTypeDefaultDescription
fullBleedbooleanfalseRemove outer padding — grid extends edge-to-edge.
eyebrowstringSmall uppercase label above the headline.
headlinestringMain headline rendered in Cal Sans over the main image. Required.
cta.labelstringCTA button label — rendered as a frosted-glass pill.
cta.hrefstringCTA button destination URL.
mainImageimage slotLarge left cell (2×2). Requires image; headline optional.
gridImagesimage slot[]1–4 right-column cells. Each has image + headline.
bottomStripimage slot[]1–4 images in a full-width strip row below the main grid.

No gradient scrims

All gradient scrims have been intentionally removed from every cell — MainCell, GridCell, and BentoCell. Text overlays (eyebrow, headline, CTA, slot headlines) render directly on the image or video with no scrim underneath. Choose photography that is naturally dark or low-contrast in the overlay area so text remains legible. Do not re-add bg-gradient-to-t divs — use better source images instead.

For video slots, the CTA uses <Button variant="frosted"> from the design system. The frosted glass treatment provides enough contrast over photography without needing a gradient behind it.

tsx
// ✅ Correct — design system Button, no scrim
<Button variant="frosted" size="md" asChild>
  <Link href={cta.href}>{cta.label}</Link>
</Button>

// ❌ Wrong — do not add gradient overlays back
<div className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent" />

Usage

tsx
// apps/web/components/home-blocks/HomeBentoBlock.tsx
import { HomeBentoBlockComponent } from '@/components/home-blocks/HomeBentoBlock'

// Inside HomeBlockRenderer:
case 'homeBentoBlock':
  return (
    <HomeBentoBlockComponent
      key={block._key}
      block={block}      // HomeBentoBlock from @/lib/sanity/types
      priority={i === 0} // true for above-the-fold LCP
    />
  )