Skip to content

Guides

React Native & Expo

formkrafter-react-native renders the same form specs as the web packages with native components — no WebView, no Web Components. Validation, rules, option sources and the recap walk all come from formkrafter-core, so a spec built in the web builder renders on the phone unchanged.

It is a renderer: there is no mobile builder, by design.

Terminal window
npx expo install @streamline-pulse/formkrafter-react-native

React ≥18 and React Native ≥0.76 are peer dependencies. Nothing else is required for the core bricks — the date, file and signature bricks each live behind an opt-in entry point.

  1. Wrap your app in the theme provider:

    import { FkThemeProvider } from '@streamline-pulse/formkrafter-react-native'
    <FkThemeProvider theme={colorScheme === 'dark' ? 'dark' : 'light'}>
    <App />
    </FkThemeProvider>
  2. Render a spec:

    import { useRef } from 'react'
    import { FormRenderer } from '@streamline-pulse/formkrafter-react-native'
    import type { FormRendererHandle } from '@streamline-pulse/formkrafter-react-native'
    function Checkout({ spec }) {
    const form = useRef<FormRendererHandle>(null)
    return (
    <FormRenderer
    ref={form}
    spec={spec}
    onDataChange={(data, isValid) => console.log(data, isValid)}
    onSubmit={(data, isValid) => isValid && send(data)}
    />
    )
    }
  3. Validate on demand through the ref:

    const result = form.current?.validate()
    // { valid: boolean, errors: { fieldKey: message, … } }

A wizard spec (panel:stepper) renders with per-step validation and its own submit button, which calls your onSubmit — no extra wiring. Grid rows count in the global verdict.

FkThemeProvider accepts 'light', 'dark' or a full token table — the same values the web exposes as CSS custom properties:

import { fkDarkTheme } from '@streamline-pulse/formkrafter-react-native'
<FkThemeProvider theme={{ ...fkDarkTheme, colorPrimary: '#e0662d' }}>

Two independent knobs, identical to the web:

  • Chrome strings (stepper buttons, grid actions, select search…) come from the shared store: setFkTranslations(frFkTranslations) from formkrafter-core.
  • Field labels written as { en: 'Full name', fr: 'Nom complet' } resolve through the locale prop on FormRenderer.

Switch both without remounting — the entered data stays.

A reviewer reads a filled form and flags what is wrong; the applicant then corrects only those fields. Both halves exist natively, with the same annotation map as the web:

import { FormReview, FormRenderer } from '@streamline-pulse/formkrafter-react-native'
import { flaggedKeys } from '@streamline-pulse/formkrafter-core'
// The reviewer's screen: read-only, an "Incorrect" switch and a comment box under each field.
<FormReview spec={spec} data={submitted} notes={previousReviews}
onReviewChange={(reviews, flagged) => save(reviews)} />
// The applicant's screen: notes under the flagged fields, everything else locked.
<FormRenderer spec={spec} data={submitted} annotations={reviews} editableKeys={flaggedKeys(reviews)} />

notes shows an earlier pass as a muted reminder so the next reviewer looks at those fields again. Custom bricks that render through the exported Field get the notes and controls for free; others read annotation, note and review from their props. A data grid is reviewed as one field natively.

Metro resolves imports statically, so bricks that depend on a native module live on their own entry points. Install the module, call the registration once at startup; applications that skip a brick never resolve its module.

Entry point Native module Registration
…/date @react-native-community/datetimepicker registerNativeDateBricks()
…/file expo-document-picker registerNativeFileBrick()
…/signature react-native-svg registerNativeSignatureBrick()
import { registerNativeDateBricks } from '@streamline-pulse/formkrafter-react-native/date'
registerNativeDateBricks()

File uploads go through core’s fileUploadService — the same injection point as the web — and the signature stores an image/svg+xml data URL where the web’s canvas produces PNG.

The registry is the extension point. A brick receives data, error, disabled, resolved configs, onDataChange and, during a review, annotation, note and review, and renders whatever it wants — gluestack, NativeBase, Tamagui or your own components:

import {
createNativeBrick,
registerNativeBrick,
} from '@streamline-pulse/formkrafter-react-native'
registerNativeBrick(
createNativeBrick({
type: 'input',
id: 'rating',
render: (props) => (
<Stars value={props.data} onChange={props.onDataChange} error={props.error} />
),
}),
)

Register before the first render to replace a built-in — the defaults never clobber an existing registration. Bricks without a native renderer show an explicit placeholder rather than failing.

A project by Streamline Pulse