Xzero UI
Pro library

Blog grid

A blog index with a featured post, category filter, post cards, pagination and an in-place reader.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui/blog-grid
Live · keyboard ready

Use this as the index of a blog, journal or news page. Load posts from your CMS into blog-grid-data.ts or the posts prop, pass getHref to link each card to its article route, and keep category and page in your URL through the controlled props.

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui/blog-grid

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { BlogGrid } from "@/components/xzero/blocks/blog-grid/blog-grid";

export function BlogIndex({ posts }: { posts: BlogPost[] }) {
  return (
    <BlogGrid
      posts={posts}
      categories={["Product", "Design", "Engineering", "Company"]}
      getHref={post => `/blog/${post.id}`}
    />
  );
}

API reference

BlogGrid

A blog index: title, category filter with a gliding highlight, a large featured post, a responsive card grid and pagination. Without getHref, cards open an in-place reader whose image morphs out of the card.

PropTypeDefaultDescription
postsBlogPost[]sample postsPosts to list. Sorted newest first by date.
categoriesstring[]–Filter labels in order. "All" is added in front.
titlestring"Journal"Section heading.
descriptionstring–Line under the heading. Pass an empty string to hide it.
categorystring–Active category, or "All" (controlled).
defaultCategorystring"All"Initial category when uncontrolled.
onCategoryChange(category: string) => void–Called when a filter is picked. The page resets to 1.
pagenumber–One based page (controlled).
defaultPagenumber1Initial page when uncontrolled.
onPageChange(page: number) => void–Called from the page numbers and the previous and next buttons.
pageSizenumber6Cards per page below the featured post.
showFeaturedbooleantrueShows the post marked featured (or the newest) of the current filter as a large card on page one.
getHref(post: BlogPost) => string–Link for each post. When set, cards are plain links and the in-place reader is off.
onPostOpen(post: BlogPost) => void–Called when a post opens, for analytics or routing.
classNamestring–Extra class on the section.

BlogPost

One post.

PropTypeDefaultDescription
id (required)string–Stable id, also the fallback hash link.
title (required)string–Card and reader title.
excerpt (required)string–Two line summary on cards, the lead paragraph in the reader.
category (required)string–Must match one of categories to be filterable.
date (required)string–ISO date (YYYY-MM-DD).
readTime (required)number–Minutes to read.
author (required){ name: string; avatar?: string; role?: string }–Byline.
image{ src: string; alt: string }–Cover image. Cards crop it to 3:2.
bodystring[]–Paragraphs for the in-place reader.
featuredboolean–Prefer this post for the featured slot.

Keyboard interactions

KeysAction
TabMoves through filters, cards, and pagination.
EnterOpens the focused post, or picks the focused filter or page.
SpacePicks the focused filter or page.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Guidance for AI tools

Blocks are complete, self-contained screens with sample data. Replace the sample data and connect the callbacks described above. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components