Masonry

A column layout for items of unequal height, built on CSS columns rather than a JS layout pass — so it packs before paint and reflows on resize for free. Items order top-to-bottom within a column, which is the trade.

src/components/ui/masonry.tsx

About

From the component's own source, where it was written.

A column layout for items of unequal height.

For galleries, note walls, log cards — anything where forcing a uniform row height would crop the content or leave ragged gaps under it.

**CSS columns, not a JS layout pass.** The usual masonry implementation measures every child and assigns it to the shortest column on each render, which costs a layout read per item, has to be redone on resize, and reflows visibly after paint. column-count does the same packing in the layout engine, before paint, and reflows on resize for free.

The trade is real and worth stating: CSS columns order items **top to bottom within each column**, not left to right across them. For a gallery that is fine. For anything where reading order carries meaning — a ranked list, a feed — it is wrong, and Kanban or a plain grid is the honest choice.

break-inside: avoid on each item is what stops a card being sliced in half across a column boundary, which is the default and never what anyone wants.

Install

npx astralyx-ui add masonry

Usage

import { Masonry } from '@/components/ui/masonry'

<Masonry columns={{ base: 1, sm: 2, lg: 3 }} gap={4}>
  {items.map((item) => <Card key={item.id}>{item.body}</Card>)}
</Masonry>

Composer

Change the props and the source below follows.

Sensor drift

Three probes reported the same 0.4°C offset after the firmware bump.

Retry storm

The queue drained in 90 seconds once the backoff jitter landed.

Cold start

First request after a deploy pays 800ms. Warming halves it, and the warmer costs more than the latency does.

Cache hit rate

94% and steady.

Token spend

The cheap model handles 91% of calls and 11% of the bill, which is the whole argument for routing.

Index size

Two million chunks, 4.1 GB on disk.

Props
tsx
<Masonry columns={{ base: 1, sm: 2, lg: 3 }} gap={4}>
  {items.map((item) => (
    <Card key={item.id}>{item.body}</Card>
  ))}
</Masonry>

Examples

Uneven tiles

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

Masonry

A column layout for items of unequal height.

Extends ComponentProps<'div'>.

PropTypeDefaultDescription
childrenReactNode——
columns{ base?: number; sm?: number; md?: number; lg?: number; xl?: number }{ base: 1, sm: 2, lg: 3 }Columns per breakpoint. Written out rather than interpolated, because Tailwind scans source text and columns-${n} produces nothing at build time.
gap2 | 3 | 4 | 6 | 84Applied as both the column gap and the item’s bottom margin — CSS columns have no row gap.
ordertop-to-bottom per column—The honest trade for not running a JS layout pass. Fine for a gallery; wrong for a ranked list or a feed, where a plain grid or Kanban is the right answer.

Behaviour

TopicDetailDescription
break-insideavoidApplied to every item, so a card is never sliced across a column boundary — which is the default without it.