Use this skill when building list or table layouts with List, ListRow, ListCol, and TableHeader — including variant and spacing selection, expandable row patterns, ordered/table layouts, and the rule that a list of disclosure-style items must use List's expandable row pattern (not multiple Disclosure components). See gamut-accessibility for ARIA and focus guidance.
Installs into .claude/skills of the current project.
Are you the author of Gamut List?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/codecademy-gamut-list)
---
name: gamut-list
description: Use this skill when building list or table layouts with List, ListRow, ListCol, and TableHeader — including variant and spacing selection, expandable row patterns, ordered/table layouts, and the rule that a list of disclosure-style items must use List's expandable row pattern (not multiple Disclosure components). See gamut-accessibility for ARIA and focus guidance.
---
# Gamut List
Structured, repeating layouts built from `List`, `ListRow`, `ListCol`, and `TableHeader`. Colors, borders, and spacing are wired through the `variant` and `spacing` props — consumers do not override these with raw CSS values.
Source: `@codecademy/gamut` — `packages/gamut/src/List/List.tsx`
See also: [`gamut-datatable`](../gamut-datatable/SKILL.md) — use instead of List when data needs sorting, filtering, or query state. [`gamut-datalist`](../gamut-datalist/SKILL.md) — use instead of List when rows need expansion or selection. [`gamut-accessibility`](../gamut-accessibility/SKILL.md) — ARIA, focus, and keyboard interaction rules. [`gamut-layout`](../gamut-layout/SKILL.md) — spacing tokens and system props.
Storybook:
- [Organisms / Lists & Tables / List](https://gamut.codecademy.com/?path=/docs-organisms-lists-tables-list-list--docs)
- [ListRow](https://gamut.codecademy.com/?path=/docs-organisms-lists-tables-list-listrow--docs)
- [ListCol](https://gamut.codecademy.com/?path=/docs-organisms-lists-tables-list-listcol--docs)
## Components
```tsx
import { List, ListRow, ListCol, TableHeader } from '@codecademy/gamut';
```
| Component | Role |
| ------------- | ----------------------------------------------------------------------- |
| `List` | Root wrapper; sets `variant`, `spacing`, `as`, and context for children |
| `ListRow` | Single row; handles expandable content and click interactions |
| `ListCol` | Column cell; controls `type`, `size`, `fill`, and justification |
| `TableHeader` | Sticky header row; use only with `List as="table"` |
## When to use List
List is for **fully custom, manually composed layouts**. Reach for a higher-level component first:
- Data that needs **sorting, filtering, or query state** → use [`DataTable`](../gamut-datatable/SKILL.md) or [`DataList`](../gamut-datalist/SKILL.md) instead of wiring these up manually in List.
- Rows that need **expansion or row selection** → use [`DataList`](../gamut-datalist/SKILL.md).
Use List directly when:
- You need fully custom row/column composition that DataTable or DataList cannot accommodate.
- Displaying repetitive content where individual rows may contain interactive elements, metrics, or controls — use List, not Card.
- Comparing data across rows with a fully custom layout — use `variant="table"` (or `as="table"`) rather than a plain `<table>`.
- Needing numbered rows — use `as="ol"`.
- **Needing multiple expandable/disclosure-style items** — use List's expandable row pattern (see [Expandable rows](#expandable-rows)), not multiple standalone `Disclosure` components.
## Variants
```tsx
<List variant="default" />
```
| `variant` | Use for |
| --------- | ------------------------------------------------------------------------------------------------------- |
| `default` | Rows with abstract content (buttons, custom renders); bordered, no gutter |
| `table` | Metrics or comparable data; alternating row backgrounds |
| `card` | Content that doesn't need to be adjacent (e.g. curriculum progress); bordered rows with vertical gutter |
| `block` | Feature-forward designs or page scaffolding; always on a colored background |
| `plain` | Minimal styling — no borders or backgrounds; apply custom styles per row via Emotion `styled` |
## Spacing
```tsx
<List spacing="condensed" />
```
| `spacing` | Use for |
| ----------- | -------------------------------------------- |
| `normal` | Mixed content that needs room for components |
| `condensed` | Default choice; reduced padding between rows |
| `compact` | Tightest layout; data-dense views |
## `as` prop
Default is `ul`. Pass `as="ol"` for numbered rows or `as="table"` for semantic table output.
```tsx
// ordered list — always include one ListCol with type="header" so numbering renders correctly
<List as="ol">
<ListRow>
<ListCol type="header">Step one</ListCol>
<ListCol>Details</ListCol>
</ListRow>
</List>
// semantic table with sticky header
<List as="table">
<TableHeader>
<ListCol columnHeader>Name</ListCol>
<ListCol columnHeader>Role</ListCol>
</TableHeader>
<ListRow>
<ListCol type="header">Worf</ListCol>
<ListCol>Lieutenant Commander</ListCol>
</ListRow>
</List>
```
## Key props
### List
| Prop | Type | Default | Effect |
| ----------------------- | ------------------------------------------------------ | ----------- | -------------------------------------------------------- |
| `variant` | `'default' \| 'table' \| 'card' \| 'block' \| 'plain'` | `'default'` | Row styling |
| `spacing` | `'normal' \| 'condensed' \| 'compact'` | `'normal'` | Row padding |
| `as` | `'ul' \| 'ol' \| 'table'` | `'ul'` | Rendered element and semantic meaning |
| `header` | `React.ReactNode` | — | Node rendered above the row list |
| `emptyMessage` | `React.ReactNode` | — | Shown when children is empty |
| `loading` | `boolean` | — | Shows placeholder while data loads |
| `scrollable` | `boolean` | `false` | Enables horizontal scroll with sticky first column |
| `shadow` | `boolean` | `false` | Right-side shadow when scrollable content overflows |
| `disableContainerQuery` | `boolean` | `false` | Falls back to media queries instead of container queries |
| `rowBreakpoint` | `'xs' \| 'sm' \| 'md'` | `'xs'` | Breakpoint at which columns stack |
### ListRow
| Prop | Type | Notes |
| -------------------------- | ----------------------- | ----------------------------------------------------------------------- |
| `expanded` | `boolean` | Required when `renderExpanded` is set |
| `renderExpanded` | `() => React.ReactNode` | Content revealed when `expanded` is true; animates in/out |
| `expandedRowAriaLabel` | `string` | `aria-label` for the revealed region |
| `keepSpacingWhileExpanded` | `boolean` | Maintains row spacing while content is expanded |
| `onClick` | mouse event handler | Makes the full row interactive (adds `role="button"`, keyboard support) |
### ListCol `type`
| `type` | Use for |
| --------------- | ------------------------------------------------------ |
| `header` | Primary label column; sticky when `List` is scrollable |
| `content` | Secondary text content |
| `control` | Action controls (buttons, menus) |
| `expand` | Expanded content area |
| `expandControl` | The toggle button column (no right-padding) |
| `select` | Checkbox / selection column |
### ListCol `size`
`'content'` (default, fits content) | `'sm'` | `'md'` | `'lg'` | `'xl'`
Pass `fill` to grow a column to fill remaining space.
## Expandable rows
List provides two patterns for rows that reveal content. Both animate open/closed via framer-motion.
### Expand on button click
Use `ExpandControl` in a `type="expandControl"` column to toggle a specific row.
```tsx
const [isExpanded, setExpanded] = useState(false);
<ListRow
expanded={isExpanded}
renderExpanded={() => <Text>Revealed content</Text>}
expandedRowAriaLabel="Row details"
>
<ListCol type="header">Row label</ListCol>
<ListCol type="content">Secondary detail</ListCol>
<ListCol type="expandControl">
<ExpandControl
expanded={isExpanded}
onExpand={() => setExpanded(!isExpanded)}
/>
</ListCol>
</ListRow>;
```
### Expand on row click
Pass `onClick` to `ListRow` to make the entire row the toggle target. The row receives `role="button"` and keyboard Enter support automatically.
```tsx
const [isExpanded, setExpanded] = useState(false);
<ListRow
expanded={isExpanded}
onClick={() => setExpanded(!isExpanded)}
renderExpanded={() => <Text>Revealed content</Text>}
>
<ListCol type="header">Row label</ListCol>
<ListCol type="content">Secondary detail</ListCol>
<ListCol type="control">
<Rotation rotated={isExpanded}>
<ArrowChevronDownIcon color="text-disabled" />
</Rotation>
</ListCol>
</ListRow>;
```
## List of disclosure-style items
When you need multiple expandable items (an FAQ, an accordion, a list of sections), **use List's expandable row pattern above — do not render multiple standalone `Disclosure` components**.
`Disclosure` is designed for a single, isolated expandable container. For two or more expandable items, the correct pattern is `List` + `ListRow` with `expanded` / `renderExpanded`:
```tsx
// correct — list of expandable items
<List variant="default" spacing="normal">
{items.map(({ id, title, body }) => {
const [isExpanded, setExpanded] = useState(false);
return (
<ListRow
key={id}
expanded={isExpanded}
renderExpanded={() => <Box p={16}>{body}</Box>}
expandedRowAriaLabel={`${title} details`}
>
<ListCol type="header" fill>{title}</ListCol>
<ListCol type="expandControl">
<ExpandControl
expanded={isExpanded}
onExpand={() => setExpanded(!isExpanded)}
/>
</ListCol>
</ListRow>
);
})}
</List>
// wrong — multiple Disclosure components
<Disclosure heading="Item 1" body="..." />
<Disclosure heading="Item 2" body="..." />
```
## Empty state and loading
```tsx
// custom empty message
<List emptyMessage={<Text>No results found.</Text>}>
{rows}
</List>
// loading placeholder
<List loading>{rows}</List>
```
## Scrollable layout
Use `scrollable` when a list has many columns and collapsing would lose information. The first (`type="header"`) column sticks to the left.
```tsx
<List scrollable shadow>
<TableHeader>
<ListCol type="header" columnHeader>
Name
</ListCol>
<ListCol size="md" columnHeader>
Score
</ListCol>
<ListCol size="md" columnHeader>
Progress
</ListCol>
</TableHeader>
{rows}
</List>
```
## Container queries
By default `List` uses CSS container queries for responsive column stacking. Disable only when:
- The `List` lives in a container narrower than its breakpoint
- You are managing your own responsive logic
- The list has very few rows and container query overhead is unwanted
```tsx
<List disableContainerQuery spacing="condensed">
{rows}
</List>
```
## Accessibility
- `ListRow` sets `aria-live="polite"` automatically when `renderExpanded` is present — do not add a duplicate live region.
- Pass `expandedRowAriaLabel` to label the revealed `role="region"`.
- `onClick` on `ListRow` adds `role="button"` and keyboard Enter support automatically.
- For sortable columns pass `aria-sort` on the relevant `ListCol`.
- Ordered lists (`as="ol"`) must include at least one `ListCol type="header"` per row for numbering to render correctly.