Field

A collection of components for building accessible form fields and field groups.

Field is the React Native implementation shipped by the native-field registry item. The component adapts the registry's web-facing composition to native primitives and touch-based interaction.

The native Field collection provides the layout and supporting text needed to build structured forms. It separates field layout from the actual input control, making it possible to compose inputs, labels, descriptions, validation messages, and separators consistently.

FieldSet additionally provides a keyboard-aware scroll container for forms, while FieldError can derive its message content from an array of validation errors.

See the shadcn/ui Field documentation for the web component API, concepts, and additional examples.

Installation

CLI

npx shadcn add https://ui.tiesen.id.vn/r/native-field.json

Manual

Copy and paste the following code into your project.

import type { VariantProps } from 'class-variance-authority'import { cva } from 'class-variance-authority'import { useMemo } from 'react'import { KeyboardAvoidingView, Platform, View, ScrollView } from 'react-native'import { cn } from '@/lib/utils'import { Separator } from '@/components/native/separator'import { Typography } from '@/components/native/typography'function FieldSet({  className,  containerClassName,  ...props}: React.ComponentProps<typeof ScrollView> & {  containerClassName?: string}) {  return (    <KeyboardAvoidingView      data-slot='field-set'      className={cn('flex-1', containerClassName)}      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}      keyboardVerticalOffset={Platform.OS === 'ios' ? 90 : 0}    >      <ScrollView        data-slot='field-set-content'        keyboardShouldPersistTaps='handled'        showsVerticalScrollIndicator={false}        contentContainerClassName={cn('grow flex-col gap-4', className)}        {...props}      />    </KeyboardAvoidingView>  )}function FieldLegend({  className,  ...props}: Omit<React.ComponentProps<typeof Typography>, 'variant'>) {  return (    <Typography      data-slot='field-legend'      className={cn('mb-1.5 text-base font-medium', className)}      {...props}    />  )}function FieldGroup({  className,  ...props}: React.ComponentProps<typeof View>) {  return (    <View      data-slot='field-group'      className={cn(        'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',        className      )}      {...props}    />  )}const fieldVariants = cva(  'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',  {    variants: {      orientation: {        vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',        horizontal:          'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',        responsive:          'flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',      },    },    defaultVariants: {      orientation: 'vertical',    },  })function Field({  className,  orientation = 'vertical',  ...props}: React.ComponentProps<typeof View> & VariantProps<typeof fieldVariants>) {  return (    <View      // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role      role='group'      data-slot='field'      data-orientation={orientation}      className={cn(fieldVariants({ orientation }), className)}      {...props}    />  )}function FieldContent({  className,  ...props}: React.ComponentProps<typeof View>) {  return (    <View      data-slot='field-content'      className={cn(        'group/field-content flex flex-1 flex-col gap-0.5 leading-snug',        className      )}      {...props}    />  )}function FieldLabel({  className,  ...props}: Omit<React.ComponentProps<typeof Typography>, 'variant'>) {  return (    <Typography      data-slot='field-label'      className={cn(        'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 group-data-disabled:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',        className      )}      {...props}    />  )}function FieldTitle({  className,  ...props}: Omit<React.ComponentProps<typeof Typography>, 'variant'>) {  return (    <Typography      data-slot='field-label'      className={cn(        'flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50',        className      )}      {...props}    />  )}function FieldDescription({  className,  ...props}: Omit<React.ComponentProps<typeof Typography>, 'variant'>) {  return (    <Typography      data-slot='field-description'      className={cn(        'text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5',        'last:mt-0 nth-last-2:-mt-1',        '[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',        className      )}      {...props}    />  )}function FieldSeparator({  children,  className,  ...props}: React.ComponentProps<typeof View> & { children?: React.ReactNode }) {  return (    <View      data-slot='field-separator'      data-content={!!children}      className={cn(        'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',        className      )}      {...props}    >      <Separator className='absolute inset-0 top-1/2' />      {children && (        <View          className='relative mx-auto block w-fit bg-background px-2 text-muted-foreground'          data-slot='field-separator-content'        >          {children}        </View>      )}    </View>  )}function FieldError({  children,  errors,  ...props}: React.ComponentProps<typeof View> & {  errors?: ({ message?: string } | undefined)[]}) {  const content = useMemo(() => {    if (children) return children    if (!errors?.length) return null    const uniqueErrors = [      ...new Map(errors.map((error) => [error?.message, error])).values(),    ]    if (uniqueErrors?.length === 1)      return (        <Typography className='text-sm font-normal text-destructive'>          {uniqueErrors[0]?.message}        </Typography>      )    return (      <View className='flex flex-col gap-1'>        {uniqueErrors.map(          (error, index) =>            error?.message && (              <Typography                key={index}                className='text-sm font-normal text-destructive'              >                {error.message}              </Typography>            )        )}      </View>    )  }, [children, errors])  if (!content) return null  return (    <View role='alert' data-slot='field-error' {...props}>      {content}    </View>  )}export {  Field,  FieldLabel,  FieldDescription,  FieldError,  FieldGroup,  FieldLegend,  FieldSeparator,  FieldSet,  FieldContent,  FieldTitle,}

Features

  • Vertical, horizontal, and responsive field layouts.
  • Field labels, titles, descriptions, and error messages.
  • Keyboard-aware FieldSet form container.
  • Field groups for spacing multiple fields.
  • Optional separators with inline content.
  • Automatic deduplication of repeated validation messages.

Registry dependencies: This component depends on native-separator and native-typography.

Usage

import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
  FieldSet,
} from '@/components/native/field'
import { Input } from '@/components/native/input'

export function FieldExample() {
  return (
    <FieldSet>
      <FieldGroup>
        <Field>
          <FieldLabel>Email</FieldLabel>
          <Input placeholder='name@example.com' />
          <FieldDescription>
            We'll never share your email.
          </FieldDescription>
        </Field>

        <Field>
          <FieldLabel>Password</FieldLabel>
          <Input secureTextEntry />
          <FieldError errors={[{ message: 'Password is required.' }]} />
        </Field>
      </FieldGroup>
    </FieldSet>
  )
}

Orientation

Field supports three layout modes:

<Field orientation='vertical'>...</Field>
<Field orientation='horizontal'>...</Field>
<Field orientation='responsive'>...</Field>

Use horizontal for compact settings rows and vertical for standard mobile forms.

Field errors

FieldError can render explicit children or derive content from an errors array.

<FieldError
  errors={[
    { message: 'Email is invalid.' },
    { message: 'Email is invalid.' },
    { message: 'Email is already registered.' },
  ]}
/>

Duplicate messages are removed before rendering.

Field separators

Use FieldSeparator to visually divide groups of controls. Optional children are rendered as a label over the separator.

<FieldSeparator>Or continue with</FieldSeparator>

Keyboard-aware forms

FieldSet is backed by a React Native ScrollView and KeyboardAvoidingView. This is useful for forms where the keyboard would otherwise cover lower fields.

API Reference

FieldSet

Keyboard-aware scroll container for grouped fields.

PropTypeDefault
containerClassNamestring—
classNamestring—
...propsReact.ComponentProps<typeof ScrollView>—

FieldGroup

Layout container for related fields.

PropTypeDefault
classNamestring—
childrenReact.ReactNode—
...propsReact.ComponentProps<typeof View>—

Field

PropTypeDefault
orientation"vertical" | "horizontal" | "responsive""vertical"
classNamestring—
childrenReact.ReactNode—
...propsReact.ComponentProps<typeof View>—

FieldContent

Container for the main field content.

PropTypeDefault
classNamestring—
childrenReact.ReactNode—
...propsReact.ComponentProps<typeof View>—

FieldLabel

Typography wrapper intended for form labels.

PropTypeDefault
classNamestring—
childrenReact.ReactNode—
...propsReact.ComponentProps<typeof Text>—

FieldTitle

Typography wrapper for a compact field title.

PropTypeDefault
classNamestring—
childrenReact.ReactNode—
...propsReact.ComponentProps<typeof Text>—

FieldDescription

Typography wrapper for supporting field text.

PropTypeDefault
classNamestring—
childrenReact.ReactNode—
...propsReact.ComponentProps<typeof Text>—

FieldSeparator

Visual separator between field groups.

PropTypeDefault
childrenReact.ReactNode—
classNamestring—
...propsReact.ComponentProps<typeof View>—

FieldError

Validation error output.

PropTypeDefault
errors({ message?: string } | undefined)[]—
childrenReact.ReactNode—
classNamestring—
...propsReact.ComponentProps<typeof View>—

If children is supplied, it is rendered instead of derived error messages.

On this page