Blog index
A blog landing page: featured post, search, category and tag filters, post cards with cover, date and reading time, and pagination.
Code
<BlogIndex title="Blog" description="Notes on design and engineering." posts={posts} postHref={(p) => `/blog/${p.slug}`}/>Website · beta
Live examples and controls: Blog index in the lab.
Install
npx shadcn@latest add https://docs.nasaqui.com/r/blog-index.jsonThe landing page of a blog. A featured post on top, a search box, category and tag chips, a grid of post cards (cover, category, title, excerpt, date and reading time) and pagination. It is controlled or uncontrolled for both filters and the page, so you can keep them in the URL.
When to use
- The list page of a blog, a changelog of articles or a news section.
- A "latest writing" row: use
PostCardon its own, orProfileWriting.
When not to use
Import
import { BlogIndex, PostCard } from "@fadymondy/nasaq/web";Quick start
<BlogIndex
title="Blog"
description="Notes on design and engineering."
posts={posts}
postHref={(p) => `/blog/${p.slug}`}
/>Anatomy
BlogIndex data-slot="blog-index"
├─ SectionHeader title, description, actions
├─ featured PostCard variant="featured" (newest post marked featured, else newest)
├─ toolbar search InputGroup with clear button, category ChipGroup, tag ChipGroup
├─ post grid PostCard x pageSize, container-query columns
├─ EmptyState when nothing matches
└─ PaginationAPI
BlogIndex
| Prop | Type | Default | Description |
|---|---|---|---|
posts | BlogPostSummary[] | required | All posts. Filtering and paging are done here. |
title? / description? / actions? | ReactNode | none | Header content. |
pageSize? | number | 6 | Cards per page. |
showFeatured? | boolean | true | Show the featured post above the grid. |
postHref? | (post) => string | #slug | Link of each card. |
onOpenPost? | (post) => void | none | Called when a card is opened. |
filters? / defaultFilters? / onFiltersChange? | BlogFilters | empty | { query, category, tag }. |
page? / defaultPage? / onPageChange? | number | 1 | Current page, 1-based. |
maxTags? | number | 8 | Tag chips shown, most used first. |
labels? | Partial<BlogIndexLabels> | locale | Override any string. |
PostCard and PostCover
PostCard takes post, href, onOpen, variant (default, featured, compact) and labels. The title is a stretched link, so the whole card is one target with one tab stop. PostCover shows post.cover or, without one, art generated from the slug using the --nq-tag-* tokens.
Model helpers
Pure functions, importable in Node: filterPosts, sortByDate, paginate, pickFeatured, postCategoryCounts, tagCounts, relatedPosts, adjacentPosts, readingTime, extractToc, slugifyHeading, activeHeadingId, readingProgress, remarkCallouts.
Accessibility
- The search is a labelled field; the result count is announced in a polite live region.
- Filter chips are toggle buttons with
aria-pressed. - Each card is an
<article>with one link; the cover is decorative. - Pagination is a
navwitharia-current="page".
RTL & i18n
Strings come from the provider locale (English, Arabic) and can be overridden with labels. Dates and numbers use Latin digits. Layout uses logical classes only.
Styling
Container queries choose the columns (1, 2 or 3), so the block adapts to its parent and not the window. Covers use tokens, no hex.
Do / Don't
- Do give every post a
readingMinutes, or compute it from the body withreadingTime. - Do keep
filtersin the URL for shareable views. - Don't pass a partial list and expect server paging:
BlogIndexpages what it is given.
Related
BlogPost, Pagination, SectionHeader.
Lab
Story Components/Layout/Blog Index and page story Pages/Public/Blog.
Translations
App strings for the Nasaq locale: TranslationsProvider and useT() give t(key, vars) over your dictionaries, with nested and namespaced keys, interpolation, CLDR plurals, locale fallback, a remembered choice and seedLocale for server defaults.
Blog post
A post page: cover, byline, reading progress bar, sticky table of contents with scroll-spy, rich Markdown body with callouts and code, share, tags, author, previous and next, related posts and a comments slot.