Skip to content
  • Introduction
    nativecn is shadcn for Expo apps: Components you copy into your app and own, built so AI agents can build and verify whole mobile apps.
  • Installation
    Create a new Expo app with nativecn, or add nativecn to an existing one.
  • components.json
    The project file that records your Preset, Structure, aliases and agents.
  • CLI
    Every nativecn-cli command and flag: init, create, add, agents and mcp.
  • Changelog
    What changed in each nativecn release.
  • Theming
    Tokens, Colour Roles, Scale, Scheme, createStyles and fonts.
  • Presets & Styles
    Style, Base Colour, Accent Colour, Radius and fonts: chosen once, baked into your code.
  • Folder structure
    Flat and feature Structures, Destinations and the placement rule.
  • AI agents
    Rules, Skills, the nativecn MCP server, the Plugin and the Visual QA Loop.
  • Alert
    Alert, AlertTitle and AlertDescription: an inline message with Variants (default, destructive, success, warning, info), each with a default icon.
  • Avatar
    Round user image (expo-image) with fallback initials, in sizes sm, md and lg.
  • Badge
    Small status label with Variants default, secondary, outline, destructive, success and warning.
  • Button
    Button with Variants (primary, secondary, outline, ghost, destructive, link), Sizes, an icon slot, loading and status; icon-only requires aria-label.
  • Card
    Card surface with CardHeader, CardTitle, CardDescription, CardContent and CardFooter; onPress makes the whole card pressable.
  • Checkbox
    Checkbox with an optional label, controlled or uncontrolled, an indeterminate (mixed) state and a haptic tick.
  • Chip
    Chip (plain, toggle or removable, with an optional icon) and ChipGroup (single or multiple choice on the SelectionGroup Primitive), with a haptic tick.
  • Container
    Wraps a Screen: safe-area edges, scrolling, keyboard avoidance, Token padding and a max content width.
  • Drawer
    Drawer panel content for Expo Router's drawer (expo-router/drawer): DrawerContent, DrawerHeader, DrawerSection, DrawerItem (label, icon, href, badge; active from the current route) and DrawerFooter.
  • Empty State
    What a Screen or list shows when there is nothing to show yet: an icon, a title, a description and an action.
  • FormField
    Lays out a control with its Label, description and error, and links them for screen readers ("Email, text field, Enter a valid email").
  • Icon
    Every glyph: a Lucide icon at a Token size in a Colour Role, decorative unless labelled.
  • Input
    Single-line text field with Sizes, a leading icon, error/success status, a focus ring and an automatic Show/Hide password toggle.
  • InputOTP
    One-time code field: one hidden input drives N cells; numeric keyboard, SMS/Keychain autofill, paste, onComplete, error/success status and secure.
  • Label
    A field's visible label in the label type Variant, with a required marker read as "required".
  • List
    Settings-style rows: ListSection, ListSectionHeader, ListSectionFooter and ListItem (title, description, icon, trailing, chevron, destructive, onPress); FlashList-safe.
  • Progress
    A horizontal progress bar: a value 0–100 that animates, or indeterminate; role progressbar with its value.
  • RadioGroup
    RadioGroup and RadioGroupItem: a single choice from a list on the SelectionGroup Primitive, vertical or horizontal, with a haptic tick.
  • SchemeSwitcher
    Theme switcher / dark mode toggle: System, Light and Dark as SegmentedTabs, or one icon button cycling through them. Calls setScheme, which persists the choice.
  • SearchField
    Search box with a search icon, an automatic clear (×) button, a loading spinner and onSubmit from the keyboard's Search key.
  • SegmentedTabs
    In-screen tabs (not navigation): segmented or underline Variants, an animated indicator, and only the selected tab's content rendered.
  • Separator
    A hairline divider, horizontal or vertical, decorative by default.
  • Skeleton
    A placeholder shape shown while content loads; pulses unless Reduce Motion is on, hidden from screen readers.
  • Slider
    Pick a number in a range by dragging or tapping (Gesture Handler + Reanimated), snapped to steps with a haptic tick; adjustable for screen readers.
  • Spinner
    An indeterminate loading indicator in a Colour Role, announced as busy; still under Reduce Motion.
  • Switch
    A themed on/off Switch, identical on iOS and Android, with an optional label, a thumb that slides (instant under Reduce Motion) and a haptic tick.
  • TabNavigation
    The bottom tab bar: the custom tabBar of an Expo Router JS Tabs Layout, with classic and floating Variants, per-tab icons and badges from Tabs.Screen options.
  • Text
    All text: the type ramp as Variants, text Colour Roles, alignment and the font-scaling switch.
  • Textarea
    Multi-line text field that grows from minRows to maxRows, with a character counter when maxLength is set.
  • Toast
    Sonner-style Toasts: a <Toaster /> in the root Layout and toast(), toast.success/error/info/warning/dismiss. At most 3 stacked, 4s auto-dismiss, swipe away, announced, above native modals.
  • Profile header + grouped sections
    Drawer panel with a profile header (avatar, name, email, plan Badge), labelled sections (Main, Workspace, Support) and Log out pinned in the footer.
  • SaaS workspace
    Drawer panel for a SaaS workspace: logo and name with a close button, an Accent Colour onboarding item with a 5 Steps Badge, main items, a bottom group (Notifications with a count, Settings, Docs, Help) and a footer with a team switcher and the user's avatar.
  • Cover header
    Drawer panel with a dark cover (large avatar, name, View profile, a bell with a count, an overflow button and a round + action), icon items active in Accent Colour text, a divider and text-only secondary items.
  • Classic centred
    A centred sign-in Screen: logo, email and password, Forgot password, an "or" divider, then Apple and Google, and a Sign up link.
  • Social-first hero
    A social-first sign-in Screen: a dark brand hero (logo, headline, subtitle, centred) above a panel where Apple and Google lead and "Continue with email" reveals the email form.
  • Email-first, two steps
    An email-first sign-in Screen in two steps: the email, then the password, with "Email me a code instead" switching to a 6-digit code (InputOTP). Lucide chevron-left Back buttons.
  • Classic single form
    A classic sign-up Screen on one scroll: Apple and Google on top, "or", then name, email and password, a terms Checkbox with Terms and Privacy Policy links, Create account, and a Sign in link.
  • Step-by-step wizard
    A sign-up wizard, one question per step under a Progress bar: email, name, password, then a 6-digit code (InputOTP). Lucide chevron-left Back buttons; the Continue button rides above the keyboard.
  • Social-first + verify
    A brand-led, passwordless sign-up Screen: the logo and headline centred above Apple and Google; "Sign up with email" reveals the email field, and Send code moves on to a 6-digit code (InputOTP).
  • announce
    Reliable screen-reader announcements on iOS and Android (Button and Input status, Toast, error, success and warning Alerts).
  • FocusChain
    Automatic Next/Done between fields in on-screen order; Done submits the form.
  • FormField context
    Links a field to its label, description and error for screen readers; passes status, disabled and required down.
  • Keyboard
    KeyboardProvider setup, a scroll view that keeps the focused field above the keyboard, and a footer that keeps a form's main button above it.
  • Portal
    Renders content above every Screen (above native modals on iOS), layered and safe-area aware.
  • Pressable
    Pressed look from a Style Slot, tap area extended to 48, no handlers while disabled or loading, optional haptics, role and aria-* states.
  • SelectionGroup
    Single or multiple choice within a group, with radio, tab or checkbox roles and selected states.
  • useControllableState
    One implementation of controlled and uncontrolled values for form controls.
  • useMotion
    Enter/exit animations and timing/spring configs from the motion Tokens; instant when Reduce Motion is on.
  • Theme
    Tokens, Scale, Colour Roles, ThemeProvider, createStyles and the navigation theme.

Theming

Tokens, Colour Roles, Scale, Scheme, createStyles and fonts.

View as Markdown

The Theme is the single source of truth for every Token in your app. Components read Tokens from it and never hardcode colours or sizes, so you change the look of the whole app in one place.

The Theme files

create and init write the Theme into src/theme/ (or theme/ in apps without src/). You own these files.

FileHolds
colors.tsthe Colour Roles, light and dark
tokens.tsspacing, radius, the type ramp, elevation, motion, control heights, opacity and fonts
config.tsfontScaling (off), defaultScheme ('system') and the Scale baseline and clamp
scale.tsthe Scale computation and scaleValue(n)
provider.tsxThemeProvider, useTheme, createStyles and the Scheme store
navigation.tsExpo Router's navigation theme, derived from the Tokens
index.tsthe public exports

Edit colors.ts, tokens.ts and config.ts freely. The other files are the Theme's runtime.

Tokens

A Token is a named visual value: a colour, spacing step, radius, type step, shadow or motion value. All values in tokens.ts are base values; the Theme adapts them to each device with the Scale.

Colour Roles

A Colour Role is a colour Token with a fixed purpose, defined once for light and once for dark, in hex.

  • Paired with a …Foreground role for content drawn on top: background, card, popover, primary, secondary, muted, accent, destructive, success, warning, info. For example, primary and primaryForeground.
  • Unpaired: border, input, ring, overlay.

The Accent Colour of your Preset sets primary and ring. Every role and its Foreground pair meets WCAG AA contrast (4.5:1) in both Schemes.

To add your own role, such as brand, add brand and brandForeground to both the light and dark objects. The two must keep identical keys. Types are inferred from your files, so the new role type-checks immediately.

Spacing

A 4px grid, named like Tailwind's steps.

Step0.51234568101216
Value2481216202432404864

Radius

Every step derives from one base, which your Preset's Radius sets (10 by default).

Stepsmmdlgxl2xlfull
Value681014189999

Components also use borderCurve: 'continuous' for smooth corners.

Type ramp

The Text Component's Variants. Weight is chosen by font face, never with fontWeight. display and h1–h4 use the Heading Font; the rest use the Body Font.

VariantSize / line heightFaceLetter spacing
display36/40Bold-0.8
h130/36Bold-0.6
h224/32SemiBold-0.4
h320/28SemiBold-0.2
h417/24SemiBold0
lead18/28Regular0
body16/24Regular0
label14/20Medium0
small14/20Regular0
caption12/16Regular0
button15/20SemiBold0

Elevation, motion and controls

  • Elevation: three boxShadow levels with light and dark values: sm (Card), md (raised and pressed surfaces) and lg (Toast and overlays).
  • Motion: durations fast 150ms, base 250ms and slow 400ms; easings standard, enter and exit; Reanimated spring presets snappy and gentle.
  • Control heights: sm 36, md 44, lg 52. The minimum touch target is 48 on both platforms; smaller controls extend their tap area with hitSlop.
  • Borders: default 1, and hairline for separators only.
  • Opacity: disabled 0.5.

Scale

The Scale adapts Tokens to the current device:

Scale = clamp(shorter screen side ÷ 390, 0.85, 1.25)
  • It is measured against a 390pt baseline phone. Using the shorter side means rotating the device doesn't change it.
  • It is recomputed whenever the window changes (foldables, split screen).
  • Scaled values are rounded to the nearest pixel.
ScalesDoesn't scale
spacingthe 48 minimum touch target
radius (except full)border widths and hairlines
control heightsshadows
text size, line height, letter spacingmotion
icon sizesopacity

The Theme hands Components already-scaled Tokens. For a genuine one-off value in your own code, use t.scaleValue(n) (from useTheme()) instead of a raw number; t.scale is the zoom factor itself. The unscaled base Tokens stay exported.

Phones are the 0.1 target. Tablets work at the 1.25 cap, and Container caps content width.

Font scaling

System font scaling is off by default: Text ignores the OS font-size setting, and nativecn adapts type with the Scale instead, so Components look as designed on every device. Set fontScaling: true in config.ts to turn it back on. Text size then becomes base × Scale × OS font scale, with the OS factor capped at 1.5× for the button, label and caption Variants. This is a whole-app accessibility decision.

Scheme

The Scheme is whether the light or dark Colour Role values are in use.

  • ThemeProvider takes scheme: 'system' | 'light' | 'dark', defaulting to defaultScheme in config.ts.
  • useTheme() returns the scaled Tokens plus scheme, scale (the zoom factor), scaleValue(n) and setScheme().
  • setScheme() also sets the native appearance, so system UI such as the keyboard follows.
  • The choice is persisted with zustand and AsyncStorage under the nativecn-theme key. Only the Scheme is persisted.
  • The splash screen stays up until the persisted Scheme and the fonts have loaded, so the wrong Scheme never flashes.
  • useNavigationTheme() feeds Expo Router's navigation theme, and the status bar follows the Scheme.

For a user-facing switch, add the SchemeSwitcher Component:

npx nativecn-cli@latest add scheme-switcher

ThemeProvider

ThemeProvider is required. It wraps your root Layout (src/app/_layout.tsx), which create sets up for you and init prints a snippet for. useTheme() outside it throws a development error that points at the root Layout.

createStyles

createStyles builds a Component's styles from the Theme and returns a hook:

import { createStyles } from "@/theme";

const useStyles = createStyles((t) => ({
  card: {
    padding: t.spacing[4],
    borderRadius: t.radius.lg,
    backgroundColor: t.colors.card,
  },
}));

export function ProfileCard() {
  const styles = useStyles();
  // ...
}

Styles are built with StyleSheet.create once per Scale and Scheme and cached, never rebuilt on each render. Colours live in the same cached styles, and Variant styles are extra keys. Never style from React Native's useColorScheme() directly; read the Theme.

Fonts

Your Preset picks a Body Font and a Heading Font from eight OFL fonts: Inter, Geist, DM Sans, Figtree, Lora, Source Serif 4, Geist Mono and JetBrains Mono.

  • Each font is four static faces (Regular, Medium, SemiBold, Bold) downloaded from nativecn.dev into assets/fonts/. Only the chosen fonts are downloaded.
  • They are embedded by the expo-font Config Plugin in app.json, and named in tokens.ts. ThemeProvider also loads them with useFonts, which is what makes Expo Go work.
  • Each font carries a small letter-spacing correction for the type ramp.
  • After changing fonts, rebuild the native app; a reload is not enough.

nativecn never uses @expo-google-fonts packages. To use another font, add its four faces to assets/fonts/, update the expo-font plugin entry and the font names in tokens.ts, and check that its licence allows bundling.