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 Prop | v3 Location | Notes |
|---|---|---|
label | Label | Use Label component inside TextField |
description | Description | Use Description component inside TextField |
errorMessage | FieldError | Use FieldError component inside TextField |
variant | Input | Changed 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) |
fullWidth | Input or TextField | Still supported on both Input and TextField (fullWidth boolean prop) |
labelPlacement | - | Use layout with Label |
startContent | InputGroup.Prefix | Use InputGroup with InputGroup.Prefix inside TextField |
endContent | InputGroup.Suffix | Use InputGroup with InputGroup.Suffix inside TextField |
isClearable | - | Implement manually with button |
isRequired, isInvalid | TextField | Use on TextField |
validate | TextField | Use validate on TextField |
classNames | - | Use className on individual components |
onValueChange | Input | Use 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.Prefixfor content before the input (replacesstartContent)InputGroup.Suffixfor content after the input (replacesendContent)InputGroup.Inputfor 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
- Component Split:
Input→TextFieldfor form fields,Inputfor primitive input - Label Required: Must use
Labelcomponent instead oflabelprop - Error Display: Must use
FieldErrorcomponent instead oferrorMessageprop - Description: Must use
Descriptioncomponent instead ofdescriptionprop - Validation: Move
validatefunction fromInputtoTextField - Start/End Content: Use
InputGroupwithInputGroup.Prefix/InputGroup.SuffixinsideTextField - Clear Button: Implement manually with
CloseButton - Variants Simplified: v2 had multiple variants (flat, bordered, underlined, faded); v3 has
"primary"(default) and"secondary"(for surfaces) - Full Width:
fullWidthprop is still available on bothInputandTextField - Colors Removed: Use Tailwind CSS classes
- Sizes Removed: Use Tailwind CSS classes
- Radius Removed: Use Tailwind CSS classes
- onValueChange Removed: Use
onChangeevent handler - ClassNames Removed: Use
classNameprops on individual components