@codecademy/gamut

68.6.168.6.2-alpha.671e56.0
agent-tools/skills/gamut-list/SKILL.md
+agent-tools/skills/gamut-list/SKILL.mdNew file
+273
Index: package/agent-tools/skills/gamut-list/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-list/SKILL.md
+++ package/agent-tools/skills/gamut-list/SKILL.md
@@ -0,0 +1,273 @@
+---
+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` — [List.tsx](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/List/List.tsx)
+
+See also: [`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
+
+- Displaying repetitive content where individual rows may contain interactive elements, metrics, or controls — use List, not Card.
+- Comparing data across rows — 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.