AniUI

Field

Compound form field with label, description, and error message support.

We'll never share your email.

Web preview — components render natively on iOS & Android
import { Field, FieldLabel, FieldDescription, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function MyScreen() {
  return (
    <Field>
      <FieldLabel>Email</FieldLabel>
      <Input placeholder="you@example.com" />
      <FieldDescription>We'll never share your email.</FieldDescription>
    </Field>
  );
}

Installation#

npx @aniui/cli add field

Usage#

app/index.tsx
import { Field, FieldLabel, FieldDescription, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function MyScreen() {
  return (
    <Field>
      <FieldLabel>Email</FieldLabel>
      <Input placeholder="you@example.com" />
      <FieldDescription>We'll never share your email.</FieldDescription>
    </Field>
  );
}

Horizontal Layout#

As it appears on your ID.

Web preview — components render natively on iOS & Android
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function MyScreen() {
  return (
    <Field orientation="horizontal">
      <FieldLabel>Name</FieldLabel>
      <Input placeholder="John Doe" />
    </Field>
  );
}

Error State#

Password must be at least 8 characters.

Web preview — components render natively on iOS & Android
import { Field, FieldLabel, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function MyScreen() {
  return (
    <Field>
      <FieldLabel>Email</FieldLabel>
      <Input placeholder="you@example.com" />
      <FieldError errors={["Email is required", "Must be a valid email"]} />
    </Field>
  );
}

Components#

Field is a compound component made up of several parts:

ComponentDescription
Field

Root component that provides orientation context. Wraps label, input, description, and error.

FieldLabel

Styled label text. Adjusts layout based on orientation.

FieldDescription

Helper text displayed below the input.

FieldError

Displays one or more error messages. Renders nothing when no errors are present.

Props#

Field#

PropTypeDefault
orientation
"vertical" | "horizontal"
"vertical"
className
string
-
children
React.ReactNode
-

Also accepts all View props from React Native.

FieldLabel#

PropTypeDefault
className
string
-

Also accepts all Text props from React Native.

FieldDescription#

PropTypeDefault
className
string
-

Also accepts all Text props from React Native.

FieldError#

PropTypeDefault
errors
string[]
-
className
string
-
children
React.ReactNode
-

Also accepts all View props from React Native.

Accessibility#

  • FieldLabel uses accessibilityRole="text" for screen readers.
  • FieldError uses accessibilityRole="alert" to announce errors.
  • Horizontal layout maintains minimum touch target with min-w-[100px] on the label.

Source#

components/ui/field.tsx
import React, { createContext, useContext } from "react";
import { View, Text } from "react-native";
import { cn } from "@/lib/utils";

type Orientation = "vertical" | "horizontal";

const FieldContext = createContext<{ orientation: Orientation }>({
  orientation: "vertical",
});

export interface FieldProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  orientation?: Orientation;
  children?: React.ReactNode;
}

export function Field({ orientation = "vertical", className, children, ...props }: FieldProps) {
  return (
    <FieldContext.Provider value={{ orientation }}>
      <View
        className={cn(
          orientation === "vertical" ? "gap-1.5" : "flex-row items-start gap-3",
          className
        )}
        {...props}
      >
        {children}
      </View>
    </FieldContext.Provider>
  );
}

export interface FieldLabelProps extends React.ComponentPropsWithoutRef<typeof Text> {
  className?: string;
}

export function FieldLabel({ className, ...props }: FieldLabelProps) {
  const { orientation } = useContext(FieldContext);
  return (
    <Text
      className={cn(
        "text-sm font-medium text-foreground",
        orientation === "horizontal" && "min-w-[100px] pt-3",
        className
      )}
      accessibilityRole="text"
      {...props}
    />
  );
}

export interface FieldDescriptionProps extends React.ComponentPropsWithoutRef<typeof Text> {
  className?: string;
}

export function FieldDescription({ className, ...props }: FieldDescriptionProps) {
  return (
    <Text className={cn("text-xs text-muted-foreground", className)} {...props} />
  );
}

export interface FieldErrorProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  errors?: string[];
  children?: React.ReactNode;
}

export function FieldError({ className, errors, children, ...props }: FieldErrorProps) {
  if (!errors?.length && !children) return null;

  return (
    <View className={cn("gap-0.5", className)} accessibilityRole="alert" {...props}>
      {errors?.map((err, i) => (
        <Text key={i} className="text-sm text-destructive">{err}</Text>
      ))}
      {children}
    </View>
  );
}