---
title: Input
description: Migration guide for Input from HeroUI v2 to v3
---

<Callout type="info">
  Refer to the [v3 Input documentation](/docs/react/components/input) for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
</Callout>

## 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:

```tsx
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`:

```tsx
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

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    {/* 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" />
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    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>
    ```
  </Tab>
</Tabs>

### Input with Start/End Content

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

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    <Input
      label="Price"
      startContent={<span>$</span>}
      type="number"
    />
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { InputGroup } from "@heroui/react";

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

**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

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    <Input
      isClearable
      label="Email"
      onClear={() => console.log("cleared")}
      type="email"
    />
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    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>
    ```
  </Tab>
</Tabs>

### Controlled Input

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    import { useState } from "react";

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

    <Input
      label="Email"
      onValueChange={setValue}
      type="email"
      value={value}
    />
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    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>
    ```
  </Tab>
</Tabs>

## Summary

1. **Component Split**: `Input` → `TextField` 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
12. **onValueChange Removed**: Use `onChange` event handler
13. **ClassNames Removed**: Use `className` props on individual components
