Use this skill when building a DataList for item-focused layouts with row expansion, row selection, or rich per-item content — including expandedContent, onRowSelect, onRowExpand, variant (default/card), useLocalQuery, and empty/loading states. Do not use for bulk data comparison tables (see gamut-datatable), or for small static lists (see gamut-list).
Scanned 9/1/2026
Install to Claude Code
npx -y skills add Codecademy/gamut --skill gamut-datalist --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Gamut Datalist?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/codecademy-gamut-datalist)More formats (shields.io, HTML) on the badges page.
---
name: gamut-datalist
description: Use this skill when building a DataList for item-focused layouts with row expansion, row selection, or rich per-item content — including expandedContent, onRowSelect, onRowExpand, variant (default/card), useLocalQuery, and empty/loading states. Do not use for bulk data comparison tables (see gamut-datatable), or for small static lists (see gamut-list).
---
# Gamut DataList
Item-focused list for managing, engaging with, and expanding individual rows. Use when users interact with items — opening details, selecting for bulk actions, or viewing expanded layouts — rather than scanning and comparing data across rows.
Source: `@codecademy/gamut` — `packages/gamut/src/DataList/DataList.tsx`
See also: [`gamut-datatable`](../gamut-datatable/SKILL.md) — query-focused table for bulk data comparison. [`gamut-list`](../gamut-list/SKILL.md) — lower-level list primitives for fully custom layouts. [`gamut-accessibility`](../gamut-accessibility/SKILL.md) — ARIA and keyboard interaction. [`gamut-z-index`](../gamut-z-index/SKILL.md) — the `zIndex` values used by the sticky header row and `EmptyRows`/`emptyMessage` below.
Storybook: [Organisms / Lists & Tables / DataList](https://gamut.codecademy.com/?path=/docs-organisms-lists-tables-datalist--docs)
## Components
```tsx
import { DataList } from '@codecademy/gamut';
import { useLocalQuery } from '@codecademy/gamut';
```
| Symbol | Role |
| --------------- | ---------------------------------------------------------------------------------------- |
| `DataList` | Root component; supports expansion, selection, and card/default variants |
| `useLocalQuery` | Client-side hook for sort/filter state; spread its return value directly into `DataList` |
## When to use DataList
- Users **open, expand, or engage** with individual items (content libraries, assignment lists, course catalogs).
- Rows need **expandable detail panels** with rich layouts.
- Users need to **select rows** for bulk actions.
- Items contain **icons, graphics, or complex layouts** rather than plain metrics.
- Optional filtering or sorting across shared attributes is needed at the list level.
**Do not use DataList when:**
- The goal is to **compare data across rows** (scores, metrics, statuses in columns) → use [`DataTable`](../gamut-datatable/SKILL.md).
- The list is small, static, and needs fully custom row layouts → use [`List`](../gamut-list/SKILL.md) directly.
- You need a table with horizontal scrolling → use `DataTable` (DataList is not scrollable).
## Design principles
- **Engage with individual items**: design each row to support the action users take on it (open, launch, select, expand).
- **Customize items with rich content**: icons, progress indicators, graphics, and other atoms are appropriate within rows.
- **Keep item controls visible**: action controls should be on the right side of the row.
- **Place lists inside main containers**: avoid overflow by placing DataList in appropriately sized parent layouts.
- **Use DataTable for comparison-first designs**: if the design is primarily about scanning data between items, reach for DataTable instead.
## Variants
```tsx
<DataList variant="default" />
```
| `variant` | Use for |
| --------- | ---------------------------------------------------------------------------------- |
| `default` | Standard bordered rows; best for most item-management layouts |
| `card` | Rows with vertical gap; best for content that doesn't need to be visually adjacent |
## Props
| Prop | Type | Default | Notes |
| ----------------------- | ------------------------------------------------------ | ------------------- | ------------------------------------------------------------------------ |
| `id` | `string` | required | Unique ID for the list |
| `idKey` | `keyof Row` | required | Row identifier — must be a `string \| number` field |
| `rows` | `Row[]` | required | Data array |
| `columns` | `ColumnConfig<Row>[]` | required | Column definitions |
| `query` | `Query<Row>` | — | Current sort/filter state |
| `onQueryChange` | `OnQueryChange<Row>` | — | Called when sort or filter changes |
| `selected` | `Row[IdKey][]` | — | Array of selected row IDs |
| `onRowSelect` | `RowStateChange<'select' \| 'select-all', Row[IdKey]>` | — | Called on row or select-all toggle |
| `expanded` | `Row[IdKey][]` | — | Array of expanded row IDs |
| `onRowExpand` | `RowStateChange<'expand', Row[IdKey]>` | — | Called when a row is expanded or collapsed |
| `expandedContent` | `ExpandRow<Row>` | — | Render function for expanded row content; receives `{ row, onCollapse }` |
| `variant` | `'default' \| 'card'` | `'default'` | Row visual style |
| `header` | `boolean` | — | Whether to show a header row |
| `hideSelectAll` | `boolean` | `false` | Hides the select-all checkbox in the header |
| `loading` | `boolean` | `false` | Replaces row content with shimmer placeholders |
| `spacing` | `'condensed' \| 'normal'` | `'condensed'` | Row padding |
| `emptyMessage` | `ReactNode` | default empty state | Rendered when `rows` is empty |
| `disableContainerQuery` | `boolean` | `false` | Falls back to media queries instead of container queries |
> DataList always sets `scrollable={false}`. For a horizontally scrollable table, use `DataTable`.
## ColumnConfig
Identical to DataTable — see [`gamut-datatable`](../gamut-datatable/SKILL.md#columnconfig) for the full table.
## Basic usage
```tsx
import { DataList, useLocalQuery } from '@codecademy/gamut';
const columns = [
{ key: 'title', header: 'Title', size: 'md', type: 'header', fill: true },
{ key: 'status', header: 'Status', size: 'sm' },
];
const MyList = ({ data }) => {
const queryData = useLocalQuery({ idKey: 'id', rows: data, columns });
return <DataList id="my-list" idKey="id" columns={columns} {...queryData} />;
};
```
## Expandable rows
Pass `expandedContent`, `expanded`, and `onRowExpand` together. The `expandedContent` render function receives `{ row, onCollapse }` — call `onCollapse` from within the expanded panel to close it programmatically.
```tsx
const [expanded, setExpanded] = useState<string[]>([]);
const handleExpand = ({ type, payload: { rowId, toggle } }) => {
if (type === 'expand') {
setExpanded((prev) =>
toggle ? prev.filter((id) => id !== rowId) : [...prev, rowId]
);
}
};
<DataList
id="expandable-list"
idKey="id"
columns={columns}
rows={data}
expanded={expanded}
onRowExpand={handleExpand}
expandedContent={({ row, onCollapse }) => (
<Box p={16}>
<Text>{row.title}: additional details here</Text>
<TextButton onClick={onCollapse}>Collapse</TextButton>
</Box>
)}
/>;
```
## Selectable rows
Pass `selected` and `onRowSelect` to enable checkboxes. The callback receives `{ type, payload: { rowId, toggle } }` where `type` is `'select'` or `'select-all'`.
```tsx
const [selected, setSelected] = useState<string[]>([]);
const handleSelect = ({ type, payload: { rowId, toggle } }) => {
if (type === 'select-all') {
setSelected((prev) => (prev.length > 0 ? [] : data.map((r) => r.id)));
} else if (type === 'select') {
setSelected((prev) =>
toggle ? prev.filter((id) => id !== rowId) : [...prev, rowId]
);
}
};
<DataList
id="selectable-list"
idKey="id"
columns={columns}
rows={data}
selected={selected}
onRowSelect={handleSelect}
/>;
```
To hide row checkboxes entirely, omit `onRowSelect` and `selected`. To hide only the select-all checkbox, pass `hideSelectAll`.
## Expansion + selection combined
Both can be active at once — pass all four props together.
```tsx
<DataList
id="full-list"
idKey="id"
columns={columns}
rows={data}
selected={selected}
onRowSelect={handleSelect}
expanded={expanded}
onRowExpand={handleExpand}
expandedContent={({ row }) => <ExpandedDetail row={row} />}
/>
```
## Sorting and filtering with useLocalQuery
`useLocalQuery` handles client-side sort and filter state. Spread its return value directly into `DataList`. Mark columns as `sortable` or provide `filters` in the column config.
```tsx
const columns = [
{ key: 'title', header: 'Title', size: 'md', sortable: true },
{
key: 'type',
header: 'Type',
size: 'sm',
filters: ['Course', 'Path', 'Project'],
},
];
const queryData = useLocalQuery({ idKey: 'id', rows: data, columns });
<DataList id="my-list" idKey="id" columns={columns} {...queryData} />;
```
For server-side filtering or pagination, manage `query` and `onQueryChange` externally instead of using `useLocalQuery`.
## Empty state
DataList shows a default empty state when `rows` is empty. Override with `emptyMessage`. The default and the example below both use `zIndex={1}` to sit above table rows without a token — see [`gamut-z-index`](../gamut-z-index/SKILL.md) before changing it.
```tsx
<DataList
id="my-list"
idKey="id"
columns={columns}
rows={[]}
emptyMessage={
<Box as="tbody" height="100%" width="100%">
<FlexBox
as="tr"
height="inherit"
position="absolute"
width="inherit"
zIndex={1}
>
<FlexBox
as="th"
center
column
left="calc(50% - 115px)"
p={16}
position="sticky"
top="calc(50% - 115px)"
width="fit-content"
>
<Text>No items yet.</Text>
</FlexBox>
</FlexBox>
</Box>
}
/>
```
## Loading state
Pass `loading` to replace row content with shimmer placeholders while data fetches.
```tsx
<DataList id="my-list" idKey="id" columns={columns} rows={[]} loading />
```
## Container queries
DataList uses CSS container queries by default. Disable only when the list lives in a constrained container or you are managing your own responsive logic.
```tsx
import { Box, DataList } from '@codecademy/gamut';
<Box width={280}>
<DataList
disableContainerQuery
id="sidebar-list"
idKey="id"
rows={data}
columns={columns}
/>
</Box>;
```
## Accessibility
- DataList renders as a semantic `<table>` via the underlying `List as="table"`.
- Expanded rows receive an `aria-live="polite"` region automatically — do not add a duplicate live region.
- The expanded content region is labeled by the row's header column text; provide meaningful header column values.
- Selection checkboxes are grouped as a checkbox list; the select-all checkbox is in the `<thead>`.
- For custom `emptyMessage`, use `tbody > tr > th` structure for valid table semantics.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!