Responsive Masonry Layout Component for Astro

An Astro-native masonry layout component with shortest-column balancing, responsive breakpoints, auto-sizing columns, and arrow key navigation.

@mannisto/astro-masonry is an Astro component that creates a masonry layout using shortest-column balancing, responsive breakpoints, and built-in arrow key navigation.

You can use it to build photo galleries, card grids, blog post feeds, portfolio grids, and any UI where items have varying heights, and you need them distributed across columns.

The component runs no client-side JavaScript for layout in the default configuration. Column distribution happens at render time.

The autoColumns prop introduces a resize observer for dynamic column counting, and keyboard navigation activates only when a user moves focus into the grid.

Features

  • Balances grid items across the shortest column.
  • Creates fixed column masonry layouts.
  • Adapts column counts across responsive breakpoints.
  • Fits columns based on a minimum column width.
  • Caps auto sized columns at a chosen maximum.
  • Preserves left to right item order for readable feeds.
  • Supports arrow key movement inside the grid.
  • Adds accessible labels and roles to the root layout.

Use Cases

  • Photo galleries with images of varying aspect ratios.
  • Blog index pages where post cards have different preview lengths.
  • Portfolio grids with project tiles of inconsistent heights.
  • Product listing pages that need a fluid, packed layout.

How To Use It

Installation

Install the package in your Astro project through npm, pnpm, or Yarn.

# Install the Astro masonry component with npm.
npm install @mannisto/astro-masonry
# Use pnpm if your Astro project uses pnpm workspaces.
pnpm add @mannisto/astro-masonry
# Use Yarn if your project already uses Yarn.
yarn add @mannisto/astro-masonry

Import the component from the package component entry point inside an .astro file.

---
import { Masonry } from "@mannisto/astro-masonry/components";
---

Basic Astro Masonry Example

Use this example for a small portfolio grid or a landing page section with uneven card heights.

---
import { Masonry } from "@mannisto/astro-masonry/components";
// Use realistic card data for an Astro page section.
const projectCards = [
  {
    title: "Landing Page Review",
    text: "A compact audit card for conversion notes and UI fixes."
  },
  {
    title: "Analytics Dashboard",
    text: "A longer card that explains chart states, filters, and weekly reporting."
  },
  {
    title: "Content Calendar",
    text: "A short card for editorial workflow notes."
  },
  {
    title: "Component Library",
    text: "A detailed card for reusable buttons, forms, badges, and layout blocks."
  }
];
---
<Masonry
  columns={3}
  gap="1.25rem"
  aria-label="Project card gallery"
  role="feed"
>
  {projectCards.map((card) => (
    <article class="rounded-xl border border-slate-200 p-5 shadow-sm">
      {/* Render each card inside the masonry column layout. */}
      <h3 class="text-lg font-semibold">{card.title}</h3>
      {/* Uneven text lengths show the masonry balancing behavior. */}
      <p class="mt-2 text-sm text-slate-600">{card.text}</p>
    </article>
  ))}
</Masonry>

Responsive Masonry Layout Example

Use this pattern when a gallery needs one column on mobile, two columns on tablets, and three columns on wider screens.

---
import { Masonry } from "@mannisto/astro-masonry/components";
// Image data can come from local content collections or CMS output.
const galleryItems = [
  {
    src: "/images/studio-desk.jpg",
    alt: "Minimal workspace with a laptop and design notes",
    caption: "Workspace"
  },
  {
    src: "/images/app-wireframes.jpg",
    alt: "Mobile app wireframes on a white desk",
    caption: "Wireframes"
  },
  {
    src: "/images/ui-cards.jpg",
    alt: "Dashboard UI cards arranged on a grid",
    caption: "Dashboard UI"
  },
  {
    src: "/images/color-system.jpg",
    alt: "Brand color tokens shown as cards",
    caption: "Color System"
  }
];
---
<Masonry
  columns={1}
  breakpoints={{ 640: 2, 1024: 3 }}
  gap="20px"
  aria-label="Design inspiration gallery"
>
  {galleryItems.map((item) => (
    <figure class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
      {/* Keep alt text meaningful for image galleries. */}
      <img
        src={item.src}
        alt={item.alt}
        loading="lazy"
        class="h-auto w-full"
      />
      {/* Add captions inside each grid item when context matters. */}
      <figcaption class="p-3 text-sm text-slate-600">
        {item.caption}
      </figcaption>
    </figure>
  ))}
</Masonry>

The columns value sets the base mobile column count. The breakpoints object uses minimum viewport widths in pixels. Each breakpoint value defines the column count for that width range.

Auto Sized Columns Example

Use auto sized columns when the masonry container can change width inside a page layout, dashboard shell, or responsive content wrapper.

---
import { Masonry } from "@mannisto/astro-masonry/components";
const resourceCards = [
  {
    name: "AI Chat Layout",
    type: "UI Block",
    note: "Card for a chat panel with prompt input and response area."
  },
  {
    name: "Pricing Section",
    type: "Template",
    note: "Card for monthly plans, feature rows, and CTA buttons."
  },
  {
    name: "Admin Sidebar",
    type: "Navigation",
    note: "Card for nested links, active state styles, and account controls."
  },
  {
    name: "Data Table Header",
    type: "Component",
    note: "Card for filters, search input, and export actions."
  },
  {
    name: "Onboarding Checklist",
    type: "Pattern",
    note: "Card for multi step setup tasks and completion states."
  }
];
---
<section class="mx-auto max-w-6xl px-4">
  <Masonry
    autoColumns={280}
    columns={4}
    gap="16px"
    aria-label="Web development resource cards"
  >
    {resourceCards.map((resource) => (
      <article class="rounded-xl bg-slate-950 p-5 text-white">
        {/* The auto column width starts from 280px. */}
        <p class="text-xs uppercase tracking-wide text-slate-400">
          {resource.type}
        </p>
        <h3 class="mt-2 text-lg font-semibold">{resource.name}</h3>
        {/* Different note lengths create a natural masonry layout. */}
        <p class="mt-3 text-sm text-slate-300">{resource.note}</p>
      </article>
    ))}
  </Masonry>
</section>

autoColumns={280} tells the component to fit as many columns as the container can hold at a 280px minimum. columns={4} caps the layout at four columns.

Sequential Order Example For Readable Feeds

Use sequential order for tutorial lists, editorial cards, changelogs, and any grid where reading order matters more than height balancing.

---
import { Masonry } from "@mannisto/astro-masonry/components";
const roadmapSteps = [
  "Install the Astro package",
  "Import the masonry component",
  "Render your content cards",
  "Add responsive breakpoints",
  "Label the grid for assistive tech",
  "Check keyboard movement"
];
---
<Masonry
  columns={3}
  gap={18}
  sequential
  aria-label="Masonry implementation steps"
  role="list"
>
  {roadmapSteps.map((step, index) => (
    <div class="rounded-lg border border-slate-200 p-4" role="listitem">
      {/* Sequential mode keeps this order easier to follow. */}
      <span class="text-sm font-medium text-slate-500">
        Step {index + 1}
      </span>
      <p class="mt-1 font-semibold text-slate-900">{step}</p>
    </div>
  ))}
</Masonry>

Sequential mode distributes items left to right and top to bottom. Use it for ordered content, documentation cards, and keyboard reading flows.

Implementation Tips

Use string values for gaps when you want CSS units such as rem, em, or %. Use number values when you want pixels.

Use autoColumns for container based layouts. Use breakpoints for viewport based layouts.

Add aria-label when the masonry region contains a meaningful gallery, feed, or list. Add role when the grid needs a clearer landmark or content type.

Use sequential when visual balance could change the reading sequence. This matters for tutorials, numbered content, and time based feeds.

Configuration Options

  • columns (number): Sets the number of columns in fixed mode. Default value is 1. In auto column mode, it acts as the maximum column cap. Auto column mode has no maximum cap when this option has no value.
  • gap (number | string): Sets the spacing between items. Default value is "1rem". Number values use pixels.
  • breakpoints (Record): Sets a mobile first map of minimum viewport width to column count. Keys use pixel widths.
  • autoColumns (number | string): Sets the minimum column width for automatic column fitting. Number values use pixels.
  • sequential (boolean): Distributes items left to right and top to bottom. Default value is false.
  • aria-label (string): Sets the accessible label on the root element.
  • role (string): Sets the ARIA role on the root element.
  • class (string): Adds class names to the root element.

FAQs

Q: Why does autoColumns not respond to window resize?
A: autoColumns uses a ResizeObserver on the container element, not a window.resize listener. If the container width does not change when the viewport changes (for example, inside a fixed-width sidebar), the column count will not update. Make sure the container is sized relative to the viewport or its parent.

Q: Can I use breakpoints without autoColumns?
A: Yes. breakpoints works independently of autoColumns. Pass columns={1} as the base and breakpoints={{ 768: 2, 1280: 3 }} to get a responsive fixed-column grid with no auto-sizing behavior.

Q: How do I make the grid accessible for screen readers?
A: Pass aria-label and role to the Masonry component. Use role="feed" for a dynamic card list and role="region" with a descriptive aria-label for a static gallery. The component places items in the DOM in column order, which matches the visual column layout for screen reader traversal.

Q: Why does my auto sized masonry grid create too many columns?
A: Add columns as a maximum cap when you use autoColumns. For example, autoColumns={280} and columns={4} keeps the grid at four columns or fewer.

Alternatives and Related Resources

eremannisto

eremannisto

UX Engineer and a Web Designer bridging the gap between design and development

Leave a Reply

Your email address will not be published. Required fields are marked *