27.5k

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.

Overview

The Input component in HeroUI v3 has been simplified to a primitive input element. For form fields with labels, validation, and error messages, use the TextField compound component instead.

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.

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

Key Changes

1. Component Split

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

2. Removed Props from Input

The following props are no longer available on Input in v3:

  • label - Use Label component inside TextField
  • description - Use Description component inside TextField
  • errorMessage - Use FieldError component inside TextField
  • variant - Removed (use Tailwind CSS classes)
  • color - Removed (use Tailwind CSS classes)
  • size - Removed (use Tailwind CSS classes)
  • radius - Removed (use Tailwind CSS classes)
  • labelPlacement - Use layout with Label component
  • startContent - Use as first child in TextField
  • endContent - Use as last child in TextField
  • isClearable - Implement manually with button
  • isRequired - Use isRequired on TextField
  • isInvalid - Use isInvalid on TextField
  • validate - Use validate on TextField
  • classNames - Use className props on individual components
  • onValueChange - Use onChange event handler

3. New Props on Input

  • isOnSurface - Whether input is on a surface background (affects styling)

Migration Examples

Basic Usage

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

export default function App() {
  return (
    <Input
      label="Email"
      placeholder="Enter your email"
      type="email"
    />
  );
}
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>
  );
}

Input with Description

<Input
  description="We'll never share your email"
  label="Email"
  type="email"
/>
import { Description } from "@heroui/react";

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

Input with Error Message

<Input
  errorMessage="Please enter a valid email"
  isInvalid
  label="Email"
  type="email"
/>
<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>

Input with Start/End Content

<Input
  label="Price"
  startContent={<span>$</span>}
  type="number"
/>
<TextField name="price" type="number">
  <Label>Price</Label>
  <span>$</span>
  <Input />
  <FieldError />
</TextField>

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>

Required Input

<Input
  isRequired
  label="Email"
  type="email"
/>
<TextField isRequired name="email" type="email">
  <Label>Email</Label>
  <Input />
  <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>

Simple Input (No Label/Validation)

<Input placeholder="Search..." type="search" />
{/* v3 Input can be used directly for simple cases */}
<Input aria-label="Search" placeholder="Search..." type="search" />

Input Variants

<Input label="Email" type="email" variant="bordered" />
<Input label="Email" type="email" variant="flat" />
<Input label="Email" type="email" variant="underlined" />
{/* Use Tailwind CSS classes for styling */}
<TextField name="email" type="email">
  <Label>Email</Label>
  <Input className="border border-border" />
  <FieldError />
</TextField>

Input Sizes

<Input label="Email" size="sm" type="email" />
<Input label="Email" size="md" type="email" />
<Input label="Email" size="lg" type="email" />
{/* Use Tailwind CSS classes for sizes */}
<TextField name="email" type="email">
  <Label>Email</Label>
  <Input className="h-8 text-sm" />
  <FieldError />
</TextField>

Input Colors

<Input color="primary" label="Email" type="email" />
<Input color="danger" label="Email" type="email" />
{/* Use Tailwind CSS classes for colors */}
<TextField name="email" type="email">
  <Label>Email</Label>
  <Input className="border-primary focus-visible:border-primary" />
  <FieldError />
</TextField>

Breaking Changes 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 as children instead of props
  7. Clear Button: Implement manually with CloseButton
  8. Variants Removed: Use Tailwind CSS classes
  9. Colors Removed: Use Tailwind CSS classes
  10. Sizes Removed: Use Tailwind CSS classes
  11. Radius Removed: Use Tailwind CSS classes
  12. onValueChange Removed: Use onChange event handler
  13. ClassNames Removed: Use className props on individual components

Tips for Migration

  1. Most cases: Convert Input to TextField compound component
  2. Simple inputs: Use Input directly if you don't need labels/validation
  3. Add TextField wrapper: Wrap Input with TextField for form fields
  4. Add Label: Use Label component for labels
  5. Add FieldError: Always include FieldError for error display
  6. Move validation: Move validate function to TextField
  7. Use onChange: Replace onValueChange with onChange event handler
  8. Custom styling: Use Tailwind CSS classes for variants, colors, sizes

Need Help?

For v3 Input features and API:

For community support: