React Native

The same inputs and JSON forms for Expo apps on iOS and Android.

  • Text inputs with a verified email and a strong password
  • Languages sheet with 3 of 3 chosen
  • Stay calendar with 3 nights picked
  • Household review with an income total of 77,500
Requirements
  • Expo SDK 58With Expo Router
  • A src/ folderWith @/ mapped to src/
Install the core
  1. 1

    Set up Uniwind

    Start Metro once afterwards. Uniwind writes src/uniwind-types.d.ts, which types className.

    Terminal
    npx expo install uniwind tailwindcss
    Terminal
    // metro.config.jsconst { getDefaultConfig } = require("expo/metro-config")const { withUniwindConfig } = require("uniwind/metro")
    module.exports = withUniwindConfig(getDefaultConfig(__dirname), {  cssEntryFile: "./src/global.css",  dtsFile: "./src/uniwind-types.d.ts",})
  2. 2

    Add the theme

    Replace src/global.css. Colors switch with the system light or dark mode.

    src/global.css
    @import "tailwindcss";@import "uniwind";
    @theme {  --font-sans: "Plus Jakarta Sans";  --font-mono: "JetBrains Mono";
      --radius-xs: 4px;  --radius-sm: 6px;  --radius-md: 8px;  --radius-lg: 10px;  --radius-xl: 14px;
      --text-large-title: 34px;  --text-large-title--line-height: 40px;  --text-title: 22px;  --text-title--line-height: 28px;  --text-headline: 17px;  --text-headline--line-height: 22px;  --text-body: 17px;  --text-body--line-height: 22px;  --text-subhead: 15px;  --text-subhead--line-height: 20px;  --text-footnote: 13px;  --text-footnote--line-height: 18px;  --text-caption: 12px;  --text-caption--line-height: 16px;}
    @layer theme {  :root {    @variant light {      --color-background: oklch(0.995 0.002 270);      --color-foreground: oklch(0.17 0.02 275);      --color-card: oklch(1 0 0);      --color-card-foreground: oklch(0.17 0.02 275);      --color-popover: oklch(1 0 0);      --color-popover-foreground: oklch(0.17 0.02 275);      --color-primary: oklch(0.511 0.23 277);      --color-primary-foreground: oklch(0.985 0.01 275);      --color-secondary: oklch(0.962 0.018 275);      --color-secondary-foreground: oklch(0.4 0.18 277);      --color-muted: oklch(0.966 0.005 270);      --color-muted-foreground: oklch(0.52 0.02 270);      --color-accent: oklch(0.955 0.02 275);      --color-accent-foreground: oklch(0.36 0.16 277);      --color-destructive: oklch(0.577 0.245 27.325);      --color-success: oklch(0.6 0.15 155);      --color-border: oklch(0.92 0.008 270);      --color-input: oklch(0.9 0.01 270);      --color-ring: oklch(0.585 0.233 277);    }
        @variant dark {      --color-background: oklch(0.16 0.012 275);      --color-foreground: oklch(0.97 0.005 275);      --color-card: oklch(0.205 0.014 275);      --color-card-foreground: oklch(0.97 0.005 275);      --color-popover: oklch(0.205 0.014 275);      --color-popover-foreground: oklch(0.97 0.005 275);      --color-primary: oklch(0.673 0.182 277);      --color-primary-foreground: oklch(0.17 0.04 277);      --color-secondary: oklch(0.27 0.05 277);      --color-secondary-foreground: oklch(0.87 0.065 275);      --color-muted: oklch(0.25 0.012 275);      --color-muted-foreground: oklch(0.71 0.018 275);      --color-accent: oklch(0.28 0.055 277);      --color-accent-foreground: oklch(0.88 0.06 275);      --color-destructive: oklch(0.704 0.191 22.216);      --color-success: oklch(0.72 0.15 155);      --color-border: oklch(1 0 0 / 10%);      --color-input: oklch(1 0 0 / 15%);      --color-ring: oklch(0.585 0.233 277);    }  }}
  3. 3

    Add components.json

    At the project root, so the shadcn CLI knows where files go.

    components.json
    {  "$schema": "https://ui.shadcn.com/schema.json",  "style": "new-york",  "rsc": false,  "tsx": true,  "tailwind": {    "config": "",    "css": "src/global.css",    "baseColor": "neutral",    "cssVariables": true  },  "aliases": {    "components": "@/components",    "utils": "@/utils/cn",    "lib": "@/lib",    "hooks": "@/hooks"  }}
  4. 4

    Add the packages

    expo install picks versions for your SDK.

    Terminal
    npx expo install @tanstack/react-form zod libphonenumber-js date-fns @zxcvbn-ts/core @zxcvbn-ts/language-common @zxcvbn-ts/language-en lucide-react-native react-native-svg @gorhom/bottom-sheet react-native-gesture-handler react-native-reanimated react-native-keyboard-controller react-native-safe-area-context tailwind-merge @formatjs/intl-getcanonicallocales @formatjs/intl-locale @formatjs/intl-displaynames @formatjs/intl-listformat react-native-url-polyfill
  5. 5

    Add the core

    Form, sheets, rows and the schema helpers.

    bunx --bun shadcn@latest add https://fieldkit.muturi.dev/r/native/form.json
  6. 6

    Load the polyfills

    Before Expo Router starts.

    Terminal
    // index.ts, then set "main": "index.ts" in package.jsonimport "@/polyfills"import "expo-router/entry"
  7. 7

    Wrap the app

    Terminal
    // src/app/_layout.tsximport "@/global.css"
    import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"import { Stack } from "expo-router"import { GestureHandlerRootView } from "react-native-gesture-handler"import { KeyboardProvider } from "react-native-keyboard-controller"
    import { ToastProvider } from "@/components/fieldkit/toast"
    export default function RootLayout() {  return (    <GestureHandlerRootView style={{ flex: 1 }}>      <KeyboardProvider>        <BottomSheetModalProvider>          <ToastProvider>            <Stack />          </ToastProvider>        </BottomSheetModalProvider>      </KeyboardProvider>    </GestureHandlerRootView>  )}
Add inputs
Props and schema rules match the web inputs.
bunx --bun shadcn@latest add https://fieldkit.muturi.dev/r/native/form-email.json https://fieldkit.muturi.dev/r/native/form-phone.json
Usage
newsletter-form.tsx
import { z } from "zod"
import { Form } from "@/components/form/form"import { FormEmail } from "@/components/form/form-email"import { FormSubmit } from "@/components/form/form-submit"import { emailField } from "@/lib/field-schemas"
const schema = z.object({ email: emailField() })
export function NewsletterForm() {  return (    <Form schema={schema} defaultValues={{ email: "" }} onSubmit={save}>      <FormEmail name="email" />      <FormSubmit>Subscribe</FormSubmit>    </Form>  )}
JSON forms
Steps, show if, repeat sections and sum of, from the same spec as the web.
bunx --bun shadcn@latest add https://fieldkit.muturi.dev/r/native/json-form.json
household.tsx
import { JsonForm } from "@/components/form/json-form"
export function Household() {  return <JsonForm spec={householdSpec} review guard onSubmit={send} />}
What changes on mobile
  • Lists, dates and file sources open in bottom sheets, not popovers.
  • Inputs set the keyboard, autofill and Next key, and the field moves into view above the keyboard.
  • FormFile picks from the camera, photos or files. Use nativeFileField in place of fileField.
  • FormLeaveGuard asks in a sheet when the user goes back with unsaved changes.
  • Submit checks every field first, even while the keyboard is open.

Search docs

Find a component, input or example