Input

If web applications didn't need inputs, computers wouldn't have keyboards.

import { Field, Label } from '@/components/fieldset'
import { Input } from '@/components/input'

function Example() {
  return (
    <Field>
      <Label>Full name</Label>
      <Input name="full_name" />
    </Field>
  )
}

Component API

PropDefaultDescription
Input extends the Headless UI <Input> component
disabledfalseWhether or not to disable the input.
invalidfalseWhether or not the input has a validation error.
name-The name to use when submitting an HTML form.
defaultValue-The initial value for the input.
value-The controlled value of the input.
onChange-Handler to call when the input value changes.
Field extends the Headless UI <Field> component
disabledfalseWhether or not to disable the entire field.
Label extends the Headless UI <Label> component
This component does not expose any component-specific props.
Description extends the Headless UI <Description> component
This component does not expose any component-specific props.
ErrorMessage extends the Headless UI <Description> component
This component does not expose any component-specific props.

Examples

Basic example

Use the Input component on its own to render a standalone input without an associated Label component:

import { Input } from '@/components/input'

function Example() {
  return <Input aria-label="Full name" name="full_name" />
}

Make sure to provide an aria-label for assistive technology, or connect the Input to your own <label> element using an id.

With label

Wrap a Label and Input with the Field component to automatically associate them using a generated ID:

import { Field, Label } from '@/components/fieldset'
import { Input } from '@/components/input'

function Example() {
  return (
    <Field>
      <Label>Full name</Label>
      <Input name="full_name" />
    </Field>
  )
}

With description

Use the Description component to add a description above or below your Input:

Use the name you'd like people to see in their cart.

import { Description, Field, Label } from '@/components/fieldset'
import { Input } from '@/components/input'

function Example() {
  return (
    <Field>
      <Label>Product name</Label>
      <Description>Use the name you'd like people to see in their cart.</Description>
      <Input name="product_name" />
    </Field>
  )
}

With icon

Wrap an icon and Input with the InputGroup component to render an input with an icon:

import { Input, InputGroup } from '@/components/input'
import { MagnifyingGlassIcon } from '@heroicons/react/16/solid'

function Example() {
  return (
    <InputGroup>
      <MagnifyingGlassIcon />
      <Input name="search" placeholder="Search&hellip;" aria-label="Search" />
    </InputGroup>
  )
}

The InputGroup component is designed to work best with 16×16 icons.

Setting the type

Use the type prop to set the input type to any supported text input type:

import { Field, Label } from '@/components/fieldset'
import { Input } from '@/components/input'

function Example() {
  return (
    <Field>
      <Label>Your website</Label>
      <Input type="url" name="url" />
    </Field>
  )
}

The supported types are email, number, password, search, tel, text, url, date, datetime-local, month, time, and week.

Disabled state

Add the disabled prop to the Field component to disable an Input and the associated Label:

import { Field, Label } from '@/components/fieldset'
import { Input } from '@/components/input'

function Example() {
  return (
    <Field disabled>
      <Label>Full name</Label>
      <Input name="full_name" />
    </Field>
  )
}

You can also disable an input outside of a Field by adding the disabled prop directly to the Input itself.

Validation errors

Add the invalid prop to the Input component to indicate a validation error, and render the error using the ErrorMessage component:

This field is required.

import { ErrorMessage, Field, Label } from '@/components/fieldset'
import { Input } from '@/components/input'

function Example({ errors }) {
  return (
    <Field>
      <Label>Full name</Label>
      <Input name="full_name" invalid={errors.has('full_name')} />
      {errors.has('full_name') && <ErrorMessage>{errors.get('full_name')}</ErrorMessage>}
    </Field>
  )
}

Constraining width

Use the className prop on the Input component to make layout adjustments like adjusting the max-width:

import { Field, Label } from '@/components/fieldset'
import { Input } from '@/components/input'

function Example() {
  return (
    <Field>
      <Label>CVC</Label>
      <Input className="max-w-[6rem]" name="cvc" pattern="[0-9]*" />
    </Field>
  )
}

Be aware that the className prop is a sharp knife — make sure to only add classes that don't conflict with classes the component already includes or you'll get unexpected results.

With custom layout

Use the unstyled Field component from @headlessui/react directly instead of the styled Field component to implement a custom layout:

import { Label } from '@/components/fieldset'
import { Input } from '@/components/input'
import * as Headless from '@headlessui/react'

function Example() {
  return (
    <Headless.Field className="flex items-baseline justify-center gap-6">
      <Label>Full name</Label>
      <Input name="full_name" className="max-w-48" placeholder=" " />
    </Headless.Field>
  )
}

Using the unstyled Field component will ensure important accessibility features are still handled for you like generating IDs and associating elements using aria-* attributes.

Controlled component

Use the normal value and onChange props to use the Input component as a controlled component:

import { Field, Label } from '@/components/fieldset'
import { Input } from '@/components/input'
import { useState } from 'react'

function Example() {
  let [name, setName] = useState('')

  return (
    <Field>
      <Label>Full name</Label>
      <Input name="full_name" value={name} onChange={(e) => setName(e.target.value)} />
    </Field>
  )
}