DashboardGrid

Organism
A 12-column widget board for overview screens. Each widget declares a span in twelfths, and the grid measures its OWN width (never the window) to pick a tier: a wide container honors every span, a narrower one reflows widgets to their narrowSpan, and a phone-sized one stacks them full width.

Playground

Every control sets a real prop of DashboardGridProps. The JSX below updates as you go.
Revenue
Signups
Slowest endpoints
Props
unlocked
boolean
compact
boolean
JSX · DashboardGridProps
<DashboardGrid />

Variants

4 examples from src/organisms/dashboard-grid/dashboard-grid.md, each rendered live from its own JSX.
Revenue
Signups
Customize mode
unlocked is the app's switch, never the grid's: turning it on paints the cell affordance, shows a grip on every widget, and makes the board reorderable. The grips stay visible for as long as the mode lasts, because React Native has no portable hover event and an edit mode already announces itself. defaultOrder hands the order to the grid, which applies each drop itself and reports the result through onOrderChange, so a bare board is interactive out of the box.
<DashboardGrid
unlocked
items={[
{ id: "revenue", span: 6, title: "Revenue", content: <Chart title="Revenue" data={[{ label: "Mon", value: 12 }, { label: "Tue", value: 18 }]} /> },
{ id: "signups", span: 6, title: "Signups", content: <BarList title="Signups" items={[{ label: "Web", value: 82 }, { label: "iOS", value: 64 }]} /> },
]}
/>
Signups
Revenue
Controlled order
The primary path: the app owns the id array and persists it through its own API, applying every onOrderChange. Ids the widget list no longer holds are ignored and widgets missing from the order are appended, so a layout saved before a release keeps working.
<Stateful initial={["signups", "revenue"]}>
{(order, setOrder) => (
<DashboardGrid
unlocked
order={order}
onOrderChange={setOrder}
items={[
{ id: "revenue", span: 6, title: "Revenue", content: <Chart title="Revenue" data={[{ label: "Mon", value: 12 }, { label: "Tue", value: 18 }]} /> },
{ id: "signups", span: 6, title: "Signups", content: <BarList title="Signups" items={[{ label: "Web", value: 82 }, { label: "iOS", value: 64 }]} /> },
]}
/>
)}
</Stateful>
Revenue
Signups
Errors
Narrow spans
A wide board is not a narrow one cut in half, so a widget can declare the width it wants when the grid reflows. Without narrowSpan, a widget of five columns or more takes the full board and anything smaller pairs up at half; every widget stacks full width at phone sizes.
<DashboardGrid
items={[
{ id: "revenue", span: 8, narrowSpan: 12, title: "Revenue", content: <Chart title="Revenue" data={[{ label: "Mon", value: 12 }, { label: "Tue", value: 18 }]} /> },
{ id: "signups", span: 4, narrowSpan: 6, title: "Signups", content: <BarList title="Signups" items={[{ label: "Web", value: 82 }]} /> },
{ id: "errors", span: 4, narrowSpan: 6, title: "Errors", content: <BarList title="Errors" items={[{ label: "5xx", value: 12 }]} /> },
]}
/>
Revenue
Signups
Errors
Compact
The density axis tightens the space between cells.
<DashboardGrid
compact
items={[
{ id: "revenue", span: 4, title: "Revenue", content: <BarList title="Revenue" items={[{ label: "Pro", value: 82 }]} /> },
{ id: "signups", span: 4, title: "Signups", content: <BarList title="Signups" items={[{ label: "Web", value: 64 }]} /> },
{ id: "errors", span: 4, title: "Errors", content: <BarList title="Errors" items={[{ label: "5xx", value: 12 }]} /> },
]}
/>

States

Hover, focus and press are pinned so they can be compared side by side; the rest are real props.
Revenue
Signups
Slowest endpoints
default

Anatomy

Revenue
Signups
Slowest endpoints
1
Grid
2
Widget
3
Drag handle
Dimmed parts appear only in some states or variants.

Props

9 props on DashboardGridProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
6 props
items
DashboardWidget[]
–
The widgets to lay out, and the canonical collection prop. Their array order is the fallback order: any widget whose id is missing from the active order is appended, so adding one to a release never breaks a saved layout.
order
string[]
–
CONTROLLED widget order, as ids. The primary path: the consuming app owns the array and persists it through its own API, applying each onOrderChange. Ids the widget list does not hold are ignored, and widgets missing from it are appended, so a stored order survives a release that adds or drops a widget.
defaultOrder
string[]
–
Initial order for UNCONTROLLED use; the grid then applies each drop itself. Defaults to the items array order.
onOrderChange
(order: string[]) => void
–
Fired with the full, reconciled next order after every drop that changes it, in BOTH the controlled and the uncontrolled mode.
unlocked
boolean
false
Customize mode: cells gain the edit affordance and a drag grip, and the board becomes reorderable. The grid never flips this itself; the app owns the "Done" / "Customize" control. Locked (the default) the drag provider is not mounted at all.
storageKey
string
–
Web-only convenience for the UNCONTROLLED path: seed the initial order from browser storage under this key and write each drop back to it. Ignored when order is passed (the controlled order owns persistence). On native there is no storage, so the order is session-only there; clearStoredDashboardOrder forgets a saved layout. A server-rendered board renders defaultOrder first and adopts the saved layout in the commit right after hydration, so React always hydrates against the markup the server actually sent.
Density axis
3 props
compact
boolean
false
testID
string
–
E2E hook forwarded to the root element.
style
StyleProp<ViewStyle>
–
Outer layout composition only (width/flex within a parent), never a restyle hook.

Accessibility and keyboard

Keyboard
Space
Lifts or drops the focused card
Arrow keys
Moves a lifted card
Escape
Cancels the move
Semantics
No role of its own in source; it reads as the content it renders.
From the docs
Cells render bare, because a widget arrives with its own surface (a Card, a Chart, a Stats row) and a second frame around it would only double up. A widget's title is its accessible name rather than a header the grid paints: customize mode uses it to name the widget's drag grip and its drop zone.
Locked, it is a plain static grid and no drag machinery is mounted at all; unlocked turns on customize mode, where every cell gains a grip and the board reorders by pointer, keyboard, or screen reader through the kit's own drag-and-drop. The order is a plain array of ids, controlled through order and onOrderChange so the app can persist it, or left to the grid with defaultOrder.

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
Revenue
Signups
Errors
Web
control 10 · field 10 · card 16 · Instrument Sans
Revenue
Signups
Errors
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
Revenue
Signups
Errors
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

<DashboardGrid
items={[
{ id: "revenue", span: 8, title: "Revenue", content: <Chart title="Revenue" data={[{ label: "Mon", value: 12 }, { label: "Tue", value: 18 }, { label: "Wed", value: 15 }, { label: "Thu", value: 22 }]} /> },
{ id: "signups", span: 4, title: "Signups", content: <BarList title="Signups" items={[{ label: "Web", value: 82 }, { label: "iOS", value: 64 }]} /> },
{ id: "endpoints", span: 12, title: "Slowest endpoints", content: <BarList title="Slowest endpoints" items={[{ label: "/api/search", value: 240 }, { label: "/api/feed", value: 180 }]} /> },
]}
/>
Cells render bare, because a widget arrives with its own surface (a Card, a Chart, a Stats row) and a second frame around it would only double up. A widget's title is its accessible name rather than a header the grid paints: customize mode uses it to name the widget's drag grip and its drop zone.
Locked, it is a plain static grid and no drag machinery is mounted at all; unlocked turns on customize mode, where every cell gains a grip and the board reorders by pointer, keyboard, or screen reader through the kit's own drag-and-drop. The order is a plain array of ids, controlled through order and onOrderChange so the app can persist it, or left to the grid with defaultOrder.
The board takes its widgets through items, the collection prop every other collection-taking component in the kit uses.

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--fd
--ft
--ln
--sf
--tx2

Related components