Pro--% off in--d : --h : --m : --s
HeroUI

Input

Migration guide for Input from HeroUI v2 to v3

Refer to the v3 Input documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.

Key Change: Input → TextField

v2: Input was a full-featured component with built-in label, description, error messages, validation, variants, colors, sizes, etc.

v3: Input is now a primitive component (just the input element). For form fields, use TextField which wraps Input with Label, Description, and FieldError components. In v2 there was no separate TextField or InputGroup; the single Input component handled labels, descriptions, start/end content, and validation.

When to Use Input vs TextField

Use TextField (Most Cases)

Use TextField when you need:

  • Labels
  • Descriptions
  • Error messages
  • Validation
  • Form integration

Use Input (Primitive Only)

Use Input when you need:

  • Just a basic input element
  • Custom label/error handling
  • Integration with custom form components

Structure Changes

In v2, Input was a full-featured component that accepted props for label, description, placeholder, and other elements:

import { Input } from "@heroui/react";

export default function App() {
  return (
    <Input
      label="Email"
      placeholder="Enter your email"
      type="email"
    />
  );
}

In v3, Input is a primitive component. For form fields with labels and validation, use TextField which wraps Input with Label, Description, and FieldError:

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

export default function App() {
  return (
    <TextField name="email" type="email">
      <Label>Email</Label>
      <Input placeholder="Enter your email" />
      <FieldError />
    </TextField>
  );
}

Key Changes

1. Component Split

v2: Single Input component with all features
v3: Split into Input (primitive) and TextField (compound component)

2. Prop Changes

v2 Propv3 LocationNotes
labelLabelUse Label component inside TextField
descriptionDescriptionUse Description component inside TextField
errorMessageFieldErrorUse FieldError component inside TextField
variantInputChanged from multiple variants (flat, bordered, underlined, faded) to "primary" (default, with shadow) and "secondary" (lower emphasis, no shadow, for surfaces)
color, size, radius-Removed (use Tailwind CSS)
fullWidthInput or TextFieldStill supported on both Input and TextField (fullWidth boolean prop)
labelPlacement-Use layout with Label
startContentInputGroup.PrefixUse InputGroup with InputGroup.Prefix inside TextField
endContentInputGroup.SuffixUse InputGroup with InputGroup.Suffix inside TextField
isClearable-Implement manually with button
isRequired, isInvalidTextFieldUse on TextField
validateTextFieldUse validate on TextField
classNames-Use className on individual components
onValueChangeInputUse onChange event handler

Migration Examples

Form Validation

{/* With description */}
<Input
  description="We'll never share your email"
  label="Email"
  type="email"
/>

{/* With error message */}
<Input
  errorMessage="Please enter a valid email"
  isInvalid
  label="Email"
  type="email"
/>

{/* Required */}
<Input isRequired label="Email" type="email" />
import { Description } from "@heroui/react";

{/* With description */}
<TextField name="email" type="email">
  <Label>Email</Label>
  <Input />
  <Description>We'll never share your email</Description>
  <FieldError />
</TextField>

{/* With error message */}
<TextField
  isInvalid
  name="email"
  type="email"
  validate={(value) => {
    if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) {
      return "Please enter a valid email";
    }
    return null;
  }}
>
  <Label>Email</Label>
  <Input />
  <FieldError />
</TextField>

{/* Required */}
<TextField isRequired name="email" type="email">
  <Label>Email</Label>
  <Input />
  <FieldError />
</TextField>

Input with Start/End Content

For inputs with prefix or suffix content, use the InputGroup compound component:

<Input
  label="Price"
  startContent={<span>$</span>}
  type="number"
/>
import { InputGroup } from "@heroui/react";

<TextField name="price" type="number">
  <Label>Price</Label>
  <InputGroup>
    <InputGroup.Prefix>$</InputGroup.Prefix>
    <InputGroup.Input />
  </InputGroup>
  <FieldError />
</TextField>

InputGroup provides proper styling and layout for prefix/suffix content. Use:

  • InputGroup.Prefix for content before the input (replaces startContent)
  • InputGroup.Suffix for content after the input (replaces endContent)
  • InputGroup.Input for the input element itself

Input with Clear Button

<Input
  isClearable
  label="Email"
  onClear={() => console.log("cleared")}
  type="email"
/>
import { useState } from "react";
import { CloseButton } from "@heroui/react";

const [value, setValue] = useState("");

<TextField name="email" type="email">
  <Label>Email</Label>
  <div className="flex items-center">
    <Input value={value} onChange={(e) => setValue(e.target.value)} />
    {value && (
      <CloseButton
        aria-label="Clear"
        onPress={() => setValue("")}
      />
    )}
  </div>
  <FieldError />
</TextField>

Controlled Input

import { useState } from "react";

const [value, setValue] = useState("");

<Input
  label="Email"
  onValueChange={setValue}
  type="email"
  value={value}
/>
import { useState } from "react";

const [value, setValue] = useState("");

<TextField name="email" type="email">
  <Label>Email</Label>
  <Input
    onChange={(e) => setValue(e.target.value)}
    value={value}
  />
  <FieldError />
</TextField>

Summary

  1. Component Split: InputTextField for form fields, Input for primitive input
  2. Label Required: Must use Label component instead of label prop
  3. Error Display: Must use FieldError component instead of errorMessage prop
  4. Description: Must use Description component instead of description prop
  5. Validation: Move validate function from Input to TextField
  6. Start/End Content: Use InputGroup with InputGroup.Prefix/InputGroup.Suffix inside TextField
  7. Clear Button: Implement manually with CloseButton
  8. Variants Simplified: v2 had multiple variants (flat, bordered, underlined, faded); v3 has "primary" (default) and "secondary" (for surfaces)
  9. Full Width: fullWidth prop is still available on both Input and TextField
  10. Colors Removed: Use Tailwind CSS classes
  11. Sizes Removed: Use Tailwind CSS classes
  12. Radius Removed: Use Tailwind CSS classes
  13. onValueChange Removed: Use onChange event handler
  14. ClassNames Removed: Use className props on individual components

On this page