
Expo Design System Scaffolder
- 69 installs
- 191 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
expo-design-system-scaffolder is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
Key points
- expo-design-system-scaffolder
- AI & Agent Building
- AI-coding skill
Expo Design System Scaffolder by the numbers
- 69 all-time installs (skills.sh)
- +6 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #5,786 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pproenca/dot-skills --skill expo-design-system-scaffolderAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 69 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do I helps with ai & agent building tasks during ai-assisted development?
Helps with ai & agent building tasks during AI-assisted development.
Who is it for?
Best when you're working on ai & agent building and need structured help with expo-design-system-scaffolder.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks during ai-assisted development, or when expo-design-system-scaffolder is a claude code skill for ai & agent building. it helps solo builders move faster with ai-assi
What you get
Structured output aligned to expo-design-system-scaffolder: expo-design-system-scaffolder; AI & Agent Building; AI-coding skill.
Files
Expo Design System Scaffolder
Parameterized templates that generate Expo / React Native design system components which start correct instead of being retrofitted. Every template uses Unistyles v3 variants (never a style escape hatch), ref-as-prop, design tokens, built-in accessibility, and web/iOS parity (_web hover/focus/cursor on interactive primitives), and cites the `expo-design-system` rule IDs it satisfies.
When to Apply
Use this skill when the user wants to:
- Create or add a new shared component — a button, card, text, input, or other primitive
- Scaffold a new screen that lists a domain entity (appointments, patients, notes)
- Add a new design token group (colors, etc.) to the Unistyles theme
- Add a Storybook story that catalogs a component's variants
- Start a new feature and want its components to follow the design system from the first commit
- Set up or update the design system index so agents discover and reuse existing components before building new ones
Available Templates
To scaffold: read the template, substitute the placeholders, and write the result under the design system package (or app/ for the screen). Placeholders are single-brace identifier tokens (e.g., {ComponentName}); literal TSX and Unistyles braces (e.g., { variant }, {item.title}) stay as-is.
| Template | Generates | Placeholders |
|---|---|---|
| `variant-component.tsx.template` | Pressable primitive with variant/size variants, press/disabled states, web hover/focus/cursor, accessibility, ref prop | {ComponentName} {file_path} |
| `card-surface.tsx.template` | Composite surface with leading/trailing/children slots, tone/inset variants, elevation token | {ComponentName} {file_path} |
| `text-primitive.tsx.template` | Typed text component with variant/tone, no raw style, capped font scaling | {ComponentName} {file_path} |
| `form-field.tsx.template` | Labeled input: controlled/uncontrolled, ref prop, error variant, web focus ring, accessibility | {ComponentName} {file_path} |
| `list-screen.tsx.template` | Entity list composed from primitives: FlashList, memoized row, web row hover/cursor, safe-area insets | {ScreenName} {Entity} {entity} {entity_plural} {route_path} {file_path} |
| `token-group.ts.template` | Raw → semantic → component token group to merge into the Unistyles theme | {token_group} {file_path} |
| `component-story.tsx.template` | Storybook story rendering every variant side by side | {ComponentName} {file_path} |
| `component-index.ts.template` | The design system's single public entry and inventory catalog — agents read it before styling to reuse what exists (reuse-first) | {file_path} |
Common placeholders:
{ComponentName}— PascalCase component, e.g.AppButton,AppCard,AppText,AppTextField{ScreenName}— PascalCase screen with aScreensuffix, e.g.AppointmentsScreen{Entity}/{entity}/{entity_plural}— PascalCase, camelCase, and plural, e.g.Appointment/appointment/appointments{route_path}— route segment pushed for a row, e.g.appointments{token_group}— camelCase token group name, e.g.statusBadge{file_path}— the destination path, written into the header comment
How to Use
1. Pick the template for what you are building. 2. Choose values for its placeholders (see the table and each template's header comment). 3. Substitute the tokens and write the file under design_system_dir (or app_dir for the screen). 4. Generate a matching story with component-story.tsx.template so the variants are cataloged. 5. Append the new component to the design system index (component-index.ts.template) — an export plus a one-line catalog entry — so the next agent finds and reuses it instead of rebuilding it. 6. Fill in the data layer the screen imports (use{Entity}List, the domain type) — the scaffold owns the view; you own the data. 7. Read references/conventions.md for the rules each template enforces and when to deviate.
Setup
config.json is optional. Override on first use if your project differs:
design_system_dir— design system package source (defaultpackages/design-system/src)app_dir— Expo Router routes directory for the screen template (defaultapp)
Related Skills
- `expo-design-system` — the rule pack these templates follow; each generated file cites it.
- `expo-react-native-coder` — feature development around the scaffolded components.
// {file_path} — {ComponentName} surface with slots
// Generated by expo-design-system-scaffolder
//
// Placeholders:
// - {ComponentName}: PascalCase component name, e.g. AppCard
// - {file_path}: destination path, written into this header
//
// Rules enforced: api-slots-for-composition, api-no-style-escape-hatch,
// token-elevation-pairs, space-radius-tokens
import type { ReactNode } from 'react'
import { View } from 'react-native'
import { StyleSheet } from 'react-native-unistyles'
type {ComponentName}Props = {
children: ReactNode
leading?: ReactNode
trailing?: ReactNode
tone?: 'default' | 'alert'
inset?: 'comfortable' | 'compact'
}
// Slots (leading/trailing) accept any node, so the surface never grows a prop per element.
export function {ComponentName}({
children,
leading,
trailing,
tone = 'default',
inset = 'comfortable',
}: {ComponentName}Props) {
styles.useVariants({ tone, inset })
return (
<View style={styles.root}>
{leading}
<View style={styles.body}>{children}</View>
{trailing}
</View>
)
}
const styles = StyleSheet.create((theme) => ({
root: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.space.sm,
borderRadius: theme.radius.md,
...theme.elevation.raised,
variants: {
tone: {
default: { backgroundColor: theme.colors.surfaceRaised },
alert: { backgroundColor: theme.colors.surfaceAlert },
},
inset: {
comfortable: { padding: theme.space.lg },
compact: { padding: theme.space.sm },
},
},
},
body: { flex: 1 },
}))
// {file_path} — Design system public entry & inventory
// Generated by expo-design-system-scaffolder
//
// READ THIS FIRST before styling anything (rule: reuse-inventory-first). It is both the single
// public entry of the design system and the catalog of what already exists — so the next agent
// reuses or extends a component instead of forking a near-duplicate.
//
// When you scaffold a component, add TWO lines: an `export` below and a one-line catalog entry
// in the matching section. Keep entries one line each so the whole inventory reads in one shot.
//
// Placeholders:
// - {file_path}: destination path, e.g. packages/design-system/src/index.ts
//
// Rules enforced: govern-design-system-package, reuse-inventory-first, reuse-platform-component-first
//
// ── Components — reuse or extend these before creating anything new (reuse-extend-not-fork) ──
// AppButton — pressable primitive; variants: variant(primary|secondary), size(sm|md); web hover/focus/cursor
// AppCard — surface with leading/trailing/children slots; variants: tone(default|alert), inset(comfortable|compact)
// AppText — typed text; variants: variant(title|body|caption), tone(default|muted|danger)
// AppTextField — labeled input; controlled/uncontrolled, error variant, web focus ring
// <add new components here, one line each, on scaffold>
//
// ── Native controls — use these, don't reimplement (reuse-platform-component-first) ──
// Switch (react-native, cross-platform); DateTimePicker / Picker / segmented control
// (@expo/ui/swift-ui on iOS, behind a .ios.tsx split — see platform-divergence-split)
//
// ── Tokens — read from the Unistyles theme, never hardcode (token-no-raw-values-in-components) ──
// theme.colors.* theme.space.* theme.radius.* theme.typography.* theme.elevation.*
export { AppButton } from './AppButton'
export { AppCard } from './AppCard'
export { AppText } from './AppText'
export { AppTextField } from './AppTextField'
// export { NewComponent } from './NewComponent' ← add on scaffold
export { lightTheme, darkTheme } from './theme'
export type { AppTheme } from './theme'
// {file_path} — {ComponentName} story (variant catalog)
// Generated by expo-design-system-scaffolder
//
// Placeholders:
// - {ComponentName}: PascalCase component, e.g. AppButton
// - {file_path}: destination path, written into this header
//
// Rules enforced: govern-storybook-catalog
//
// This story is shaped for a pressable primitive (variant + title + onPress). Adjust the
// `variants` array and the rendered props to the target component — e.g. a text primitive uses
// variant='title'|'body'|'caption' with children, and a card uses tone/inset with slot content.
import { Card } from '../components/Card'
import { {ComponentName} } from './{ComponentName}'
export default { title: 'DesignSystem/{ComponentName}', component: {ComponentName} }
// Render every variant side by side so visual regressions surface in review.
const variants = ['primary', 'secondary'] as const
export const AllVariants = () => (
<Card tone="default" inset="comfortable">
{variants.map((variant) => (
<{ComponentName} key={variant} variant={variant} title={variant} onPress={() => {}} />
))}
</Card>
)
// {file_path} — {ComponentName} labeled text field
// Generated by expo-design-system-scaffolder
//
// Placeholders:
// - {ComponentName}: PascalCase component name, e.g. AppTextField
// - {file_path}: destination path, written into this header
//
// Rules enforced: api-controlled-uncontrolled, api-forward-ref, platform-web-pseudo-states,
// api-accessibility-in-contract, type-respect-font-scaling, token-no-raw-values-in-components
import { useState } from 'react'
import type { Ref } from 'react'
import { View, Text, TextInput } from 'react-native'
import type { TextInputProps } from 'react-native'
import { StyleSheet } from 'react-native-unistyles'
type {ComponentName}Props = Omit<TextInputProps, 'style' | 'value' | 'onChangeText'> & {
label: string
error?: string
value?: string
defaultValue?: string
onChangeText?: (next: string) => void
ref?: Ref<TextInput>
}
export function {ComponentName}({
label,
error,
value,
defaultValue = '',
onChangeText,
ref,
...props
}: {ComponentName}Props) {
const [internal, setInternal] = useState(defaultValue)
const isControlled = value !== undefined
const current = isControlled ? value : internal
const handleChange = (next: string) => {
if (!isControlled) setInternal(next)
onChangeText?.(next)
}
styles.useVariants({ invalid: Boolean(error) })
return (
<View style={styles.field}>
<Text style={styles.label} maxFontSizeMultiplier={1.4}>{label}</Text>
<TextInput
ref={ref}
value={current}
onChangeText={handleChange}
style={styles.input}
accessibilityLabel={label}
{...props}
/>
{error ? (
<Text style={styles.error} accessibilityLiveRegion="polite">{error}</Text>
) : null}
</View>
)
}
const styles = StyleSheet.create((theme) => ({
field: { gap: theme.space.xs },
label: { color: theme.colors.textMuted, fontSize: theme.typography.caption.fontSize },
input: {
minHeight: theme.space.touchTarget,
paddingHorizontal: theme.space.md,
borderRadius: theme.radius.sm,
borderWidth: 1,
color: theme.colors.textPrimary,
variants: {
invalid: {
true: { borderColor: theme.colors.danger },
false: { borderColor: theme.colors.border },
},
},
// Web keyboard-focus ring; the text cursor is already native on web inputs.
_web: {
_focus: { outlineColor: theme.colors.accent, outlineStyle: 'solid', outlineWidth: 2 },
},
},
error: { color: theme.colors.danger, fontSize: theme.typography.caption.fontSize },
}))
// {file_path} — {Entity} list screen composed from design system primitives
// Generated by expo-design-system-scaffolder
//
// Placeholders:
// - {ScreenName}: PascalCase screen, e.g. AppointmentsScreen
// - {Entity}: PascalCase singular, e.g. Appointment
// - {entity}: camelCase singular, e.g. appointment
// - {entity_plural}: camelCase plural, e.g. appointments
// - {route_path}: route segment pushed for a row, e.g. appointments
// - {file_path}: destination path, written into this header
//
// Rules enforced: perf-flashlist-for-lists, perf-memoize-list-items,
// space-safe-area-insets, platform-web-pseudo-states, domain-compose-from-primitives
import { memo, useCallback } from 'react'
import { Pressable, View } from 'react-native'
import { FlashList } from '@shopify/flash-list'
import { router } from 'expo-router'
import { StyleSheet } from 'react-native-unistyles'
import { Card } from '../components/Card'
import { AppText } from '../components/AppText'
import { use{Entity}List } from '../hooks/use{Entity}List'
import type { {Entity} } from '../domain/{entity}'
type RowProps = { item: {Entity}; onPress: (id: string) => void }
const {Entity}Row = memo(({ item, onPress }: RowProps) => (
<Pressable style={styles.row} onPress={() => onPress(item.id)} accessibilityRole="button">
<Card tone="default" inset="comfortable">
<AppText variant="title">{item.title}</AppText>
<AppText variant="caption" tone="muted">{item.subtitle}</AppText>
</Card>
</Pressable>
))
{Entity}Row.displayName = '{Entity}Row'
const Separator = () => <View style={styles.separator} />
export function {ScreenName}() {
const { {entity_plural} } = use{Entity}List()
const open = useCallback((id: string) => router.push('/{route_path}/' + id), [])
return (
<FlashList
data={ {entity_plural} }
keyExtractor={(item) => item.id}
contentContainerStyle={styles.content}
ItemSeparatorComponent={Separator}
renderItem={({ item }) => <{Entity}Row item={item} onPress={open} />}
/>
)
}
const styles = StyleSheet.create((theme, rt) => ({
content: { padding: theme.space.md, paddingBottom: rt.insets.bottom + theme.space.md },
row: { _web: { cursor: 'pointer', _hover: { opacity: 0.9 } } },
separator: { height: theme.space.sm },
}))
// {file_path} — {ComponentName} typed text primitive
// Generated by expo-design-system-scaffolder
//
// Placeholders:
// - {ComponentName}: PascalCase component name, e.g. AppText
// - {file_path}: destination path, written into this header
//
// Rules enforced: type-text-component-wrapper, type-scale-tokens, type-respect-font-scaling
import { Text } from 'react-native'
import type { TextProps } from 'react-native'
import { StyleSheet } from 'react-native-unistyles'
// Omit `style` so all text styling flows through variant + tone, not raw overrides.
type {ComponentName}Props = Omit<TextProps, 'style'> & {
variant?: 'title' | 'body' | 'caption'
tone?: 'default' | 'muted' | 'danger'
}
export function {ComponentName}({
variant = 'body',
tone = 'default',
maxFontSizeMultiplier = 1.6,
...props
}: {ComponentName}Props) {
styles.useVariants({ variant, tone })
// maxFontSizeMultiplier keeps OS font scaling on with a sane cap.
return <Text style={styles.text} maxFontSizeMultiplier={maxFontSizeMultiplier} {...props} />
}
const styles = StyleSheet.create((theme) => ({
text: {
variants: {
variant: {
title: theme.typography.titleL,
body: theme.typography.body,
caption: theme.typography.caption,
},
tone: {
default: { color: theme.colors.textPrimary },
muted: { color: theme.colors.textMuted },
danger: { color: theme.colors.danger },
},
},
},
}))
// {file_path} — {token_group} token group (merge into the Unistyles theme)
// Generated by expo-design-system-scaffolder
//
// Placeholders:
// - {token_group}: camelCase group name, e.g. statusBadge
// - {file_path}: destination path, written into this header
//
// Rules enforced: token-three-layer-scale, token-semantic-naming, token-define-in-unistyles-theme
//
// Three layers — keep all of them inside the design-system theme, never in a feature.
// 1) Raw layer — name by hue and shade; referenced only by the semantic layer below.
export const {token_group}Palette = {
green600: '#16A34A',
amber500: '#F59E0B',
red600: '#DC2626',
} as const
// 2) Semantic layer — add role-named tokens to lightTheme.colors / darkTheme.colors so the
// name stays honest when the value changes. Provide a value per theme:
// statusConfirmed: {token_group}Palette.green600,
// statusPending: {token_group}Palette.amber500,
// statusCancelled: {token_group}Palette.red600,
// 3) Component layer (optional) — compose semantic tokens for a specific component:
// components: { {token_group}: { background: lightTheme.colors.surfaceMuted } }
// {file_path} — {ComponentName} pressable primitive
// Generated by expo-design-system-scaffolder
//
// Placeholders:
// - {ComponentName}: PascalCase component name, e.g. AppButton
// - {file_path}: destination path, written into this header
//
// Rules enforced: api-variants-over-style-prop, api-no-style-escape-hatch,
// style-variants-api, style-press-states-from-variants, platform-web-pseudo-states,
// api-accessibility-in-contract, api-forward-ref
import { useState } from 'react'
import type { Ref } from 'react'
import { Pressable, Text, View } from 'react-native'
import { StyleSheet } from 'react-native-unistyles'
type {ComponentName}Props = {
title: string
variant?: 'primary' | 'secondary'
size?: 'sm' | 'md'
disabled?: boolean
onPress: () => void
accessibilityLabel?: string
ref?: Ref<View>
}
// No `style` prop: callers customize via variant/size, never a raw override.
export function {ComponentName}({
title,
variant = 'primary',
size = 'md',
disabled,
onPress,
accessibilityLabel,
ref,
}: {ComponentName}Props) {
const [pressed, setPressed] = useState(false)
styles.useVariants({ variant, size, pressed, disabled })
return (
<Pressable
ref={ref}
style={styles.root}
disabled={disabled}
onPress={onPress}
onPressIn={() => setPressed(true)}
onPressOut={() => setPressed(false)}
accessibilityRole="button"
accessibilityState={{ disabled: Boolean(disabled) }}
accessibilityLabel={accessibilityLabel ?? title}
>
<Text style={styles.label}>{title}</Text>
</Pressable>
)
}
const styles = StyleSheet.create((theme) => ({
root: {
alignItems: 'center',
justifyContent: 'center',
borderRadius: theme.radius.md,
minHeight: theme.space.touchTarget,
variants: {
variant: {
primary: { backgroundColor: theme.colors.accent },
secondary: { backgroundColor: theme.colors.surfaceMuted },
},
size: {
sm: { paddingHorizontal: theme.space.sm },
md: { paddingHorizontal: theme.space.md },
},
pressed: { true: { opacity: 0.7 } },
disabled: { true: { opacity: 0.4 } },
},
// Web pointer affordances so the same button feels native on web, not just touch.
_web: {
cursor: 'pointer',
_hover: { opacity: 0.92 },
_focus: { outlineColor: theme.colors.accent, outlineStyle: 'solid', outlineWidth: 2 },
},
},
label: { color: theme.colors.onAccent, fontSize: theme.typography.body.fontSize },
}))
{
"design_system_dir": "packages/design-system/src",
"app_dir": "app",
"_setup_instructions": {
"design_system_dir": "Source directory of the @clinic/design-system package where primitives, the theme, and stories live (default packages/design-system/src)",
"app_dir": "Expo Router routes directory used by the list-screen template (default app)"
}
}
Gotchas
Append failure points as they are discovered, with dates.
2026-05-22 — Web parity: _web cursor only on interactive templates
The pressable primitive, form field, and list row emit a Unistyles _web block (cursor, _hover, _focus). Do not add a pointer cursor to the card or text primitive — a hand cursor on a non-clickable surface misleads web users (platform-web-pseudo-states "When NOT to use"). Caveat: a Pressable's cursor stays pointer even when disabled on React Native Web; if that matters for a given component, override cursor: 'default' in the disabled path.
{
"version": "0.3.0",
"organization": "Airbnb DLS / React Native Community",
"technology": "Expo React Native design system components",
"discipline": "extraction",
"type": "scaffolding",
"date": "May 2026",
"abstract": "Parameterized templates that scaffold Expo / React Native design system components which obey the expo-design-system rules by construction — Unistyles v3 variants instead of style props, no raw style escape hatch, ref-as-prop, design tokens, built-in accessibility, and web/iOS parity (Unistyles `_web` hover/focus/cursor on interactive primitives) so components feel native on both web and iOS. Covers a variant pressable primitive, a slot-based card surface, a typed text primitive, a labeled form field, a FlashList entity screen, a theme token group, a Storybook variant catalog, and a single component+token index that agents read before styling so they reuse the system instead of restyling locally. Each template cites the expo-design-system rule IDs it satisfies, so generated components match the rule pack without rework.",
"references": [
"https://www.unistyl.es/v3/references/variants/",
"https://www.unistyl.es/v3/guides/theming/",
"https://www.unistyl.es/v3/references/web-only/",
"https://react.dev/blog/2024/12/05/react-19",
"https://shopify.github.io/flash-list/",
"https://www.infoq.com/news/2020/02/airbnb-design-system-react-conf/"
]
}
Conventions
These templates generate Expo / React Native design system code that obeys the `expo-design-system` rules by construction. This document explains the conventions the templates enforce and why, so you can make informed exceptions.
Styling: Unistyles StyleSheet.create((theme) => ...) only
Every template styles with StyleSheet.create from react-native-unistyles, reading values from the theme argument. Why: the style reference is stable and Unistyles repaints it natively on theme change with no React re-render. Inline objects re-allocate per render and cannot be themed. Rules: style-stylesheet-create, theme-stylesheet-theme-arg.
Component API: variant props, never a style escape hatch
Components expose variant, size, tone, and inset props and call styles.useVariants({ ... }). They do not accept a style prop; the text primitive even Omits it from its props. Why: this is the Airbnb DLS contract — a closed, reviewable set of options keeps every "primary" button identical, while a style prop lets any screen bypass tokens. Rules: api-variants-over-style-prop, api-no-style-escape-hatch, style-variants-api.
Composition: slots over a prop per element
Composite surfaces accept leading/trailing/children slots of type ReactNode rather than a show* boolean plus payload per element. Why: slots cover content the author never predicted without an ever-growing API. Rule: api-slots-for-composition.
State: controlled or uncontrolled
Inputs accept an optional value with a defaultValue fallback, so simple screens stay terse and forms keep full control. Why: controlled-only forces every call site to own state. Rule: api-controlled-uncontrolled.
Refs: ref as a prop (React 19)
Leaf components declare ref?: Ref<...> and pass it to the native element. Why: React 19 (React Native 0.81+) makes ref a regular prop, so forwardRef is the legacy form; forwarding the ref keeps focus and measurement available to forms. Rule: api-forward-ref.
Accessibility: baked into the contract
Interactive templates set accessibilityRole, derive an accessibilityLabel, expose accessibilityState, and keep OS font scaling on with a capped maxFontSizeMultiplier. Why: accessibility left to the caller is forgotten; building it in means an inaccessible instance cannot ship. Rules: api-accessibility-in-contract, type-respect-font-scaling.
Lists & native feel
The list template uses FlashList with a keyExtractor, a memoized row, and safe-area bottom padding from rt.insets. Why: a ScrollView mounts every row; an un-memoized row re-renders on every parent update; hardcoded insets collide with the home indicator. Rules: perf-flashlist-for-lists, perf-memoize-list-items, space-safe-area-insets.
Cross-platform: web parity by construction
Interactive templates emit a Unistyles _web block so the same component feels native on web as well as iOS: the pressable primitive and the list row get cursor: 'pointer', a _hover state, and a _focus ring; the form field gets a _focus ring (its text cursor is already native on web). Non-interactive surfaces — the card and the text primitive — deliberately get no cursor, since a pointer hand on something you cannot click misleads web users. Why: Unistyles v3 is a first-class web engine, so styling for touch alone leaves a button inert on the web frontend (no hover, no cursor, no focus ring). Rule: platform-web-pseudo-states.
Tokens: three layers in the theme
The token-group template adds raw → semantic → component layers to the Unistyles theme and names tokens by role, never by value. Why: role names stay honest through a rebrand, and one theme is the single source of truth. Rules: token-three-layer-scale, token-semantic-naming, token-define-in-unistyles-theme.
Reuse: read and maintain the index
The component-index.ts.template is the design system's single public entry and its inventory: a barrel of exports plus a one-line catalog of every component, the native controls to prefer, and the token namespaces. Agents read it before styling so they reuse or extend what already exists instead of forking a local near-duplicate, and append to it whenever they scaffold a component. Why: the local-styling bias comes from not seeing what the system already provides; a single cheap-to-read index makes "look first" the path of least resistance. Rules: reuse-inventory-first, reuse-extend-not-fork, govern-design-system-package.
Placeholder syntax
Placeholders are single-brace identifier tokens ({ComponentName}, {Entity}, {entity_plural}, {token_group}, {file_path}). Why: they are distinct from literal TSX and Unistyles braces ({item.title}, { variant }), which contain expressions or spaces and must stay as-is. Substitute only the named tokens.
When to deviate
- A one-off, never-reused view does not need a primitive — compose inline.
- A genuinely free-form container (e.g. a chart host) may take a constrained
styleif documented. - Skip the story for an internal primitive that is never used outside one screen.
Related skills
FAQ
What does expo-design-system-scaffolder do?
expo-design-system-scaffolder is a Claude Code skill for ai & agent building. It helps developers move faster with AI-assisted coding.
When should I use expo-design-system-scaffolder?
When you need to helps with ai & agent building tasks during ai-assisted development, or when expo-design-system-scaffolder is a claude code skill for ai & agent building. it helps developers move faster with ai-assisted coding.
What are the main capabilities?
expo-design-system-scaffolder; AI & Agent Building; AI-coding skill.