@castui/cast-ui 4.9.0 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -1
- package/dist/components/Accordion/Accordion.js +4 -3
- package/dist/components/Backdrop/Backdrop.js +7 -8
- package/dist/components/BottomSheet/BottomSheet.js +12 -14
- package/dist/components/Drawer/Drawer.js +12 -14
- package/dist/components/Progress/Progress.js +5 -4
- package/dist/components/Skeleton/Skeleton.js +11 -13
- package/dist/components/SpeedDial/SpeedDial.js +3 -5
- package/dist/components/Spinner/Spinner.js +6 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -3
- package/dist/theme/ThemeContext.d.ts +12 -1
- package/dist/theme/ThemeContext.js +5 -2
- package/dist/theme/applyCastTheme.d.ts +32 -2
- package/dist/theme/applyCastTheme.js +72 -0
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.js +3 -1
- package/dist/theme/useMotion.d.ts +32 -0
- package/dist/theme/useMotion.js +55 -0
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.js +12 -1
- package/dist/tokens/motion.d.ts +196 -0
- package/dist/tokens/motion.js +175 -0
- package/package.json +2 -1
- package/skills/cast-ui-component/SKILL.md +834 -0
- package/skills/cast-ui-docs-site/SKILL.md +114 -0
- package/skills/cast-ui-usage/SKILL.md +587 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cast-ui-usage
|
|
3
|
+
description: >-
|
|
4
|
+
How to USE the @castui/cast-ui React Native component library in an app.
|
|
5
|
+
Covers the design language (intent / prominence / size, density, light/dark,
|
|
6
|
+
brand colour overrides), ThemeProvider setup, loading fonts, theming from a
|
|
7
|
+
Figma cast-theme.json via applyCastTheme, and every component with its props
|
|
8
|
+
and usage. Use this whenever writing or reviewing app code that imports from
|
|
9
|
+
@castui/cast-ui: picking a component, wiring ThemeProvider, applying a brand
|
|
10
|
+
theme, fixing why icons or fonts render wrong, or choosing the right
|
|
11
|
+
intent/prominence/size. This is a consumer guide for building apps with the
|
|
12
|
+
library, NOT a guide to building or extending the library itself.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Using Cast UI
|
|
16
|
+
|
|
17
|
+
Cast UI (`@castui/cast-ui`) is a cross-platform React Native component library.
|
|
18
|
+
The same components run on iOS, Android, and the web (through react-native-web).
|
|
19
|
+
It has zero runtime dependencies (only `react` and `react-native` as peers), and
|
|
20
|
+
every colour, size, and spacing value comes from design tokens, so the whole
|
|
21
|
+
look is themeable at runtime with no rebuild.
|
|
22
|
+
|
|
23
|
+
This skill is for building an app *with* the library. If the task is adding a new
|
|
24
|
+
component to the library or editing its Figma kit, that is a different job and
|
|
25
|
+
this skill does not cover it.
|
|
26
|
+
|
|
27
|
+
Browse every component live in the hosted Storybook:
|
|
28
|
+
https://main--6990f00d7b8682c18d2ed5f3.chromatic.com
|
|
29
|
+
|
|
30
|
+
The documentation site has live editable examples, full prop tables, patterns,
|
|
31
|
+
templates, and the motion system: https://connagh.github.io/cast-ui/
|
|
32
|
+
|
|
33
|
+
## Install and the one rule
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @castui/cast-ui
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Peer deps: `react` (>=18), `react-native` (>=0.72). Nothing else is installed.
|
|
40
|
+
|
|
41
|
+
**The one rule: wrap your app in `ThemeProvider` once, near the root.** Every
|
|
42
|
+
component reads its colours and spacing from that context. A component rendered
|
|
43
|
+
outside a provider falls back to defaults, but always mount one so density,
|
|
44
|
+
colour mode, and brand colours work.
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { ThemeProvider, Button } from '@castui/cast-ui';
|
|
48
|
+
|
|
49
|
+
export function App() {
|
|
50
|
+
return (
|
|
51
|
+
<ThemeProvider>
|
|
52
|
+
<Button intent="brand" prominence="bold" onPress={save}>
|
|
53
|
+
Save changes
|
|
54
|
+
</Button>
|
|
55
|
+
</ThemeProvider>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## The design language
|
|
61
|
+
|
|
62
|
+
Most components share three style props. Learn these once and they apply
|
|
63
|
+
everywhere. They are the vocabulary the Figma kit and the code agree on.
|
|
64
|
+
|
|
65
|
+
- **`intent`** is what something means: `'neutral'` (default grey), `'brand'`
|
|
66
|
+
(your primary colour, blue out of the box), or `'danger'` (red, for
|
|
67
|
+
destructive or error states). Pick by meaning, not by colour.
|
|
68
|
+
- **`prominence`** is how visually heavy it is: `'default'` (outlined),
|
|
69
|
+
`'bold'` (filled, the strongest), or `'subtle'` (ghost, no border or fill
|
|
70
|
+
until hovered). A page should usually have one bold action and the rest
|
|
71
|
+
default or subtle.
|
|
72
|
+
- **`size`** is `'small'`, `'default'`, or `'large'`. It scales padding and the
|
|
73
|
+
type used inside the component.
|
|
74
|
+
|
|
75
|
+
Not every component exposes all three. A few use a `variant` instead (for
|
|
76
|
+
example `Card` is `outline | elevated`, `Badge` is `solid | subtle | outline`),
|
|
77
|
+
and purely structural pieces (Divider, Skeleton) have none. The per-component
|
|
78
|
+
list below says which props each one takes.
|
|
79
|
+
|
|
80
|
+
Two more ideas worth holding in your head:
|
|
81
|
+
|
|
82
|
+
- **Density is global, not per-component.** Spacing across the whole UI scales
|
|
83
|
+
with the provider's `density`. Colours, radius, and type never change with
|
|
84
|
+
density, so brand and legibility stay constant whether the app feels tight or
|
|
85
|
+
roomy.
|
|
86
|
+
- **Colour is themeable, structure is not.** You can recolour any intent at
|
|
87
|
+
runtime through the provider. You cannot restyle a component's layout through
|
|
88
|
+
props beyond the `style` escape hatch, that is by design, it keeps every
|
|
89
|
+
instance consistent.
|
|
90
|
+
|
|
91
|
+
## ThemeProvider: the five controls
|
|
92
|
+
|
|
93
|
+
`ThemeProvider` takes five independent settings. All are optional.
|
|
94
|
+
|
|
95
|
+
### density
|
|
96
|
+
|
|
97
|
+
How tight or roomy spacing feels, `'compact' | 'default' | 'comfortable'`.
|
|
98
|
+
Changing it rescales padding and gaps across every component at once.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<ThemeProvider density="compact">{/* ... */}</ThemeProvider>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### colorMode
|
|
105
|
+
|
|
106
|
+
`'light' | 'dark'`. Switches every colour in the library. Drive it off the OS
|
|
107
|
+
setting with React Native's `useColorScheme`:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { useColorScheme } from 'react-native';
|
|
111
|
+
|
|
112
|
+
const scheme = useColorScheme();
|
|
113
|
+
<ThemeProvider colorMode={scheme === 'dark' ? 'dark' : 'light'}>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### colors (brand overrides)
|
|
117
|
+
|
|
118
|
+
Pass a partial `colors` object to replace specific intent colours with your own.
|
|
119
|
+
You only write the slots you want to change, everything else keeps its default.
|
|
120
|
+
The shape is `intent -> prominence -> state -> { bg, fg, border }`, where state
|
|
121
|
+
is `default | hover | active`.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<ThemeProvider
|
|
125
|
+
colors={{
|
|
126
|
+
brand: {
|
|
127
|
+
bold: {
|
|
128
|
+
default: { bg: '#7C3AED', fg: '#FFFFFF', border: '#7C3AED' },
|
|
129
|
+
hover: { bg: '#6D28D9', fg: '#FFFFFF', border: '#6D28D9' },
|
|
130
|
+
active: { bg: '#5B21B6', fg: '#FFFFFF', border: '#5B21B6' },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### scheme (the rest of the palette)
|
|
138
|
+
|
|
139
|
+
A deep-partial override for the non-intent colours: surfaces, standalone text
|
|
140
|
+
colours, the focus ring, and overlay. Most apps never set this by hand,
|
|
141
|
+
`applyCastTheme` (below) builds it for you from a Figma export. Set it directly
|
|
142
|
+
only when you want to nudge, say, the page surface or description text colour
|
|
143
|
+
without a full theme file.
|
|
144
|
+
|
|
145
|
+
### motion (retime the system)
|
|
146
|
+
|
|
147
|
+
Primitive-level motion overrides: durations, cycle lengths, easing beziers,
|
|
148
|
+
springs. Semantic roles (transition / feedback / loop) are rebuilt from them,
|
|
149
|
+
so one number retimes every component that uses it. Like colours, this
|
|
150
|
+
usually arrives from Figma via a cast-theme.json rather than by hand.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<ThemeProvider motion={{ duration: { base: 300 } }}>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Nesting and reading the theme
|
|
157
|
+
|
|
158
|
+
Providers nest. A compact data table can live inside a comfortable app:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<ThemeProvider density="comfortable">
|
|
162
|
+
<Page>
|
|
163
|
+
<ThemeProvider density="compact"><DataTable /></ThemeProvider>
|
|
164
|
+
</Page>
|
|
165
|
+
</ThemeProvider>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Your own components can read the active theme with `useTheme()`:
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { useTheme } from '@castui/cast-ui';
|
|
172
|
+
|
|
173
|
+
function Price() {
|
|
174
|
+
const { colors, scheme, density } = useTheme();
|
|
175
|
+
return <Text style={{ color: colors.brand.bold.default.bg }}>£12</Text>;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`useTheme()` returns `{ density, colorMode, components, colors, scheme,
|
|
180
|
+
disabledColors, motion }`. `colors[intent][prominence][state]` gives `{ bg, fg, border }`;
|
|
181
|
+
`components[name][size]` gives the spacing tokens for the active density. The
|
|
182
|
+
raw token modules (`lightColors`, `intentColors`, `label`, `body`, `fontFamily`,
|
|
183
|
+
`iconSize`, and so on) are also exported for direct use.
|
|
184
|
+
|
|
185
|
+
## Theming from Figma with cast-sync and applyCastTheme
|
|
186
|
+
|
|
187
|
+
The `cast-sync` Figma plugin (in the package repo) reads the cast-ui-kit Figma
|
|
188
|
+
file's colour variables and downloads a `cast-theme.json`. The workflow is:
|
|
189
|
+
recolour the variables in Figma, run the plugin, drop the new file into the app.
|
|
190
|
+
No code changes.
|
|
191
|
+
|
|
192
|
+
The clean way to apply that file is `applyCastTheme(theme, mode)`. It pairs the
|
|
193
|
+
file's colours with the matching `colorMode` so they cannot desync, and maps the
|
|
194
|
+
file's `text`, `surface`, and `focusRing` sections into the `scheme` prop so they
|
|
195
|
+
actually take effect. Spread the result into `ThemeProvider`:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
import theme from './cast-theme.json';
|
|
199
|
+
import { ThemeProvider, applyCastTheme } from '@castui/cast-ui';
|
|
200
|
+
|
|
201
|
+
const [mode, setMode] = useState<'light' | 'dark'>('light');
|
|
202
|
+
|
|
203
|
+
<ThemeProvider {...applyCastTheme(theme, mode)}>
|
|
204
|
+
<App />
|
|
205
|
+
</ThemeProvider>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`applyCastTheme` is forward-compatible: older theme files and files with extra
|
|
209
|
+
sections both load without throwing. The older manual form still works if you
|
|
210
|
+
only want the intent colours:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<ThemeProvider colorMode="light" colors={theme.colors.light}>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
but `applyCastTheme` is preferred because it also brings across surfaces, text,
|
|
217
|
+
and the focus ring, and keeps the mode in sync.
|
|
218
|
+
|
|
219
|
+
## Motion in your own components
|
|
220
|
+
|
|
221
|
+
Read animation values through `useMotion()`, never as raw numbers. It returns
|
|
222
|
+
the motion tokens plus `reduceMotion` (tracks the OS setting live),
|
|
223
|
+
`useNativeDriver` (false on web), and `scale(ms)` (collapses to 0 under
|
|
224
|
+
reduce-motion).
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { useMotion } from '@castui/cast-ui';
|
|
228
|
+
|
|
229
|
+
const motion = useMotion();
|
|
230
|
+
Animated.timing(value, {
|
|
231
|
+
toValue: 1,
|
|
232
|
+
duration: motion.scale(motion.transition.enter.duration),
|
|
233
|
+
easing: motion.transition.enter.easing,
|
|
234
|
+
useNativeDriver: motion.useNativeDriver,
|
|
235
|
+
}).start();
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Roles: `transition.standard|enter|exit|expand`, `feedback.press|shake|pop`,
|
|
239
|
+
`loop.spin|pulse|indeterminate`. For loops, check `motion.reduceMotion` and
|
|
240
|
+
skip starting the loop. Drawer, BottomSheet, Backdrop, Spinner, Skeleton,
|
|
241
|
+
Progress, SpeedDial, and Accordion already animate this way.
|
|
242
|
+
|
|
243
|
+
## Fonts (read this if text or icons look wrong)
|
|
244
|
+
|
|
245
|
+
Cast UI ships no font files. Two fonts must be loaded by the app:
|
|
246
|
+
|
|
247
|
+
- **Inter** for all text.
|
|
248
|
+
- **Material Symbols Outlined** for the `<Icon>` component and every embedded
|
|
249
|
+
icon.
|
|
250
|
+
|
|
251
|
+
If a font is not loaded there is no error. Text quietly falls back to the system
|
|
252
|
+
font, and **icons render as their literal name** ("star" instead of the glyph).
|
|
253
|
+
So if you see icon names as words on screen, the Material Symbols font is not
|
|
254
|
+
loaded. Load both once at start-up.
|
|
255
|
+
|
|
256
|
+
Expo (covers iOS, Android, and web):
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
import { useFonts } from 'expo-font';
|
|
260
|
+
|
|
261
|
+
const [fontsLoaded] = useFonts({
|
|
262
|
+
Inter: require('./assets/Inter.ttf'),
|
|
263
|
+
MaterialSymbolsOutlined: require('./assets/MaterialSymbolsOutlined.ttf'),
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Plain web, add to the HTML head:
|
|
268
|
+
|
|
269
|
+
```html
|
|
270
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
|
271
|
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap" rel="stylesheet" />
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Bare React Native, link the `.ttf` files as assets keeping the family names
|
|
275
|
+
`Inter` and `MaterialSymbolsOutlined`. The Material Symbols variable font is
|
|
276
|
+
about 10 MB; for production native builds, subset it to the icons you use.
|
|
277
|
+
|
|
278
|
+
## Icons
|
|
279
|
+
|
|
280
|
+
Icons are the `<Icon>` component, rendering Material Symbols Outlined by name
|
|
281
|
+
(the font ligature). Anywhere a component takes an icon prop (`leadingIcon`,
|
|
282
|
+
`trailingIcon`, `icon`), pass the Material Symbols name as a string and the
|
|
283
|
+
component colours and sizes it for you:
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
<Button leadingIcon="add" intent="brand" prominence="bold" onPress={add}>
|
|
287
|
+
New
|
|
288
|
+
</Button>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Browse names at https://fonts.google.com/icons. You can also pass your own React
|
|
292
|
+
node instead of a string if you need a custom icon. Standalone:
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
import { Icon } from '@castui/cast-ui';
|
|
296
|
+
|
|
297
|
+
<Icon name="chevron_right" size="default" color="#374151" />
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`<Icon>` props: `name` (required), `size` (`'xs' | 'small' | 'default' | 'large'`
|
|
301
|
+
= 12 / 16 / 20 / 24, or a number), `color`, and the Material Symbols axes `fill`,
|
|
302
|
+
`weight`, `grade`, `opticalSize`. Only the Outlined style font is loaded, so
|
|
303
|
+
picking Rounded or Sharp in a design will not match in code.
|
|
304
|
+
|
|
305
|
+
## Component catalogue
|
|
306
|
+
|
|
307
|
+
All 37 components, grouped by use. Props marked `?` are optional. Strings passed
|
|
308
|
+
as `children` are required to be plain strings (not arbitrary nodes) on most
|
|
309
|
+
components, this keeps the type ramp consistent and prevents injection. For the
|
|
310
|
+
exact, always-current prop types, read the exported TypeScript types or the
|
|
311
|
+
Storybook "Playground" story for each component.
|
|
312
|
+
|
|
313
|
+
### Foundational
|
|
314
|
+
|
|
315
|
+
**Text** renders the type ramp. `type` is one of `caption`, `label-sm|md|lg`,
|
|
316
|
+
`body-sm|md|lg`, `title-sm|md|lg`, `heading-sm|md|lg`, `display-sm|md|lg`.
|
|
317
|
+
Other props: `color?`, `numberOfLines?`, `selectable?`. Children must be a string.
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
<Text type="heading-lg">Dashboard</Text>
|
|
321
|
+
<Text type="body-md" color="#6B7280">Last synced 2 minutes ago</Text>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Icon** see the Icons section above.
|
|
325
|
+
|
|
326
|
+
**Divider** a separator line. `orientation?` (`'horizontal' | 'vertical'`),
|
|
327
|
+
`color?`.
|
|
328
|
+
|
|
329
|
+
### Actions and input
|
|
330
|
+
|
|
331
|
+
**Button** `children: string`, `intent?`, `prominence?`, `size?`, `disabled?`,
|
|
332
|
+
`leadingIcon?`, `trailingIcon?`, `onPress?`. The workhorse action.
|
|
333
|
+
|
|
334
|
+
**Input** single-line text field. `label?`, `helperText?`, `placeholder?`,
|
|
335
|
+
`value?`, `defaultValue?`, `onChangeText?`, `size?`, `error?`, `disabled?`,
|
|
336
|
+
`leadingIcon?`, `trailingIcon?`, plus the usual RN text-input props
|
|
337
|
+
(`secureTextEntry?`, `keyboardType?`, `autoCapitalize?`, `returnKeyType?`,
|
|
338
|
+
`onSubmitEditing?`, `onFocus?`, `onBlur?`). Set `error` to show the error state.
|
|
339
|
+
|
|
340
|
+
**Select** dropdown with three modes via `type`: `'single'`, `'multi'` (tag
|
|
341
|
+
pills), `'combobox'` (search). Compound: put `SelectOption` (and optionally
|
|
342
|
+
`SelectGroup`, `SelectSeparator`) as children.
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
<Select type="single" label="Country" value={country} onValueChange={setCountry}>
|
|
346
|
+
<SelectOption value="uk">United Kingdom</SelectOption>
|
|
347
|
+
<SelectOption value="us">United States</SelectOption>
|
|
348
|
+
</Select>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
For `multi` use `values` / `onValuesChange`; for `combobox` use `searchValue` /
|
|
352
|
+
`onSearchChange`. Other props: `size?`, `placeholder?`, `leadingIcon?`,
|
|
353
|
+
`disabled?`, `error?`.
|
|
354
|
+
|
|
355
|
+
**Checkbox** `checked?` (`true | false | 'indeterminate'`), `onChange?`,
|
|
356
|
+
`children?` (label), `size?`, `disabled?`.
|
|
357
|
+
|
|
358
|
+
**Radio** a single control with `value?`, `checked?`, `onChange?`, `children?`,
|
|
359
|
+
`size?`. Use **RadioGroup** to manage a set: `value`, `onValueChange`, `size?`,
|
|
360
|
+
`disabled?`, with `Radio` children.
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
<RadioGroup value={plan} onValueChange={setPlan}>
|
|
364
|
+
<Radio value="free">Free</Radio>
|
|
365
|
+
<Radio value="pro">Pro</Radio>
|
|
366
|
+
</RadioGroup>
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Toggle** on/off switch. `checked?`, `onChange?`, `children?` (label), `size?`,
|
|
370
|
+
`disabled?`.
|
|
371
|
+
|
|
372
|
+
**Chip** compact element for filters, selections, tags. `children: string`,
|
|
373
|
+
`intent?`, `variant?` (`'outline' | 'subtle'`), `size?`, `selected?`,
|
|
374
|
+
`disabled?`, `leadingIcon?`, `onPress?`, `onRemove?` (shows a remove affordance).
|
|
375
|
+
|
|
376
|
+
### Display and feedback
|
|
377
|
+
|
|
378
|
+
**Badge** small pill for labels, counts, status. `children: string`, `intent?`,
|
|
379
|
+
`variant?` (`'solid' | 'subtle' | 'outline'`), `size?`, `dot?` (status dot),
|
|
380
|
+
`leadingIcon?`, `trailingIcon?`.
|
|
381
|
+
|
|
382
|
+
**Alert** inline message. `intent?`, `size?`, `variant?` (`'subtle' | 'outline'`),
|
|
383
|
+
`title?`, `description?`, `icon?` (string name, custom node, or `null` to hide),
|
|
384
|
+
`onClose?` (shows a close button).
|
|
385
|
+
|
|
386
|
+
**Toast** brief notification. `title: string`, `children?` (body), `intent?`,
|
|
387
|
+
`size?`, `icon?`, `onClose?`.
|
|
388
|
+
|
|
389
|
+
**Card** content container. `size?`, `variant?` (`'outline' | 'elevated'`),
|
|
390
|
+
`image?`, `icon?`, `title?`, `subtitle?`, `body?`, `actions?` (a node, usually
|
|
391
|
+
buttons), or arbitrary `children`.
|
|
392
|
+
|
|
393
|
+
**Avatar** user representation. `size?`, and one of `source` (image),
|
|
394
|
+
`initials` (string), or `icon`. Falls back gracefully across the three via
|
|
395
|
+
`type` resolution.
|
|
396
|
+
|
|
397
|
+
**Skeleton** loading placeholder. `shape?` (`'text' | 'circle' | 'rectangle'`),
|
|
398
|
+
`width?`, `height?`, `radius?`, `animated?` (pulse, on by default).
|
|
399
|
+
|
|
400
|
+
**Progress** linear progress bar. `value?` (0 to 100 for determinate, omit or
|
|
401
|
+
pass `null` for an indeterminate sweep), `intent?`, `size?`.
|
|
402
|
+
|
|
403
|
+
**Spinner** indeterminate circular loader. `intent?`, `size?`. Use Progress when
|
|
404
|
+
you know the percentage, Spinner when you do not.
|
|
405
|
+
|
|
406
|
+
**Tooltip** short hint on hover or focus. `children: string`, `direction?`
|
|
407
|
+
(`'top' | 'bottom' | 'left' | 'right'`), `size?`, `hasArrow?`.
|
|
408
|
+
|
|
409
|
+
### Overlays and layout
|
|
410
|
+
|
|
411
|
+
**Dialog** modal for confirmations and focused tasks. `open`, `onClose?`, plus
|
|
412
|
+
content props: `title` (required), `description?`, `icon?`, `size?`,
|
|
413
|
+
`primaryAction?` / `secondaryAction?` (each `{ label, onPress }` — no intent
|
|
414
|
+
field; the primary action renders bold brand), or `children`.
|
|
415
|
+
`DialogContent` is the card alone, for custom overlays.
|
|
416
|
+
|
|
417
|
+
```tsx
|
|
418
|
+
<Dialog
|
|
419
|
+
open={open}
|
|
420
|
+
onClose={close}
|
|
421
|
+
title="Delete project?"
|
|
422
|
+
description="This cannot be undone."
|
|
423
|
+
primaryAction={{ label: 'Delete', onPress: remove }}
|
|
424
|
+
secondaryAction={{ label: 'Cancel', onPress: close }}
|
|
425
|
+
/>
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**BottomSheet** modal surface that slides up from the bottom. It hugs its content
|
|
429
|
+
up to about 90% of the screen height, then the content scrolls. `open`,
|
|
430
|
+
`onClose?`, `closeOnBackdropPress?` (default true), `title?`, `showHandle?`
|
|
431
|
+
(drag handle, default true), and `children`. `BottomSheetContent` is the sheet
|
|
432
|
+
card without the modal or animation, for inline use.
|
|
433
|
+
|
|
434
|
+
```tsx
|
|
435
|
+
<BottomSheet open={open} onClose={close} title="Share to">
|
|
436
|
+
<Button intent="brand" prominence="bold" onPress={share}>Continue</Button>
|
|
437
|
+
</BottomSheet>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Popover** anchored floating panel. `children` (the panel content),
|
|
441
|
+
`direction?`, `size?`, `hideArrow?`.
|
|
442
|
+
|
|
443
|
+
**List** vertical list. Compose `ListItem` (with `description?`, `icon?`,
|
|
444
|
+
`trailingIcon?`, `selected?`, `disabled?`, `onPress?`), `ListSubheader`, and
|
|
445
|
+
`ListDivider`.
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
<List>
|
|
449
|
+
<ListSubheader>Account</ListSubheader>
|
|
450
|
+
<ListItem icon="person" onPress={openProfile}>Profile</ListItem>
|
|
451
|
+
<ListDivider />
|
|
452
|
+
<ListItem icon="logout" onPress={signOut}>Sign out</ListItem>
|
|
453
|
+
</List>
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**Tabs** underline tab bar. `<Tabs value onValueChange intent? size?>` owns
|
|
457
|
+
selection; `<Tab value leadingIcon? disabled?>` is one tab. `intent` colours only
|
|
458
|
+
the selected tab's label and underline.
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
<Tabs value={tab} onValueChange={setTab}>
|
|
462
|
+
<Tab value="overview">Overview</Tab>
|
|
463
|
+
<Tab value="activity">Activity</Tab>
|
|
464
|
+
</Tabs>
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Accordion** stack of expandable sections. `<Accordion type value? defaultValue?
|
|
468
|
+
onValueChange? size? collapsible?>` with `<AccordionItem value title leadingIcon?
|
|
469
|
+
disabled?>` children. `type` is `'single'` (one open at a time) or `'multiple'`.
|
|
470
|
+
|
|
471
|
+
```tsx
|
|
472
|
+
<Accordion type="single" defaultValue="shipping">
|
|
473
|
+
<AccordionItem value="shipping" title="Shipping">
|
|
474
|
+
Free delivery on orders over £50.
|
|
475
|
+
</AccordionItem>
|
|
476
|
+
<AccordionItem value="returns" title="Returns" leadingIcon="undo">
|
|
477
|
+
Return any item within 30 days.
|
|
478
|
+
</AccordionItem>
|
|
479
|
+
</Accordion>
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Added in 4.9
|
|
483
|
+
|
|
484
|
+
**Link** inline or standalone text link. `children: string`, `intent?`
|
|
485
|
+
(default brand), `size?`, `underline?` (`'none' | 'hover' | 'always'`),
|
|
486
|
+
`href?` (real anchor on web), `onPress?`, `leadingIcon?/trailingIcon?`,
|
|
487
|
+
`disabled?`.
|
|
488
|
+
|
|
489
|
+
**Backdrop** full-screen dimming scrim. `open`, `onPress?`, `invisible?`,
|
|
490
|
+
`children?` (centred, e.g. a Spinner). Fades with transition/standard.
|
|
491
|
+
|
|
492
|
+
**Breadcrumbs** hierarchy trail. `<Breadcrumbs size? separator?>` with
|
|
493
|
+
`<Breadcrumb current? onPress?>` children; the `current` item renders as text.
|
|
494
|
+
|
|
495
|
+
**CodeBlock** monospaced code on a subtle surface. `children: string`,
|
|
496
|
+
`title?`, `language?`, `showCopy?` (default true), `showLineNumbers?`, `size?`,
|
|
497
|
+
`onCopy?`.
|
|
498
|
+
|
|
499
|
+
**Drawer** panel sliding from an edge. `open`, `onClose?`, `anchor?`
|
|
500
|
+
(`'left' | 'right' | 'top' | 'bottom'`), `title?`, `closeOnBackdropPress?`.
|
|
501
|
+
`DrawerContent` is the panel alone.
|
|
502
|
+
|
|
503
|
+
**Menu** actions anchored to a trigger. `<Menu trigger placement? size?
|
|
504
|
+
open? onOpenChange? closeOnSelect?>` with `<MenuItem leadingIcon? intent?
|
|
505
|
+
disabled? onPress>`, `<MenuDivider>`, `<MenuLabel>` children. Fires actions;
|
|
506
|
+
holds no value (that's Select).
|
|
507
|
+
|
|
508
|
+
**ToggleButtonGroup** segmented control. `exclusive?` (default true) with
|
|
509
|
+
`value/onValueChange`, or multi with `values/onValuesChange`; `intent?`,
|
|
510
|
+
`size?`. `<ToggleButton value children? leadingIcon? disabled?>` per segment.
|
|
511
|
+
|
|
512
|
+
**AppBar** top bar. `title`, `leadingIcon?` + `onLeadingPress?`, `leading?`
|
|
513
|
+
(custom slot), `trailing?` (actions slot — note: not `actions`), `intent?`,
|
|
514
|
+
`prominence?` (bold = filled bar), `size?`, `align?` (`'start' | 'center'`).
|
|
515
|
+
|
|
516
|
+
**Slider** pick a number by dragging. `value?/defaultValue?/onValueChange?`,
|
|
517
|
+
`min?` (0), `max?` (100), `step?` (1), `intent?`, `size?`, `disabled?`.
|
|
518
|
+
|
|
519
|
+
**SpeedDial** FAB that fans into actions. `icon?/openIcon?`, `direction?`,
|
|
520
|
+
`intent?`, `size?`, `open?/onOpenChange?/defaultOpen?`, `backdrop?` (default
|
|
521
|
+
true); `<SpeedDialAction icon label onPress>` children.
|
|
522
|
+
|
|
523
|
+
**Table** columnar data. `<Table size? striped? hoverable?>` with
|
|
524
|
+
`TableHead/TableBody/TableRow/TableCell`; cells take `flex?`, `width?`,
|
|
525
|
+
`numeric?`, `align?`; rows take `onPress?`.
|
|
526
|
+
|
|
527
|
+
**Autocomplete** type-to-filter over a fixed list. `options:
|
|
528
|
+
{ value, label }[]`, `value?/defaultValue?/onValueChange?` (null = none),
|
|
529
|
+
`onInputChange?`, `label?/helperText?/placeholder?/leadingIcon?`, `size?`,
|
|
530
|
+
`filterOptions?`.
|
|
531
|
+
|
|
532
|
+
### Breakpoints (responsive layouts)
|
|
533
|
+
|
|
534
|
+
`useBreakpoint()` returns the active tier (`base` under 600, then `sm`/`md`/
|
|
535
|
+
`lg`/`xl` at 600/840/1200/1600). `useMinWidth('md')` is a boolean.
|
|
536
|
+
`useResponsiveValue({ base: 1, md: 2, lg: 3 })` picks per tier with fallback
|
|
537
|
+
to the nearest below. Fixed foundation: not on the theme, not exported by
|
|
538
|
+
cast-sync.
|
|
539
|
+
|
|
540
|
+
## Recipes
|
|
541
|
+
|
|
542
|
+
**Light/dark that follows the OS:**
|
|
543
|
+
|
|
544
|
+
```tsx
|
|
545
|
+
const scheme = useColorScheme();
|
|
546
|
+
<ThemeProvider colorMode={scheme === 'dark' ? 'dark' : 'light'}>
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
**A whole brand from Figma:** export `cast-theme.json` with the cast-sync plugin,
|
|
550
|
+
then `<ThemeProvider {...applyCastTheme(theme, mode)}>`.
|
|
551
|
+
|
|
552
|
+
**A destructive confirm:** `Dialog` with `primaryAction` `intent: 'danger'`.
|
|
553
|
+
|
|
554
|
+
**A primary plus secondary action row:** one `Button` `prominence="bold"`, the
|
|
555
|
+
rest `prominence="default"` or `"subtle"`, all the same `size`.
|
|
556
|
+
|
|
557
|
+
**Custom one-off styling:** most components accept a `style` prop for layout
|
|
558
|
+
(margin, width, alignSelf). Use it for positioning, not for recolouring,
|
|
559
|
+
recolour through the theme so it stays consistent.
|
|
560
|
+
|
|
561
|
+
## Conventions and gotchas
|
|
562
|
+
|
|
563
|
+
- **Text children are strings.** Button, Text, Badge, Chip, Tab, ListItem and
|
|
564
|
+
similar take a plain string as children, not arbitrary JSX. Compose layout
|
|
565
|
+
around them, not inside them.
|
|
566
|
+
- **Icons are names, not imports.** Pass a Material Symbols name string. If icons
|
|
567
|
+
show as words, the Material Symbols font is not loaded (see Fonts).
|
|
568
|
+
- **Recolour through the theme, never hardcode hex** in component usage. Use the
|
|
569
|
+
`colors` or `scheme` provider props, or `useTheme()` for your own views.
|
|
570
|
+
- **Density is set on the provider, not per component.** To make one region
|
|
571
|
+
denser, nest a provider.
|
|
572
|
+
- **Web vs native focus:** the focus ring is web-only (a CSS outline). On native
|
|
573
|
+
there is intentionally no focus ring, matching platform norms.
|
|
574
|
+
- **It is one cross-platform set.** The same import works on iOS, Android, and
|
|
575
|
+
web; do not look for platform-specific entry points.
|
|
576
|
+
- **Discover exact props from types.** The package ships full `.d.ts`. The export
|
|
577
|
+
surface lives in the package entry point, and each component exports its
|
|
578
|
+
`XxxProps` type. The Storybook "Playground" story for a component is the live,
|
|
579
|
+
exhaustive prop reference.
|
|
580
|
+
|
|
581
|
+
## When this skill does not apply
|
|
582
|
+
|
|
583
|
+
If the task is building or extending the library (new component, design tokens,
|
|
584
|
+
the Figma kit, cast-sync, Code Connect), this consumer guide is the wrong tool,
|
|
585
|
+
that work has its own maintainer playbook in the package repo.
|
|
586
|
+
|
|
587
|
+
<!-- Last reconciled: 2026-07-04 against @castui/cast-ui v4.10.0 (37 components, motion token system). -->
|