Source: https://monarchui.dev/components/section-card/

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.

@rookcom/monarch-react/patterns

Component

## Header actions, "View all" and footer

## Usage examples

### Loading, empty and error states

### Collapsible and danger tone

## 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: `busy` keeps the content and shows a spinner by the title.
- The heading level follows the page outline (`headingLevel`), never the visual size.

## API reference

```tsx
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 with `defaultOpen`, controlled with `open` and `onOpenChange`).
- The error state’s retry button calls `onRetry`; show `retrying` while 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 `headingId` to point other elements at it.
- The collapsible toggle follows the APG disclosure pattern: a button inside the heading with `aria-expanded` and `aria-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 `state` from the query, so every section has a loading, empty and error story.
- Use `flush` for 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"` or `busy`.
- Do not nest SectionCards.
- Do not use it for settings with a save bar; SettingsSection has that built in.

## Related components

- [SectionHeader](https://monarchui.dev/components/section-header/): The header it renders; use it alone for unboxed sections.
- [Card](https://monarchui.dev/components/card/): A plain surface without a title or states.
- [SettingsSection](https://monarchui.dev/components/settings-section/): Settings rows with Save/Discard.
- [EmptyState](https://monarchui.dev/components/empty-state/): The default empty body; pass your own in `empty`.
- [ErrorState](https://monarchui.dev/components/error-state/): The error body.
- [LoadingState](https://monarchui.dev/components/loading-state/): The loading body.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [EmptyState](https://monarchui.dev/components/empty-state/), [ErrorState](https://monarchui.dev/components/error-state/), [LoadingState](https://monarchui.dev/components/loading-state/), [SectionHeader](https://monarchui.dev/components/section-header/) |

## 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.
- `busy` keeps 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`, and `defaultOpen={false}`.
- Exposes tone and flush; honours `headingLevel` and `headingId`.
- 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`

## All example code

### Header actions, "View all" and footer

```tsx
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>
```

### Loading, empty and error states

```tsx
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

```tsx
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>
```
