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.jsonManual
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
FieldSetform 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-separatorandnative-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.
| Prop | Type | Default |
|---|---|---|
containerClassName | string | — |
className | string | — |
...props | React.ComponentProps<typeof ScrollView> | — |
FieldGroup
Layout container for related fields.
| Prop | Type | Default |
|---|---|---|
className | string | — |
children | React.ReactNode | — |
...props | React.ComponentProps<typeof View> | — |
Field
| Prop | Type | Default |
|---|---|---|
orientation | "vertical" | "horizontal" | "responsive" | "vertical" |
className | string | — |
children | React.ReactNode | — |
...props | React.ComponentProps<typeof View> | — |
FieldContent
Container for the main field content.
| Prop | Type | Default |
|---|---|---|
className | string | — |
children | React.ReactNode | — |
...props | React.ComponentProps<typeof View> | — |
FieldLabel
Typography wrapper intended for form labels.
| Prop | Type | Default |
|---|---|---|
className | string | — |
children | React.ReactNode | — |
...props | React.ComponentProps<typeof Text> | — |
FieldTitle
Typography wrapper for a compact field title.
| Prop | Type | Default |
|---|---|---|
className | string | — |
children | React.ReactNode | — |
...props | React.ComponentProps<typeof Text> | — |
FieldDescription
Typography wrapper for supporting field text.
| Prop | Type | Default |
|---|---|---|
className | string | — |
children | React.ReactNode | — |
...props | React.ComponentProps<typeof Text> | — |
FieldSeparator
Visual separator between field groups.
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
className | string | — |
...props | React.ComponentProps<typeof View> | — |
FieldError
Validation error output.
| Prop | Type | Default |
|---|---|---|
errors | ({ message?: string } | undefined)[] | — |
children | React.ReactNode | — |
className | string | — |
...props | React.ComponentProps<typeof View> | — |
If children is supplied, it is rendered instead of derived error messages.