Widget
A thing on a dashboard: a title, a card, and a line about it underneath.
Overview
A thing on a dashboard: a title, a card, and a line about it underneath.
First example
<Widget><Widget.Header><Widget.Heading><Widget.Title>Tokens consommés</Widget.Title><Widget.Description>30 derniers jours</Widget.Description></Widget.Heading><Chart.Legend labels={['Entrée', 'Sortie']} /></Widget.Header><Widget.Content><LineChart data={rows} xKey="jour" yKeys={['entree', 'sortie']} /></Widget.Content><Widget.Footer>Mis à jour il y a 2 minutes</Widget.Footer></Widget>
Anatomy
<Widget>
<Widget.TextSlot />
<Widget.ViewSlot />
</Widget>
Usage
<Widget>
<Widget.Header>
<Widget.Heading>
<Widget.Title>Tokens consommés</Widget.Title>
<Widget.Description>30 derniers jours</Widget.Description>
</Widget.Heading>
<Chart.Legend labels={['Entrée', 'Sortie']} />
</Widget.Header>
<Widget.Content>
<LineChart data={rows} xKey="jour" yKeys={['entree', 'sortie']} />
</Widget.Content>
<Widget.Footer>Mis à jour il y a 2 minutes</Widget.Footer>
</Widget>
Root props
WidgetProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| size | Size | undefined | No | The padding, the gaps and the corner. Never a height — a widget is as tall as its content. |
| radius | RadiusKey | undefined | No | The frame's corner. The card's follows it, one step in. |
| isElevated | boolean | undefined | No | Whether the card is lifted off the frame. On by default: the soft frame stays flat against the page and the shadow is what separates the card from it. There is no variant — a widget has one look. |
| style | StyleProp<ViewStyle> | No | — |
| children | ReactNode | No | — |
Slots
WidgetTextSlotProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
WidgetViewSlotProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
Variants, sizes and colour
No variant
A widget has one look — there is no variant, and no primary/secondary/tertiary. The
frame is always defaultSoft. The card is surface in light — white against a near-white
frame, so the step up is obvious — and surfaceSecondary in dark, where surface would
land on top of what defaultSoft composites to and vanish (and dark mode drops the surface
shadow, so colour is the only separation left). What moves is structural:
size— the padding, the gaps, the corner and the type.radius— the frame's corner (the card's follows it).isElevated— whether the card is lifted off the frame.
Size
size moves the padding, the gaps, the corner and the type — never a height. A widget
is as tall as what is in it.
| size | frame padding (x / y) | card padding | corner | title |
|---|---|---|---|---|
xs | 2 / 3 | 2 | xl | sm |
sm | 2.5 / 3.5 | 2.5 | 2xl | md |
md | 3 / 4 | 3 | 2xl | lg |
lg | 3.5 / 5 | 3.5 | 3xl | xl |
The frame's side inset is one step tighter than its vertical one: the card carries most of the width, so a wide band on either side of it is margin the frame does not need. On the sides it comes out level with the card's own inset, which keeps the horizontal rhythm even from the frame edge to the content; the vertical band stays wider, and is what the card's corner is derived from.
Accessibility
Widget.Title is an accessibilityRole="header", which is what a screen reader jumps
between — so a dashboard of widgets is navigable by their titles. Pass your own
accessibilityRole to override it, on a widget whose title is not a heading.
The root has no role of its own: a widget is a grouping, and what is inside it carries the semantics.
Migration from legacy
Implementation notes
It is a card held in a soft frame
That is the whole of it, and it is worth stating plainly because a widget and a card look alike in a screenshot.
A card puts its content flush on its own ground: the header, the body and the footer are all the same surface, and what separates them is space.
A widget is a quiet defaultSoft ground with no border. The header and the footer sit
straight on it, and Widget.Content is the one card — one step up from the frame
(surface in light, surfaceSecondary in dark; see No variant). What is
inside the card reads as the thing being shown; the title and the timestamp around it, on
the soft ground, read as the frame's own labels — not part of the thing.
Reach for a card when the content is the card's content. Reach for a widget when the frame is a label around something with its own edges.
The card's corner is derived, not chosen
An inner corner is the outer one less the gap between them — here the frame's vertical padding, the band above and below the card that a near-full-width card visibly sits within:
cardRadius = theme.radius[radius] − theme.spacing(paddingY)
Two arcs that do not follow that rule run at different rates, and the inset stops reading as a card the frame is holding and starts reading as a sticker laid on it. It is the one thing this component's shape depends on, so it is not a prop.
It is clamped at zero, because a large padding under a small corner would otherwise ask for
a negative radius — an xs widget at radius="sm" has a square card, correctly.
A radius prop therefore moves both corners. That is what the forty size × radius
compounds in the recipe are for: an axis sees only its own prop, and this value needs two.
`isElevated` is on by default
The soft frame stays flat against the page; the shadow lands on the card, and that is what separates it from the frame it sits in. A widget is one of several on a dashboard, and the raised card is what makes each one read as its own object.
In dark mode there is no shadow to land — theme.shadows.surface is empty by design —
so the card leans entirely on the colour step, which is why it uses surfaceSecondary
there rather than surface.
<Widget isElevated={false}>
Flat, the card falls back on that colour step alone — enough on most themes, and the right call when the widget is already inside a bordered container that does the separating.