@castui/cast-ui 4.8.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.
@@ -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). -->