UI contract
Screens import primitives only from @/ui; the stack behind it is a swappable adapter typed by UiKit.
This is the reference for src/ui/contract.ts, the one contract every UI stack implements. If you
want to style a screen rather than read the spec, start with Styling.
It applies to the tree before bun run setup, or after bun run setup --keep-modules. A finalized
tree has no contract: the kit sits in src/components/ui, its prop types in
src/components/ui/types.ts, and there's no UiKit left to satisfy.
Screens, components and routes import UI only from @/ui, so switching from NativeWind to
Tamagui (or to plain StyleSheet) changes the adapter under src/ui/<stack>/, not the app.
export * from "./contract";
export * from "./theme-mode";
export * from "./nativewind4"; // the active stackPrimitives
UiKit is the runtime export list every src/ui/<stack>/index.ts must provide: the primitives
in Styling → The primitives, which also covers list screens.
Theme mode (useThemeMode, setThemeMode, useResolvedMode) is re-exported from
src/ui/theme-mode.ts, and Box's short align / justify aliases are FlexAlias in the
contract - adapters map them.
Rules
- Props are token-typed. Spacing props take
SpaceToken(0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16), colours takeColorToken, radii takeRadiusToken- never raw numbers or hex strings. styleis the only escape hatch. Every primitive acceptsstyle?: StyleProp<ViewStyle>(orTextStyle). There is deliberately noclassNameon the contract; Tailwind classes are an implementation detail of the NativeWind adapters and do not exist for Tamagui, Unistyles orStyleSheet.- The contract file imports nothing from a stack. Only React and react-native types.
- ESLint enforces it (
eslint.config.js, forsrc/screens/**andsrc/app/**):View,Text,StyleSheet,Pressable,TextInput,Switchfromreact-nativeare restricted; so are@/components/ui/*,@/ui/<adapter>(only@/uiand@/ui/contractare public),@/theme/*,nativewind,react-native-css,react-native-reanimated, and importingreadynative.configdirectly (use@/lib/readynative).
Tokens
The token sets and bun run gen:theme work the same in every tree - see
Styling → Theme tokens and gen:theme.
Adding a UI stack adapter
This needs modules/, so it works in a tree set up with --keep-modules.
-
bun run gen module ui <stack>scaffoldsmodules/ui/<stack>/{module.json,files/,docs.md}. -
Implement every primitive under
files/src/ui/<stack>/and finishindex.tswith:src/ui/<stack>/index.ts const kit = { ThemeProvider, useTheme, Screen, Box, Text, Button, Pressable, Card, Row, List, Input, Switch, Divider, Icon, Skeleton, Loading, EmptyState, ErrorState, ToastHost, toast, } satisfies UiKit; export default kit;satisfies UiKitis what makes a missing or mistyped primitive a compile error. -
Declare the theme provider in
module.jsonsosetupnests it at order 50:{ "import": "ThemeProvider", "as": "UiThemeProvider", "from": "@/ui", "order": 50 }, and list every file underfiles[]. -
If the stack needs a generated theme file, add a renderer in
scripts/lib/gen-theme*.tsand put its id inmodule.json.gen. -
Apply it with
bun run setup --ui <stack> --yes --keep-modules, then runbun run typecheck,bun run lint,bun run testandbunx expo export -p ios -p android. Runsetupa second time: it should change nothing. Thestylesheetadapter is the reference implementation of the contract - copy its structure.