TextField

Composition-friendly text fields with labels, descriptions, and inline validation

Import

import { TextField } from '@heroui/react';

Usage

"use client";

import {Input, Label, TextField} from "@heroui/react";

export function Basic() {
  return (
    <TextField className="w-full max-w-64" name="email" type="email">
      <Label>Email</Label>
      <Input placeholder="Enter your email" />
    </TextField>
  );
}

Anatomy

import {TextField, Label, Input, Description, FieldError} from '@heroui/react';

export default function Example() {
  return (
    <TextField isRequired>
      <Label>Display name</Label>
      <Input placeholder="Jane" />
      <Description>Visible to other team members.</Description>
      <FieldError />
    </TextField>
  );
}

TextField combines label, input, description, and error into a single accessible component.
For standalone inputs, use Input or TextArea.

With Description

Choose a unique username for your account
"use client";

import {Description, Input, Label, TextField} from "@heroui/react";

export function WithDescription() {
  return (
    <TextField className="w-full max-w-64" name="username">
      <Label>Username</Label>
      <Input placeholder="Enter username" />
      <Description>Choose a unique username for your account</Description>
    </TextField>
  );
}

Required Field

This field is required
"use client";

import {Description, Input, Label, TextField} from "@heroui/react";

export function Required() {
  return (
    <TextField isRequired className="w-full max-w-64" name="fullName">
      <Label>Full Name</Label>
      <Input placeholder="John Doe" />
      <Description>This field is required</Description>
    </TextField>
  );
}

Validation

Use isInvalid together with FieldError to surface validation messages.

Choose a unique username for your profile.
Minimum 20 characters (0/20).
"use client";

import {Description, FieldError, Input, Label, TextArea, TextField} from "@heroui/react";
import React from "react";

export function Validation() {
  const [username, setUsername] = React.useState("");
  const [bio, setBio] = React.useState("");

  const isUsernameInvalid = username.length > 0 && username.length < 3;
  const isBioInvalid = bio.length > 0 && bio.length < 20;

  return (
    <div className="flex w-full max-w-64 flex-col gap-4">
      <TextField isRequired isInvalid={isUsernameInvalid} name="username" onChange={setUsername}>
        <Label>Username</Label>
        <Input placeholder="jane_doe" value={username} />
        {isUsernameInvalid ? (
          <FieldError>Username must be at least 3 characters.</FieldError>
        ) : (
          <Description>Choose a unique username for your profile.</Description>
        )}
      </TextField>

      <TextField isRequired isInvalid={isBioInvalid} name="bio" onChange={setBio}>
        <Label>Bio</Label>
        <TextArea placeholder="Tell us about yourself..." value={bio} />
        {isBioInvalid ? (
          <FieldError>Bio must contain at least 20 characters.</FieldError>
        ) : (
          <Description>Minimum 20 characters ({bio.length}/20).</Description>
        )}
      </TextField>
    </div>
  );
}

Controlled

Control the value to synchronize counters, previews, or formatting.

Characters: 0
Characters: 0 / 200
"use client";

import {Description, Input, Label, TextArea, TextField} from "@heroui/react";
import React from "react";

export function Controlled() {
  const [name, setName] = React.useState("");
  const [bio, setBio] = React.useState("");

  return (
    <div className="flex w-full max-w-64 flex-col gap-4">
      <TextField name="name" onChange={setName}>
        <Label>Display name</Label>
        <Input placeholder="Jane" value={name} />
        <Description>Characters: {name.length}</Description>
      </TextField>
      <TextField name="bio" onChange={setBio}>
        <Label>Bio</Label>
        <TextArea placeholder="Tell us about yourself..." value={bio} />
        <Description>Characters: {bio.length} / 200</Description>
      </TextField>
    </div>
  );
}

Error Message

Please enter a valid email address
"use client";

import {FieldError, Input, Label, TextField} from "@heroui/react";

export function WithError() {
  return (
    <TextField isInvalid className="w-full max-w-64" name="email" type="email">
      <Label>Email</Label>
      <Input placeholder="user@example.com" />
      <FieldError>Please enter a valid email address</FieldError>
    </TextField>
  );
}

Disabled State

This field cannot be edited
"use client";

import {Description, Input, Label, TextField} from "@heroui/react";

export function Disabled() {
  return (
    <TextField isDisabled className="w-full max-w-64" name="accountId">
      <Label>Account ID</Label>
      <Input placeholder="Auto-generated" value="USR-12345" />
      <Description>This field cannot be edited</Description>
    </TextField>
  );
}

TextArea

Use TextArea instead of Input for multiline content.

Maximum 500 characters
"use client";

import {Description, Label, TextArea, TextField} from "@heroui/react";

export function TextAreaExample() {
  return (
    <TextField className="w-full max-w-64" name="message">
      <Label>Message</Label>
      <TextArea placeholder="Write your message here..." rows={4} />
      <Description>Maximum 500 characters</Description>
    </TextField>
  );
}

Input Types

"use client";

import {Input, Label, TextField} from "@heroui/react";

export function InputTypes() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-4">
      <TextField name="password" type="password">
        <Label>Password</Label>
        <Input placeholder="••••••••" />
      </TextField>

      <TextField name="age" type="number">
        <Label>Age</Label>
        <Input max="150" min="0" placeholder="21" />
      </TextField>

      <TextField name="email" type="email">
        <Label>Email</Label>
        <Input placeholder="user@example.com" />
      </TextField>

      <TextField name="website" type="url">
        <Label>Website</Label>
        <Input placeholder="https://example.com" />
      </TextField>

      <TextField name="phone" type="tel">
        <Label>Phone</Label>
        <Input placeholder="+1 (555) 000-0000" />
      </TextField>
    </div>
  );
}

Styling

Passing Tailwind CSS classes

import {TextField, Label, Input, Description} from '@heroui/react';

function CustomTextField() {
  return (
    <TextField className="gap-2 rounded-xl border border-border/60 bg-surface-2 p-4 shadow-sm">
      <Label className="text-sm font-semibold text-default-700">
        Project name
      </Label>
      <Input className="rounded-lg border border-border/60 bg-surface-2 px-3 py-2" />
      <Description className="text-xs text-default-500">
        Keep it short and memorable.
      </Description>
    </TextField>
  );
}

Customizing the component classes

TextField has minimal default styling. Override the .text-field class to customize the container styling.

@layer components {
  .text-field {
    @apply flex flex-col gap-1;
  }

  /* When invalid, the description is hidden automatically */
  .text-field[data-invalid="true"] [data-slot="description"],
  .text-field[aria-invalid="true"] [data-slot="description"] {
    @apply hidden;
  }

  /* Description has default padding */
  .text-field [data-slot="description"] {
    @apply px-1;
  }
}

CSS Classes

  • .text-field – Root container with minimal styling (flex flex-col gap-1)

Note: Child components (Label, Input, TextArea, Description, FieldError) have their own CSS classes and styling. See their respective documentation for customization options.

Interactive States

TextField automatically manages these data attributes based on its state:

  • Invalid: [data-invalid="true"] or [aria-invalid="true"] - Automatically hides the description slot when invalid
  • Disabled: [data-disabled="true"] - Applied when isDisabled is true
  • Focus Within: [data-focus-within="true"] - Applied when any child input is focused
  • Focus Visible: [data-focus-visible="true"] - Applied when focus is visible (keyboard navigation)

Additional attributes are available through render props (see TextFieldRenderProps below).

API Reference

TextField Props

TextField inherits all props from React Aria's TextField component.

Base Props

PropTypeDefaultDescription
childrenReact.ReactNode | (values: TextFieldRenderProps) => React.ReactNode-Child components (Label, Input, etc.) or render function.
classNamestring | (values: TextFieldRenderProps) => string-CSS classes for styling, supports render props.
styleReact.CSSProperties | (values: TextFieldRenderProps) => React.CSSProperties-Inline styles, supports render props.
idstring-The element's unique identifier.

Validation Props

PropTypeDefaultDescription
isRequiredbooleanfalseWhether user input is required before form submission.
isInvalidboolean-Whether the value is invalid.
validate(value: string) => ValidationError | true | null | undefined-Custom validation function.
validationBehavior'native' | 'aria''native'Whether to use native HTML form validation or ARIA attributes.
validationErrorsstring[]-Server-side validation errors.

Value Props

PropTypeDefaultDescription
valuestring-Current value (controlled).
defaultValuestring-Default value (uncontrolled).
onChange(value: string) => void-Handler called when the value changes.

State Props

PropTypeDefaultDescription
isDisabledboolean-Whether the input is disabled.
isReadOnlyboolean-Whether the input can be selected but not changed.

Form Props

PropTypeDefaultDescription
namestring-Name of the input element, for HTML form submission.
autoFocusboolean-Whether the element should receive focus on render.

Accessibility Props

PropTypeDefaultDescription
aria-labelstring-Accessibility label when no visible label is present.
aria-labelledbystring-ID of elements that label this field.
aria-describedbystring-ID of elements that describe this field.
aria-detailsstring-ID of elements with additional details.

Composition Components

TextField works with these separate components that should be imported and used directly:

  • Label - Field label component from @heroui/react
  • Input - Single-line text input from @heroui/react
  • TextArea - Multi-line text input from @heroui/react
  • Description - Helper text component from @heroui/react
  • FieldError - Validation error message from @heroui/react

Each of these components has its own props API. Use them directly within TextField for composition:

<TextField isRequired isInvalid={hasError}>
  <Label>Email Address</Label>
  <Input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
  <Description>We'll never share your email.</Description>
  <FieldError>Please enter a valid email address.</FieldError>
</TextField>

TextFieldRenderProps

When using render props with className, style, or children, these values are available:

PropTypeDescription
isDisabledbooleanWhether the field is disabled.
isInvalidbooleanWhether the field is currently invalid.
isReadOnlybooleanWhether the field is read-only.
isRequiredbooleanWhether the field is required.
isFocusedbooleanWhether the field is currently focused (DEPRECATED - use isFocusWithin).
isFocusWithinbooleanWhether any child element is focused.
isFocusVisiblebooleanWhether focus is visible (keyboard navigation).