Marble UI
← Components

Masonry

Preview: Default

Accessible masonry grid: React Aria GridList in a virtualized WaterfallLayout (arrow keys follow the visual layout, only visible items render). Give it a fixed-height scroll container via className. Put padding and borders on a wrapper, not here: the layout spans the scroller's full client width, so padding pushes the last column out. Items are announced by `textValue(item)`; by default an item's `title`, `label`, `name` or `alt` string, else its id.

Import

import { Masonry } from '@marbleui/ui'

Props

<Masonry>

PropTypeDefaultDescription
children *required(item: T) => ReactNode—
items *requiredT[]—
label *requiredstring—
classNamestring—
gapnumber12
minItemWidthnumber180
onAction(key: Key) => void—
textValue(item: T) => stringdefaultText

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria GridList · React Aria GridListItem · React Aria Virtualizer · React Aria WaterfallLayout. Follow the primitive's docs for the full keyboard map.

Usage notes

Virtualized masonry grid for items of different heights, such as shots or pins. For equal-height tiles use Grid; for a photo gallery that opens full screen use Lightbox.

<Masonry label="Shots" items={shots} className="h-[36rem]" minItemWidth={220} onAction={(id) => openShot(id)}>
  {(shot) => <img src={shot.url} alt={shot.title} style={{ aspectRatio: shot.ratio }} />}
</Masonry>
  • Give it a fixed height through className: it is its own scroll container and renders only the visible items. Each item needs a unique id, and its height comes from its content, so give images a known aspect ratio.
  • minItemWidth (180) decides how many columns fit; gap (12) is the spacing in px.
  • It’s a React Aria GridList: arrow keys follow the visual layout, and onAction runs on click or Enter. label is required.
  • Screen readers announce each item by textValue(item). By default that’s the item’s title, label, name or alt string, falling back to its id.
Example source · show
import { Masonry } from './masonry'

const items = Array.from({ length: 40 }, (_, i) => ({ id: i + 1, h: [140, 220, 180, 260, 160, 300][i % 6]!, c: (i % 8) + 1 }))
export const styles = {
  Default: () => (
    <div className="w-[36rem] max-w-full rounded-xl border border-border bg-bg p-3">
      <Masonry label="Gallery" items={items} className="h-96">
        {(it) => <div className="flex flex-col justify-end p-3 text-sm font-medium text-white" style={{ height: it.h, background: `linear-gradient(160deg, var(--chart-${it.c}), color-mix(in oklab, var(--chart-${it.c}) 50%, black))` }}>Shot {it.id}</div>}
      </Masonry>
    </div>
  ),
}

Source: packages/ui/src/masonry/masonry.tsx

Catalog

Category
collections
Priority
Extra
Primitive
react-aria
React Aria
Virtualizer (WaterfallLayout)