Components · Layout
SectionCard
A titled card for one section: header with actions and "View all", body, footer, and built-in loading, empty and error states.
Header actions, "View all" and footer
- Draft the launch brief
- Review pricing copy
- Book the kickoff call
import { Badge, Button, SectionHeaderLink } from '@rookcom/monarch-react';
import { SectionCard } from '@rookcom/monarch-react/patterns';
// ClockIcon, PlusIcon: demo icons from the docs site; use your own.
const tasks = ['Draft the launch brief', 'Review pricing copy', 'Book the kickoff call'];
<SectionCard
title="Open tasks"
description="Assigned to you this week."
meta={<Badge>3</Badge>}
icon={<ClockIcon />}
actions={
<Button size="xs" leadingIcon={<PlusIcon />}>
Add
</Button>
}
link={
<SectionHeaderLink href="#/components/section-card">
View all tasks
</SectionHeaderLink>
}
footer={<span>Updated just now</span>}
>
<ul style={{ margin: 0, paddingInlineStart: '1.25rem' }}>
{tasks.map((t) => (
<li key={t}>{t}</li>
))}
</ul>
</SectionCard>Usage examples
Loading, empty and error states
import { useState } from 'react';
import { Button } from '@rookcom/monarch-react';
import { SectionCard, type SectionCardState } from '@rookcom/monarch-react/patterns';
function StatesDemo() {
const [state, setState] = useState<SectionCardState>('loading');
return (
<div style={{ display: 'grid', gap: '0.75rem', width: '100%' }}>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
{(['ready', 'loading', 'empty', 'error'] as const).map((s) => (
<Button key={s} size="xs" aria-pressed={state === s} onClick={() => setState(s)}>
{s}
</Button>
))}
</div>
<SectionCard
title="Upcoming invoices"
state={state}
emptyDescription="Invoices you schedule appear here."
errorDescription="Check your connection and try again."
onRetry={() => setState('ready')}
>
<p style={{ margin: 0 }}>3 invoices due this month.</p>
</SectionCard>
</div>
);
}
<StatesDemo />Collapsible and danger tone
import { Button } from '@rookcom/monarch-react';
import { SectionCard } from '@rookcom/monarch-react/patterns';
<div style={{ display: 'grid', gap: '1rem', width: '100%' }}>
<SectionCard title="Activity" collapsible defaultOpen={false}>
<p style={{ margin: 0 }}>Ada changed the status to Active.</p>
</SectionCard>
<SectionCard
title="Delete this project"
tone="danger"
description="Deleting the project removes its tasks and files."
actions={
<Button variant="danger" size="sm">
Delete project
</Button>
}
/>
</div>Purpose
Puts one section of a dashboard, detail or settings page in a card: a SectionHeader (title, description, meta, actions, "View all" link), the body, an optional footer, and the loading, empty and error states every data-backed section needs. It can be collapsible.
Use when
- Dashboard widgets and detail-page panels that load their own data.
- A titled card with header actions or a "View all" link.
- A table or list that runs edge to edge inside a titled card (
flush).
Avoid when
- A group of settings rows with Save/Discard: use SettingsSection.
- An unboxed section title: use SectionHeader.
- A plain surface without a title: use Card.
Design principles
- Always a labelled region: the heading names the
<section>, so landmark navigation lists it. - One state at a time: the body shows the content, a skeleton, an empty state or an error, never two.
- Refreshing is not loading:
busykeeps the content and shows a spinner by the title. - The heading level follows the page outline (
headingLevel), never the visual size.
API reference
import { SectionCard } from '@rookcom/monarch-react/patterns';| Export | Kind | Rendering | Root class |
|---|---|---|---|
SectionCard | component | client | .mn-section-card- |
Renders <section aria-labelledby> and forwards its ref; native attributes pass through.
The header is a SectionHeader without children; title, description, meta, actions, link and headingLevel are passed to it.
States reuse LoadingState, EmptyState and ErrorState (compact). Pass empty for a custom empty state with actions.
Exported from @rookcom/monarch-react/patterns.
Generated from the TypeScript declarations of packages/react/src/components/layout/section-card.
Properties
SectionCard
A titled card for one section of a page or dashboard: header with actions and a "View all" link, body, optional footer, and built-in loading, empty and error states. Optionally collapsible.
| Prop | Type | Description |
|---|---|---|
title required | ReactNode | The section title, rendered as a heading that labels the section. |
actions | ReactNode | Buttons or a menu at the end of the header. |
busy | boolean | The content is refreshing in place: a spinner after the title and `aria-busy` on the section, while the current content stays visible. |
children | ReactNode | |
className | string | |
collapsible | boolean | The title becomes a disclosure button that shows and hides the body. |
defaultOpen | boolean | Initial expanded state (uncontrolled). Default `true`. |
description | ReactNode | A sentence under the title explaining the section. |
empty | ReactNode | Replaces the default empty state (for example an EmptyState with actions). |
emptyDescription | ReactNode | |
emptyTitle | ReactNode | Title of the default empty state. Default "Nothing here yet". |
errorDescription | ReactNode | |
errorTitle | ReactNode | Title of the error state. Default "This section didn’t load". |
flush | boolean | No body padding, for a table or list that runs edge to edge. |
footer | ReactNode | Content of the footer bar: secondary actions, a summary, a pager. |
headingId | string | Id for the heading. Generated when omitted. |
headingLevel | "h2" | "h3" | "h4" | "h5" | "h6" | Heading element. Default `h2`. |
icon | ReactNode | Decorative icon before the title. Hidden from assistive technology. |
id | string | |
link | ReactNode | A trailing "View all" link (SectionHeaderLink), after `actions`. |
loadingLabel | string | Announced while loading. Default "Loading". |
loadingVariant | LoadingStateVariant | Skeleton shape while loading. Default `rows`. |
meta | ReactNode | A short secondary line: a count, a date range, "Updated 5 min ago". |
onOpenChange | ((open: boolean) => void) | |
onRetry | (() => void) | Shows a retry button in the error state. |
open | boolean | Expanded state (controlled). Only with `collapsible`. |
retrying | boolean | The retry is in flight. |
state | SectionCardState | What the body shows: the children (`ready`, default), a skeleton (`loading`), an EmptyState (`empty`) or an ErrorState (`error`). Exposed as `data-state`. |
tone | "danger" | "default" | `danger` for a destructive zone (danger border and title). Exposed as `data-tone`. |
Variants
| Prop | Values | Description |
|---|---|---|
state | 'ready''loading''empty''error' | What the body shows. Exposed as data-state. |
tone | 'default''danger' | danger uses the danger border and title colour. Exposed as data-tone. |
flush | 'false''true' | Removes the body padding and adds a divider, for tables and lists. data-flush. |
collapsible | 'false''true' | The title becomes a disclosure button. data-collapsible, data-open. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Ready | data-state="ready" | Shows the children. |
| Loading | data-state="loading", aria-busy | A skeleton (loadingVariant) with a polite loadingLabel announcement. |
| Empty | data-state="empty" | empty, or a compact EmptyState from emptyTitle and emptyDescription. |
| Error | data-state="error" | A compact ErrorState with errorTitle, errorDescription and a retry button when onRetry is given. |
| Busy | aria-busy | Refreshing in place: a spinner after the title; content stays. |
| Collapsed | data-open absent | The body and footer are hidden; the header stays. |
Interaction behaviour
- Header actions and the "View all" link behave as usual.
- With
collapsible, activating the title toggles the body (uncontrolled withdefaultOpen, controlled withopenandonOpenChange). - The error state’s retry button calls
onRetry; showretryingwhile it runs.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Moves to the title toggle (when collapsible), header actions, link, then the body. |
| Enter / Space | On the title toggle, expands or collapses the section. |
Accessibility
- Role
- region (section with aria-labelledby)
- Accessible name
- The section title.
- The heading labels the section; set
headingIdto point other elements at it. - The collapsible toggle follows the APG disclosure pattern: a button inside the heading with
aria-expandedandaria-controls. - The icon is decorative and hidden from assistive technology.
- Loading and busy set
aria-busy; the loading label is announced politely.
Responsive behaviour
- Header actions wrap under the title when space runs out (SectionHeader).
- Below 480px the header, body and footer padding tighten.
- The footer wraps its content and keeps it end-aligned.
Theme support
- Surface, borders, footer and danger colours use Monarch semantic tokens, so every theme applies.
- The chevron rotation is instant with reduced motion.
Do and don't
Do
- Drive
statefrom the query, so every section has a loading, empty and error story. - Use
flushfor tables and edge-to-edge lists. - Give "View all" links text that makes sense alone ("View all tasks").
Don't
- Do not render your own spinner inside the body; use
state="loading"orbusy. - Do not nest SectionCards.
- Do not use it for settings with a save bar; SettingsSection has that built in.
Related components
- SectionHeader: The header it renders; use it alone for unboxed sections.
- Card: A plain surface without a title or states.
- SettingsSection: Settings rows with Save/Discard.
- EmptyState: The default empty body; pass your own in
empty. - ErrorState: The error body.
- LoadingState: The loading body.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | EmptyState, ErrorState, LoadingState, SectionHeader |
Testing coverage
- Is a region labelled by its heading, with icon, actions, link, body and footer; ref forwarding; Tab order.
- Shows loading (busy, announced), empty (default and custom) and error (retry) states instead of the children.
busykeeps the content and marks the section busy.- Collapsible uncontrolled: the title is a disclosure button; Enter and Space toggle the body and footer.
- Collapsible controlled with
open, anddefaultOpen={false}. - Exposes tone and flush; honours
headingLevelandheadingId. - Renders on the server.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/section-card; Cross-browser axe: /components/section-card |
Tests: packages/react/src/components/layout/section-card