Astro-native masonry layout component with shortest-column balancing, responsive breakpoints, and accessible arrow key navigation.
npm install @mannisto/astro-masonrypnpm add @mannisto/astro-masonryyarn add @mannisto/astro-masonry---
import { Masonry } from "@mannisto/astro-masonry/components"
---
<Masonry autoColumns={280} columns={3}>
{items.map((item) => (
<div>{item}</div>
))}
</Masonry>autoColumns fills as many columns as the container fits at the given minimum width. columns caps the maximum at 3.
| Prop | Type | Default | Description |
|---|---|---|---|
columns |
number |
1 |
Number of columns in fixed mode, or maximum cap when used with autoColumns. Defaults to unlimited when autoColumns is set |
gap |
number | string |
"1rem" |
Gap between items. Numbers are treated as px |
breakpoints |
Record<number, number> |
— | Map of minWidth → columns. Mobile-first, stacks on top of columns |
autoColumns |
number | string |
— | Fill as many columns as fit at this minimum width. Numbers are treated as px |
sequential |
boolean |
false |
Distribute items left to right, top to bottom instead of shortest-column |
aria-label |
string |
— | Sets aria-label on the root element |
role |
string |
— | Sets role on the root element |
class |
string |
— | Class names on the root element |
<Masonry columns={3}>
{items.map((item) => (
<div>{item}</div>
))}
</Masonry>
Items are distributed into the shortest column, keeping overall height balanced.
columns sets the base, breakpoints adds on top. Keys are minimum viewport widths in px.
<Masonry
columns={1}
breakpoints={{ 640: 2, 1024: 3 }}
>
{items.map((item) => (
<div>{item}</div>
))}
</Masonry>Fills as many columns as the container fits at the given minimum width. Responds automatically to container resize.
<Masonry autoColumns={280}>
{items.map((item) => (
<div>{item}</div>
))}
</Masonry>columns limits the maximum number of columns when used with autoColumns.
<Masonry
autoColumns={280}
columns={4}
>
{items.map((item) => (
<div>{item}</div>
))}
</Masonry>breakpoints can adjust the cap at different viewport widths.
<Masonry
autoColumns={280}
columns={1}
breakpoints={{ 640: 2, 1024: 4 }}
>
{items.map((item) => (
<div>{item}</div>
))}
</Masonry>Items are distributed left to right, top to bottom. Useful when reading order matters.
<Masonry columns={3} sequential>
{items.map((item) => (
<div>{item}</div>
))}
</Masonry>
Items are distributed left to right, top to bottom, preserving reading order.
Items can be added to the grid after initial render. Append them directly to any [data-masonry-column] element, then dispatch a masonry:refresh event on the root to redistribute:
const root = document.querySelector("[data-masonry]")
const column = root.querySelector("[data-masonry-column]")
const card = document.createElement("div")
column.appendChild(card)
root.dispatchEvent(new CustomEvent("masonry:refresh"))If your items change the column count (for example a viewport resize crosses a breakpoint), the grid re-layouts automatically and the new items are included without any extra steps.
When focus is inside the grid, arrow keys move between items:
| Key | Behaviour |
|---|---|
ArrowUp |
Previous item in the same column |
ArrowDown |
Next item in the same column |
ArrowLeft |
Item in the previous column at the same vertical position |
ArrowRight |
Item in the next column at the same vertical position |
Screen readers navigate items in column order, matching the visual layout. Use aria-label and role to identify the grid as a landmark or feed.
<Masonry
columns={3}
aria-label="Photo gallery"
role="feed"
>
{items.map((item) => (
<div>{item}</div>
))}
</Masonry>MIT © Ere Männistö

