---
title: FormField
description: Form field wrapper providing label, description, and validation message layout.
sidebar:
  order: 2
search:
  tags: [label, validation, errors, field]
---

# FormField

> Form field wrapper providing label, description, and validation message layout.

## Import

```tsx
import { createForm, Form, FormField } from 'moraine'
```

Inside `Form`, a named field automatically reads its value and validation state from Formisch.
Top-level fields accept a string name; nested and array fields use a path such as
`['users', 0, 'email']`. Outside `Form`, the component remains a standalone layout primitive.

Pass the schema type to validate names at compile time:

```tsx
<FormField<typeof schema> name={['profile', 'email']} label="Email">
  <Input />
</FormField>
```

## Slot Structure

Label, description, and hint area above a control slot with validation feedback.

```text
root
├── wrapper
│   ├── labelWrapper (optional)
│   │   ├── label
│   │   └── hint (optional)
│   └── description (optional)
└── {children}
    ├── help (optional)
    └── error (optional)
```

## Examples

### Basic

Label, hint, description, and help text with a single control.

```tsx
function Basic() {
  return (
    <div class="mx-auto max-w-xl w-full">
      <FormField
        label="Workspace Name"
        hint="Required"
        description="Name used in URLs and workspace-level permissions."
        help="Use lowercase letters, numbers, and dashes."
        required
      >
        <Input placeholder="acme-platform" />
      </FormField>
    </div>
  )
}
```

### With Validation

Bind to a Formisch store and show validation feedback without a render prop.

```tsx
function WithValidation() {
  const form = createForm({
    schema: v.object({ email: v.pipe(v.string(), v.email('Enter a valid email.')) }),
    initialInput: { email: '' },
  })

  return (
    <Form of={form} class="mx-auto max-w-xl w-full space-y-4">
      <FormField name="email" label="Owner Email" required>
        <Input type="email" placeholder="owner@acme.dev" />
      </FormField>

      <Button type="submit">Save</Button>
    </Form>
  )
}
```

### Horizontal Layout

Use `orientation="horizontal"` for form-like row layouts.

```tsx
function HorizontalLayout() {
  return (
    <div class="mx-auto max-w-2xl w-full space-y-4">
      <FormField
        orientation="horizontal"
        label="Display Name"
        description="Public name shown in activity feeds."
      >
        <Input placeholder="Moraine Team" />
      </FormField>

      <FormField orientation="horizontal" label="Default Role" required>
        <Select
          options={[
            { label: 'Developer', value: 'developer' },
            { label: 'Designer', value: 'designer' },
            { label: 'Manager', value: 'manager' },
          ]}
          placeholder="Select role"
        />
      </FormField>
    </div>
  )
}
```

### Sizes

Preview the field typography scale from xs to xl.

```tsx
function Sizes() {
  const SIZES: FormFieldSizeName[] = ['xs', 'sm', 'md', 'lg', 'xl']

  type FormFieldSizeName = Exclude<FormFieldT.Variant['size'], undefined>

  return (
    <div class="gap-4 grid sm:grid-cols-2">
      <For each={SIZES}>
        {(size) => (
          <FormField
            size={size}
            label={`Workspace Name (${size})`}
            description="Name used in URLs and workspace-level permissions."
            help="Use lowercase letters, numbers, and dashes."
          >
            <Input size={size} placeholder={`acme-platform-${size}`} />
          </FormField>
        )}
      </For>
    </div>
  )
}
```

### Manual Error

Force error state with a custom error message.

```tsx
function ManualError() {
  return (
    <div class="mx-auto max-w-xl w-full">
      <FormField
        label="Access Token"
        hint="Required"
        error="Token has expired. Generate a new token and retry."
      >
        <Input type="password" value="********" />
      </FormField>
    </div>
  )
}
```

### Render Context

Use render-function children to react to `error` state.

```tsx
function RenderContext() {
  const form = createForm({
    schema: v.object({
      releaseTitle: v.pipe(v.string(), v.nonEmpty('Release title is required.')),
    }),
    initialInput: { releaseTitle: '' },
  })

  return (
    <Form of={form} class="mx-auto max-w-xl w-full space-y-4">
      <FormField name="releaseTitle" label="Release Title" required>
        {(props) => <Input placeholder={props.error ? 'Title is required' : 'v2.14.0'} />}
      </FormField>

      <Button type="submit">Create Draft</Button>
    </Form>
  )
}
```

### Nested Path

Use array path names for nested form fields.

```tsx
function NestedPath() {
  const form = createForm({
    schema: v.object({
      profile: v.object({
        name: v.pipe(v.string(), v.nonEmpty('Name is required.')),
        email: v.pipe(v.string(), v.email('Valid email is required.')),
      }),
    }),
    initialInput: { profile: { name: '', email: '' } },
  })

  return (
    <Form of={form} class="mx-auto max-w-xl w-full space-y-4">
      <FormField name={['profile', 'name']} label="Profile Name" required>
        <Input placeholder="Moraine Team" />
      </FormField>

      <FormField name={['profile', 'email']} label="Profile Email" required>
        <Input type="email" placeholder="team@acme.dev" />
      </FormField>

      <Button type="submit">Save Profile</Button>
    </Form>
  )
}
```

## API Reference

### Attributes

#### `root`

Field wrapper that links label, control, description, and messages.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-orientation | string \| undefined | Stores the rendered orientation. |

#### `wrapper`

Inner wrapper that arranges label, control, helper text, and messages.

#### `labelWrapper`

Row that groups the field label and optional hint.

#### `label`

Accessible field label associated with the control.

#### `container`

Region that contains the wrapped form control.

#### `description`

Helper text associated with the control.

#### `error`

Validation error message region for the field.

#### `hint`

Short hint rendered beside the field label.

#### `help`

Additional guidance rendered below the control.

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| as | T \| undefined | div | The HTML element or component to render as. |
| children | ComponentOrElement<FormFieldT.RenderContext> \| undefined | — | Children of the field, can be a render function. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | FormFieldT.Classes \| undefined | — | — |
| description | JSX.Element | — | Description text shown below the label. |
| error | boolean \| string \| JSX.Element | — | Custom error message or force error state. |
| help | JSX.Element | — | Help text shown below the control when no error is present. |
| hint | JSX.Element | — | Hint text shown near the label. |
| id | string \| undefined | — | Unique identifier for the form field. |
| label | JSX.Element | — | Label for the field. |
| name | FormFieldT.Name<TSchema> \| undefined | — | The name of the field (key in form state). |
| orientation | "horizontal" \| "vertical" \| undefined | — | — |
| ref | JSX.HTMLElementTags[T] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| required | boolean \| undefined | false | Whether the field is required. |
| size | "xs" \| "sm" \| "md" \| "lg" \| "xl" \| undefined | — | — |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | FormFieldT.Styles \| undefined | — | — |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-orientation | string \| undefined | Stores the rendered orientation. |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
