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>
| Prop | Type | Default | Description |
|---|---|---|---|
| children *required | (item: T) => ReactNode | — | |
| items *required | T[] | — | |
| label *required | string | — | |
| className | string | — | |
| gap | number | 12 | |
| minItemWidth | number | 180 | |
| onAction | (key: Key) => void | — | |
| textValue | (item: T) => string | defaultText |
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 uniqueid, 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, andonActionruns on click or Enter.labelis required. - Screen readers announce each item by
textValue(item). By default that’s the item’stitle,label,nameoraltstring, falling back to itsid.
Example source · show· hide
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)