# Masonry

Lay out items of different heights in columns, each one placed in the shortest column or in order.

Items keep their own height and stack into columns without gaps, like a Pinterest board. Modeled on
[MUI's Masonry](https://mui.com/material-ui/react-masonry/), and works in React, Vue, Svelte, Solid, Preact and vanilla
JS.

- Each item goes to the shortest column, or fills the columns in order
- A fixed column count, a minimum column width, or a column count set from CSS per breakpoint
- Items can span several columns
- Re-lays out when an item resizes, an image loads, or items are added or removed
- Renders as a plain grid until measured, so server-rendered markup reads fine before hydration
- RTL support

## Installation

Pick your framework in the preview, then copy the prompt into Claude Code, Cursor or
another coding agent, or add the component yourself with the shadcn CLI or by hand.

Add the component with the shadcn CLI, using the registry item for your framework, styling and API (Zag or Ark UI):

```sh
# React, Panda CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-react-panda.json

# React, CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-react.json

# Vue, Panda CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-vue-panda.json

# Vue, CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-vue.json

# Svelte, Panda CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-svelte-panda.json

# Svelte, CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-svelte.json

# Solid, Panda CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-solid-panda.json

# Solid, CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-solid.json

# Preact, Panda CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-preact-panda.json

# Preact, CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-preact.json

# Vanilla, Panda CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-vanilla-panda.json

# Vanilla, CSS, Zag
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-vanilla.json

# React, Panda CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-react-panda-ark.json

# React, CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-react-ark.json

# Vue, Panda CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-vue-panda-ark.json

# Vue, CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-vue-ark.json

# Svelte, Panda CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-svelte-panda-ark.json

# Svelte, CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-svelte-ark.json

# Solid, Panda CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-solid-panda-ark.json

# Solid, CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-solid-ark.json

# Preact, Panda CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-preact-panda-ark.json

# Preact, CSS, Ark UI
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/masonry-basic-preact-ark.json
```

With Panda CSS, add `"@isbatak/panda-masonry"` to `presets` in your Panda config, or import `masonryPreset` from it and add that instead.

Preact with Ark UI uses the React components through `preact/compat`, so alias `react` and `react-dom` to `preact/compat` unless `@preact/preset-vite` already does.

## Examples

Scroll to an example to load it in the preview, and open the code view there for the full source. The settings button in
the preview changes the example live, and the snippet under it follows. Each example is its own registry item, so you
can add it with the CLI the same way.

Items must be direct children of the root, and each one needs a unique `value`.

### Image masonry

Items can hold anything, images included. The layout runs again as each image loads, so lazy-loaded images don't need a
known size up front. Items are placed in reading order, row by row.

With Zag (React):

```ts
const controller = masonry.createMasonry()
const api = controller.connect(
  {
    id,
    columns: 3,
  },
  layout,
  normalizeProps,
)
```

With Ark UI (React):

```tsx
<Masonry.Root columns={3}>
  <Masonry.Item value="1">{/* … */}</Masonry.Item>
</Masonry.Root>
```

### Items with variable height

Every item is watched for size changes. Open an accordion and the items below it move, and may switch columns, so each
one still lands in the shortest column.

With Zag (React):

```ts
const controller = masonry.createMasonry()
const api = controller.connect(
  {
    id,
    columns: 3,
  },
  layout,
  normalizeProps,
)
```

With Ark UI (React):

```tsx
<Masonry.Root columns={3}>
  <Masonry.Item value="1">{/* … */}</Masonry.Item>
</Masonry.Root>
```

### Columns

Set `columns` for a fixed count. To change it per breakpoint, leave `columns` unset and set the `--masonry-columns` CSS
variable on the root instead, here 3 columns on small screens and 4 from `sm` up. Set `minColumnWidth`, for example
`"12rem"`, to fit as many columns as there is room for.

With Zag (React):

```ts
const controller = masonry.createMasonry()
const api = controller.connect(
  {
    id,
  },
  layout,
  normalizeProps,
)
```

With Ark UI (React):

```tsx
<Masonry.Root>
  <Masonry.Item value="1">{/* … */}</Masonry.Item>
</Masonry.Root>
```

### Spacing

Set `gap` to a number of pixels or a CSS length. Leave it unset to take the spacing from CSS, so it can change per
breakpoint, here from `2` to `6`. With Panda CSS, the recipe's `gap` variant picks from the spacing tokens.

With Zag (React):

```ts
const controller = masonry.createMasonry()
const api = controller.connect(
  {
    id,
    columns: 3,
  },
  layout,
  normalizeProps,
)
```

With Ark UI (React):

```tsx
<Masonry.Root columns={3}>
  <Masonry.Item value="1">{/* … */}</Masonry.Item>
</Masonry.Root>
```

### Sequential

Set `sequential` to fill the columns in order, left to right, instead of adding each item to the shortest column.

With Zag (React):

```ts
const controller = masonry.createMasonry()
const api = controller.connect(
  {
    id,
    columns: 4,
    sequential: true,
  },
  layout,
  normalizeProps,
)
```

With Ark UI (React):

```tsx
<Masonry.Root columns={4} sequential>
  <Masonry.Item value="1">{/* … */}</Masonry.Item>
</Masonry.Root>
```

### Spanning columns

Set `span` on an item to make it cover several columns. It goes where the columns it covers are shortest, and the span
is capped at the number of columns.

With Zag (React):

```ts
const controller = masonry.createMasonry()
const api = controller.connect(
  {
    id,
    columns: 3,
  },
  layout,
  normalizeProps,
)
```

With Ark UI (React):

```tsx
<Masonry.Root columns={3}>
  <Masonry.Item value="1">{/* … */}</Masonry.Item>
</Masonry.Root>
```

## Server-side rendering

Until the items are measured, the root is a plain CSS grid with the same columns and gap, so server-rendered markup
shows every item in place without overlapping. After hydration the items are measured and moved into their columns, and
the root gets a `data-measured` attribute. There's no need for a default height or column count.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | — | The unique id of the masonry. |
| `ids` | `ElementIds` | — | The ids of the elements in the masonry. Useful for composition. |
| `columns` | `number` | `4` | The number of columns. Leave it unset to control it from CSS with the `--masonry-columns` variable, for example to change it per breakpoint. |
| `minColumnWidth` | `string` | — | The minimum column width, for example `"12rem"`. When set, the root fits as many columns as there is room for and `columns` is ignored. |
| `gap` | `number \| string` | — | The space between items, in pixels or as a CSS length. Leave it unset to control it from CSS with `gap`. |
| `sequential` | `boolean` | `false` | Whether items fill the columns in order, left to right, instead of going to the shortest column. |
| `onLayoutChange` | `(details: LayoutChangeDetails) => void` | — | Function called when the number of columns or the height changes. |
| `dir` | `"ltr" \| "rtl"` | `"ltr"` | The document's text/writing direction. |

## API

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `columns` | `number` | — | The number of columns, or `0` before the first layout. |
| `measured` | `boolean` | — | Whether the items have been measured and placed. Before that they render as a plain grid. |
| `reflow` | `() => void` | — | Measure and place the items again. |
