Card Grid
A grid of 3–24 peer cards to browse, with an optional header, filter bar, result count and "See all" footer.
import {
CardGrid,
CardGridFooter,
CardGridFooterLink,
CardGridHeader,
CardGridItem,
CardGridList,
} from '@fairgarden-private/design/components/CardGrid'
Each CardGridItem holds one Card. The root is the card-grid container: the columns follow its width, not the viewport, so a grid in a side column reflows as it would on a phone. Without container-query support the list falls back to auto-fit columns at the card minimum. Don't use a card grid for ranked or dated items (use Index Rows or Timeline), don't mix card kinds, and never scroll it sideways.
Editorial and compact
Editorial and compact
kind is required. editorial and block grids run 1 column below 608 px of container, 2 from 608 and 3 from 944; compact grids 1 below 360, 2 below 704 and 4 from 704. lead puts one lead card across the grid, with Index Rows after it. The footer's "See all" is the standalone Link. Faced cards pad by the same container. header renders the §11.8 section header in the header slot: pass SectionHeader props (the grid is then labelled by its heading) or any node, such as a SectionHeader element. CardGridHeader still takes children for a hand-built header.
Programs
Get outside
- Volunteers tally migrants along the river trail.
Meadow restoration
Three seasons of native seed and patience.Night walk
Owls, moths and the smell of wet leaves.
On the preserve
import {
Card,
CardBody,
CardMeta,
CardTitle,
CardTitleLink,
} from '@fairgarden-private/design/components/Card'
import {
CardGrid,
CardGridFooter,
CardGridFooterLink,
CardGridItem,
CardGridList,
} from '@fairgarden-private/design/components/CardGrid'
import { SectionHeader } from '@fairgarden-private/design/components/SectionHeader'
import styles from './kinds.module.css'
const programs = [
{ title: 'Spring bird count', meta: 'Program · May', body: 'Volunteers tally migrants along the river trail.' },
{ title: 'Meadow restoration', meta: 'Program · All year', body: 'Three seasons of native seed and patience.' },
{ title: 'Night walk', meta: 'Event · Fridays', body: 'Owls, moths and the smell of wet leaves.' },
]
const species = ['Barred owl', 'Wood thrush', 'Red eft', 'Pawpaw']
/**
* Editorial grids go 1 → 2 → 3 columns on their container; compact grids
* 1 → 2 → 4. `header` takes SectionHeader props (the grid is then labelled
* by the heading) or any node, such as a SectionHeader element.
*/
export function CardGridKinds() {
return (
<div className={styles.stack}>
<CardGrid kind="editorial" header={{ eyebrow: 'Programs', heading: 'Get outside' }}>
<CardGridList>
{programs.map((program) => (
<CardGridItem key={program.title}>
<Card faced>
<CardTitle>
<CardTitleLink href="#kinds">{program.title}</CardTitleLink>
</CardTitle>
<CardMeta>{program.meta}</CardMeta>
<CardBody>{program.body}</CardBody>
</Card>
</CardGridItem>
))}
</CardGridList>
<CardGridFooter>
<CardGridFooterLink href="#kinds">See all programs</CardGridFooterLink>
</CardGridFooter>
</CardGrid>
<CardGrid
kind="compact"
aria-labelledby="card-grid-species"
header={<SectionHeader level="module" heading="On the preserve" headingId="card-grid-species" />}
>
<CardGridList>
{species.map((name) => (
<CardGridItem key={name}>
<Card>
<CardTitle>
<CardTitleLink href="#kinds">{name}</CardTitleLink>
</CardTitle>
<CardMeta>Seen this week</CardMeta>
</Card>
</CardGridItem>
))}
</CardGridList>
</CardGrid>
</div>
)
}
Filtered, with a count
Filtered, with a count
CardGridToolbar holds filter chips and sort; it is hidden in print. CardGridCount is a polite live region, so keep it mounted and change its text. When a filter leaves nothing, render an EmptyState with filtered in place of the list, name the filter and offer "Clear Filters".
'use client'
import * as React from 'react'
import { Button } from '@fairgarden-private/design/components/Button'
import {
Card,
CardMeta,
CardTitle,
CardTitleLink,
} from '@fairgarden-private/design/components/Card'
import {
CardGrid,
CardGridCount,
CardGridItem,
CardGridList,
CardGridToolbar,
} from '@fairgarden-private/design/components/CardGrid'
import {
EmptyState,
EmptyStateAction,
EmptyStateHeading,
EmptyStateText,
} from '@fairgarden-private/design/components/EmptyState'
import { Toggle } from '@fairgarden-private/design/components/Toggle'
const events = [
{ title: 'Night walk', format: 'In person' },
{ title: 'Seed swap', format: 'In person' },
{ title: 'Stream ecology talk', format: 'Virtual' },
{ title: 'Trail crew day', format: 'In person' },
]
/** The count is a polite live region; an empty result swaps the list for an EmptyState. */
export function CardGridFiltered() {
const [virtualOnly, setVirtualOnly] = React.useState(false)
const [hideVirtual, setHideVirtual] = React.useState(false)
const shown = events.filter(
(event) =>
(!virtualOnly || event.format === 'Virtual') && (!hideVirtual || event.format !== 'Virtual')
)
return (
<CardGrid kind="compact">
<CardGridToolbar>
<Toggle variant="chip" pressed={virtualOnly} onPressedChange={setVirtualOnly}>
Virtual Only
</Toggle>
<Toggle variant="chip" pressed={hideVirtual} onPressedChange={setHideVirtual}>
Hide Virtual
</Toggle>
</CardGridToolbar>
<CardGridCount>
{shown.length} {shown.length === 1 ? 'event' : 'events'}
</CardGridCount>
{shown.length > 0 ? (
<CardGridList>
{shown.map((event) => (
<CardGridItem key={event.title}>
<Card faced>
<CardTitle>
<CardTitleLink href="#filtered">{event.title}</CardTitleLink>
</CardTitle>
<CardMeta>{event.format}</CardMeta>
</Card>
</CardGridItem>
))}
</CardGridList>
) : (
<EmptyState filtered>
<EmptyStateHeading>No events match these filters</EmptyStateHeading>
<EmptyStateText>“Virtual Only” and “Hide Virtual” leave nothing to show.</EmptyStateText>
<EmptyStateAction>
<Button
onClick={() => {
setVirtualOnly(false)
setHideVirtual(false)
}}
>
Clear Filters
</Button>
</EmptyStateAction>
</EmptyState>
)}
</CardGrid>
)
}
Color and print
primary colors the count and footer link; secondary only the footer link's underline and chevron. Bare cards inherit both. Faced cards open their own face, so pass primary and secondary to each faced card too. In print the grid runs 2-up with each row whole, and the filter bar is dropped.
API Reference
CardGrid
The card grid module: an optional header (header, or a composed
CardGridHeader holding the §11.8 section header), an optional
CardGridToolbar and CardGridCount, the
CardGridList of CardGridItems (one Card each), and an optional
CardGridFooter with a CardGridFooterLink (“See all ›"). Never mix
card kinds in one grid, and never scroll it horizontally (use §12.4).
| Prop | Type | Description |
|---|---|---|
header | | The module header (§11.8), rendered in the header slot before the
children: a |
kind | | Required. |
primary | | Primary Radix scale: the count and footer link text. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: the footer link’s underline and chevron
( |
CardGridHeader, CardGridToolbar and CardGridCount
The module header slot: holds the §11.8 SectionHeader (eyebrow +
hairline for explore grids; CardGrid’s header prop fills it for you).
Sits --size-px-5 above the grid and stays with the first row in print.
The filter and sort bar: filter chips (§9.4) and a sort Select (§10.5), wrapping onto more lines as needed. Hidden in print.
The result count of a filtered grid (“12 events”), a polite live region
in --primary12. Keep it mounted and change its text, so the change is
announced.
CardGridList and CardGridItem
The grid itself, a ul whose columns follow the card-grid container
(auto-fit without container-query support). Rows keep equal heights and
print 2-up, each row whole.
One cell: holds one Card, stretched to the row’s height.
CardGridFooter and CardGridFooterLink
The module footer, --size-px-7 below the grid: a CardGridFooterLink or §9.9 pagination.
“See all ›": the standalone Link (§9.3), type-label caps authored in
sentence case, with its chevron in --role-glyph. The Link owns every
state, so hover follows the central link model.
| Prop | Type | Description |
|---|---|---|
external | | Adds the arrow-open mark and “(external site)” for assistive technology.
Default |
index | | Adds the screen-only visited ✓ used in long indexes, such as reference
and archive lists. Default |
list | | With |
muted | | Rests in |
primary | | Primary Radix scale: the link text and focus ring. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: the accent underline. Never defaulted; omitted, it inherits the scope. |
className | | Extra class names, added after the module’s own. |
Additional types
CardGridCountProps
Props for CardGridCount: paragraph props and render.
type CardGridCountProps = useRender.ComponentProps<'p'>CardGridFooterLinkProps
CardGridFooterProps
CardGridHeaderProps
Props for CardGridHeader: header props and render.
type CardGridHeaderProps = useRender.ComponentProps<'header'>CardGridItemProps
Props for CardGridItem: list-item props and render.
type CardGridItemProps = useRender.ComponentProps<'li'>CardGridKind
The four grid builds (§12.3).
type CardGridKind = 'editorial' | 'compact' | 'block' | 'lead'CardGridListProps
Props for CardGridList: list props and render.
type CardGridListProps = useRender.ComponentProps<'ul'>CardGridProps
Props for CardGrid: section props, render, the kind and the color axes.
type CardGridProps = React.ClassAttributes<HTMLElement> &
React.HTMLAttributes<HTMLElement> &
__type & {
kind: CardGridKind;
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
header?: React.ReactNode | SectionHeaderProps;
}CardGridToolbarProps
Props for CardGridToolbar: div props and render.
type CardGridToolbarProps = useRender.ComponentProps<'div'>Specification: DESIGN-SYSTEM.md §12.3 (card grid) and §5.10.2 (module reflow).