Native Forms

Field differences

How native form fields differ from their web counterparts — component mappings, styling, and platform-specific behavior.


Text inputs

Native text fields use React Native's TextInput component instead of HTML <input> elements.

WebNative
<input type="text"><TextInput>
<input type="email"><TextInput keyboardType="email-address">
<input type="password"><TextInput secureTextEntry>
<input type="url"><TextInput keyboardType="url">
<textarea><TextInput multiline>

Keyboard types

Native text fields automatically set the appropriate keyboardType:

Field typeKeyboard type
textdefault
emailemail-address
urlurl
phonephone-pad
numbernumeric
currencydecimal-pad

Select and dropdown fields

Web uses native HTML <select> elements or custom dropdowns. Native uses react-native-element-dropdown for a consistent cross-platform dropdown experience.

Behavior differences

  • Web: Opens a native browser dropdown or a custom styled dropdown
  • Native: Opens a bottom sheet or modal with a scrollable list

Search selects

On native, search selects use react-native-element-dropdown's built-in search functionality. The search input appears at the top of the dropdown modal.


Checkbox fields

Web uses HTML <input type="checkbox">. Native uses expo-checkbox.

npx expo install expo-checkbox

The Switch field uses React Native's built-in Switch component on both platforms — no additional dependency needed.


Date and time pickers

Web renders standard HTML date/time inputs or a custom date picker component. Native uses @react-native-community/datetimepicker.

Platform behavior

PlatformBehavior
iOSInline spinner-style picker
AndroidModal dialog picker
npm install @react-native-community/datetimepicker

Phone input

Web renders a custom phone input with country code selector. Native uses react-native-phone-number-input.

npm install react-native-phone-number-input

Markdown editor

The markdown editor behaves differently on each platform:

PlatformBehavior
WebFull MDX Editor with toolbar, live preview, and image upload
NativeText input for markdown with react-native-markdown-display for preview

The web markdown editor is significantly more feature-rich. On native, consider using a textArea field for simple rich text needs.


Theming differences

Web theme

The web theme (tailwindTheme) uses Tailwind CSS class strings:

const tailwindTheme = {
  input:
    'border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-sky-500',
  label: 'block text-sm font-medium text-gray-700 mb-1',
  error: 'text-red-500 text-sm mt-1',
  // ...
}

Native theme

The native theme (NativeTheme) uses React Native StyleSheet objects:

const NativeTheme = {
  input: {
    borderWidth: 1,
    borderColor: '#D1D5DB',
    borderRadius: 6,
    paddingHorizontal: 12,
    paddingVertical: 8,
  },
  label: {
    fontSize: 14,
    fontWeight: '500',
    color: '#374151',
    marginBottom: 4,
  },
  error: {
    color: '#EF4444',
    fontSize: 12,
    marginTop: 4,
  },
  // ...
}

Custom native themes

import { NativeForm, createCustomTheme } from '@nestledjs/forms-native'

const myTheme = createCustomTheme({
  input: {
    borderColor: '#6366F1',
    borderRadius: 12,
  },
  label: {
    color: '#1E1B4B',
    fontWeight: '600',
  },
})

<NativeForm id="form" fields={fields} submit={save} theme={myTheme} />

Feature parity table

FeatureWebNative
All 24+ field typesYesYes
Declarative APIYesYes
Imperative APIYesYes
Conditional logicYesYes
Validation (sync/async/Zod)YesYes
Cross-field validationYesYes
Validation groupsYesYes
Read-only modeYesYes
Custom themesYes (CSS classes)Yes (StyleSheet)
Apollo search selectsYesYes
Markdown editor (full)YesLimited
Custom field componentsYesYes
Form context hooksYesYes
submitTransformYesYes

Sharing field definitions

The recommended pattern for cross-platform apps is to define fields in a shared module:

// packages/shared/src/form-fields.ts
import { FormFieldClass } from '@nestledjs/forms-core'

export const profileFields = [
  FormFieldClass.text('displayName', {
    label: 'Display Name',
    required: true,
    validate: (v) => v.length >= 2 || 'Too short',
  }),
  FormFieldClass.email('email', {
    label: 'Email',
    required: true,
  }),
  FormFieldClass.phone('phone', {
    label: 'Phone',
  }),
  FormFieldClass.select('timezone', {
    label: 'Timezone',
    options: timezones,
  }),
]

Then import in each platform:

// apps/web/src/ProfileForm.tsx
import { Form } from '@nestledjs/forms'
import { profileFields } from '@shared/form-fields'

// apps/mobile/src/ProfileForm.tsx
import { NativeForm } from '@nestledjs/forms-native'
import { profileFields } from '@shared/form-fields'

The field definitions are 100% portable. Only the rendering component changes.

Previous
Overview