Index Rows
Ruled listing rows for many same-kind items: articles, events, reports, ranked topics and related pages. The default listing on reading pages.
import {
IndexRow,
IndexRows,
IndexRowsList,
IndexRowTitle,
IndexRowTitleLink,
} from '@fairgarden-private/design/components/IndexRows'
Each row has one title Link whose hit area covers the row; the row draws the focus ring inside its rules. Rules run from the text column to the module edge, never under the key column, and rows are never boxed. Numbers and dates use tabular figures. Use cards instead only when images drive the choice.
Ranked, related and article
Ranked, related and article
kind is required. ranked puts a muted rank numeral (no period) in a narrow column and expects a count or a stated ranking basis; it renders an ordered list. related puts a 72 px thumbnail beside each title. article sets serif titles over a caps meta line and an optional dek; from 1024 px of container the meta moves to a right-aligned column. Mark the current row with aria-current on its link, which adds the 3 px bar. index on the link adds the visited ✓ for long indexes.
Most read topics
- 1
Birding(29)
- 2
Trail work(21)
- 3
Native plants(17)
The long return of the river otter
Twenty years after the last sighting, tracks on the sandbar.
import {
IndexRow,
IndexRowCount,
IndexRowDek,
IndexRowMeta,
IndexRows,
IndexRowsFooter,
IndexRowsHeader,
IndexRowsList,
IndexRowTitle,
IndexRowTitleLink,
} from '@fairgarden-private/design/components/IndexRows'
import { Link } from '@fairgarden-private/design/components/Link'
import { thumb } from './thumb'
import styles from './kinds.module.css'
const topics = [
{ label: 'Birding', count: 29 },
{ label: 'Trail work', count: 21 },
{ label: 'Native plants', count: 17 },
]
/** Ranked (with counts), related (with thumbnails) and article rows. */
export function IndexRowsKinds() {
return (
<div className={styles.stack}>
<IndexRows kind="ranked">
<IndexRowsHeader>
<h2 className={styles.heading}>Most read topics</h2>
</IndexRowsHeader>
<IndexRowsList>
{topics.map((topic, index) => (
<IndexRow key={topic.label} rank={index + 1}>
<IndexRowTitle>
<IndexRowTitleLink href="#kinds" aria-current={index === 1 ? 'page' : undefined}>
{topic.label}
</IndexRowTitleLink>
<IndexRowCount>({topic.count})</IndexRowCount>
</IndexRowTitle>
</IndexRow>
))}
</IndexRowsList>
<IndexRowsFooter>
<Link kind="standalone" href="#kinds">
See all topics
</Link>
</IndexRowsFooter>
</IndexRows>
<IndexRows kind="related">
<IndexRowsHeader>
<h2 className={styles.heading}>Related</h2>
</IndexRowsHeader>
<IndexRowsList>
{['How owls hear', 'Nest boxes that work'].map((title) => (
<IndexRow key={title} thumb={<img src={thumb} alt="" />}>
<IndexRowTitle>
<IndexRowTitleLink href="#kinds">{title}</IndexRowTitleLink>
</IndexRowTitle>
</IndexRow>
))}
</IndexRowsList>
</IndexRows>
<IndexRows kind="article">
<IndexRowsList>
<IndexRow>
<IndexRowTitle>
<IndexRowTitleLink href="#kinds" index>
The long return of the river otter
</IndexRowTitleLink>
</IndexRowTitle>
<IndexRowMeta>Field notes · 12 Sep 2026</IndexRowMeta>
<IndexRowDek>Twenty years after the last sighting, tracks on the sandbar.</IndexRowDek>
</IndexRow>
<IndexRow>
<IndexRowTitle>
<IndexRowTitleLink href="#kinds" index>
Why we leave the dead trees standing
</IndexRowTitleLink>
</IndexRowTitle>
<IndexRowMeta>Stewardship · 3 Sep 2026</IndexRowMeta>
</IndexRow>
</IndexRowsList>
</IndexRows>
</div>
)
}
Event index
Event index
Event rows carry a mono date key, --role-rule rules and, from 768 px of container, a column rule between the date and the title. IndexRowDisclosure makes the title the trigger of a Collapsible: collapsed titles are muted, open ones grow and take the full ink, and the LTA glyph turns inward. Past events say "Past" in words; they are never dimmed. Rows print expanded with the date column at the left.
Meet at the north kiosk. Bring a red light; we walk slowly and quietly.
- Host
- Ana Díaz
- Entrance
- North lot
Meet at the north kiosk. Bring a red light; we walk slowly and quietly.
- Host
- Ana Díaz
- Entrance
- North lot
Meet at the north kiosk. Bring a red light; we walk slowly and quietly.
- Host
- Ana Díaz
- Entrance
- North lot
import { Button } from '@fairgarden-private/design/components/Button'
import {
IndexRow,
IndexRowDisclosure,
IndexRows,
IndexRowsList,
} from '@fairgarden-private/design/components/IndexRows'
import styles from './event.module.css'
const events = [
{ date: '2026-10-03', key: '03 / 10', title: 'Night walk at Hemlock Ravine', meta: 'In person · 7 p.m.' },
{ date: '2026-10-11', key: '11 / 10', title: 'Seed swap', meta: 'In person · 10 a.m.' },
{ date: '2026-09-12', key: '12 / 09', title: 'Stream ecology talk', meta: 'Virtual · Past' },
]
/** Event rows expand in place; collapsed titles are muted, open titles grow and darken. */
export function IndexRowsEvent() {
return (
<IndexRows kind="event">
<IndexRowsList>
{events.map((event) => (
<IndexRow key={event.title} date={<time dateTime={event.date}>{event.key}</time>}>
<IndexRowDisclosure title={event.title} meta={event.meta}>
<p>Meet at the north kiosk. Bring a red light; we walk slowly and quietly.</p>
<dl className={styles.pairs}>
<dt>Host</dt>
<dd>Ana Díaz</dd>
<dt>Entrance</dt>
<dd>North lot</dd>
</dl>
<Button size="sm">Register</Button>
</IndexRowDisclosure>
</IndexRow>
))}
</IndexRowsList>
</IndexRows>
)
}
API Reference
IndexRows
The index module: an optional IndexRowsHeader (the §11.8 eyebrow and
hairline, plus any Tabs), the IndexRowsList of IndexRows and an
optional IndexRowsFooter (“See all ›"). Rows are separated by rules,
never boxed; numbers and dates use tabular figures.
| Prop | Type | Description |
|---|---|---|
kind | | Required. |
primary | | Primary Radix scale: titles, keys, rules, bars and glyphs. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: the title links’ underline and the footer
chevron ( |
IndexRowsHeader, IndexRowsList and IndexRowsFooter
The module header slot: the §11.8 section header (eyebrow + --role-hairline
rule) and, where needed, “Upcoming / Past” Tabs (§9.5). Stays with the
first row in print.
The rows: an ol for ranked lists, whose order means something; a ul otherwise.
The module footer: a standalone Link (“See all ›", type-label caps
with its --role-glyph chevron). Prints “Full list (short URL)” for lists
of more than 10 rows (§7.6).
IndexRow
One row: an optional key column (rank, date or thumb) and the text
column, which holds IndexRowTitle (or an event’s IndexRowDisclosure),
IndexRowMeta and IndexRowDek. The row rule runs from the text column
to the module edge, never under the key column.
| Prop | Type | Description |
|---|---|---|
date | | Event rows: the date key, e.g. |
rank | | Ranked rows: the rank numeral, with no period (“1″, “2″ …); |
thumb | | Related rows: a thumbnail |
IndexRowTitle, IndexRowTitleLink and IndexRowCount
The row’s title: type-itemhead for article and event rows,
type-body-ui at --font-weight-6 for ranked and related rows. Wrap an
IndexRowTitleLink inside, and follow it with an IndexRowCount in
ranked lists.
The row’s one link, inside IndexRowTitle: a §9.3 title Link whose hit
area stretches over the row. Pass aria-current="page" (or "true" for
the active filter row) to mark the current row. index adds the visited ✓
for long indexes.
| 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. |
The ranked label’s count, “(29)": type-body-ui in --role-muted, after the title.
IndexRowMeta and IndexRowDek
The meta line in --role-muted: type-label caps for article rows,
type-caption otherwise. Past rows say “Past” here; never dim them. From
1024 px of container an article row’s meta moves to a right-aligned column.
An optional one- or two-line dek under an article title: type-body-ui in --primary12.
IndexRowDisclosure
An expandable event row (§10.13): the title is the trigger, inside an
h3, with the LTA disclosure glyph; the details (body, /
“Entrance” pairs, a sm Button) sit in the panel. Collapsed titles are
--role-muted and grow to type-h2 in --primary12 when open. Rows
print expanded, glyphs hidden.
| Prop | Type | Description |
|---|---|---|
meta | | An optional meta line under the title (format, place; “Past”), shown in both states. |
panelProps | | Props for the details panel. |
title | | The event title, the trigger’s label: |
Additional types
IndexRowCountProps
Props for IndexRowCount: span props and render.
type IndexRowCountProps = useRender.ComponentProps<'span'>IndexRowDekProps
Props for IndexRowDek: paragraph props and render.
type IndexRowDekProps = useRender.ComponentProps<'p'>IndexRowDisclosureProps
Props for IndexRowDisclosure: Base UI Collapsible Root props plus the title, meta and panel props.
type IndexRowDisclosureProps = {
/** The event title, the trigger's label: `--role-muted` collapsed, `--primary12` open. */
title: React.ReactNode;
/** An optional meta line under the title (format, place; "Past"), shown in both states. */
meta?: React.ReactNode;
/** Props for the details panel. */
panelProps?: Omit<CollapsiblePanelProps, 'children'>;
}IndexRowMetaProps
Props for IndexRowMeta: paragraph props and render.
type IndexRowMetaProps = useRender.ComponentProps<'p'>IndexRowProps
Props for IndexRow: list-item props, render and the key column content.
type IndexRowProps = React.ClassAttributes<HTMLLIElement> &
React.LiHTMLAttributes<HTMLLIElement> &
__type & { rank?: React.ReactNode; date?: React.ReactNode; thumb?: React.ReactNode }IndexRowsFooterProps
IndexRowsHeaderProps
Props for IndexRowsHeader: header props and render.
type IndexRowsHeaderProps = useRender.ComponentProps<'header'>IndexRowsKind
The four listing builds (§12.11).
type IndexRowsKind = 'ranked' | 'related' | 'event' | 'article'IndexRowsListProps
Props for IndexRowsList: list props and render.
type IndexRowsListProps = useRender.ComponentProps<'ul'>IndexRowsProps
Props for IndexRows: section props, render, the kind and the color axes.
type IndexRowsProps = React.ClassAttributes<HTMLElement> &
React.HTMLAttributes<HTMLElement> &
__type & {
kind: IndexRowsKind;
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;
}IndexRowTitleLinkProps
Props for IndexRowTitleLink: Link props except kind, which is always title.
type IndexRowTitleLinkProps = {
/** Extra class names, added after the module's own. */
className?: string;
/**
* Primary Radix scale: the link text and focus ring. Never defaulted;
* omitted, it inherits the scope [D133].
*/
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/**
* Secondary Radix scale: the accent underline. Never defaulted; omitted,
* it inherits the scope.
*/
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;
/**
* With `kind="nav"`: a list link (nav-panel, footer, drawer and breadcrumb
* lists), whose hover is `--role-link-hover` color only, plus the
* `--ds-stroke-1-5` `--role-accent` underline where that ink is
* `--primary12`. Without it, `nav` is bare navigation text (bar and
* utility items, page numbers, toolbar links), whose hover is the
* `--border-size-2` `--role-accent` underline (§9.3) [D181]. Default
* `false`.
*/
list?: boolean | null;
/**
* Adds the screen-only visited ✓ used in long indexes, such as reference
* and archive lists. Default `false` [D174, D175].
*/
index?: boolean | null;
/**
* Adds the arrow-open mark and "(external site)" for assistive technology.
* Default `false`.
*/
external?: boolean | null;
/**
* Rests in `--role-muted` instead of `--primary12`, as a breadcrumb's
* ancestors. Hover takes `--role-link-hover` only (the underline is added
* where that ink is `--primary12`); with `kind="nav"` it replaces the bar
* item's bare-text underline (§9.3, §9.8) [D181]. Default `false`.
*/
muted?: boolean | null;
}IndexRowTitleProps
Props for IndexRowTitle: heading props and render (default <h3>).
type IndexRowTitleProps = useRender.ComponentProps<'h3'>Specification: DESIGN-SYSTEM.md §12.11 (ruled index rows) and §5.10.2 (module reflow).