# Input

> A React text input: icons and addons inside the box, a clear button, a loading spinner, a character counter and floating labels.

- Type: React component (Inputs)
- Access: Free (the source is open to everyone)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add input`
- Docs: https://wingo-ui.com/components/input

## Interaction

Click anywhere in the box, on an icon, on the padding or on a suffix like USD, and the caret lands in the text without jumping; the whole box takes a soft ring. Type and a clear button pops in at the end: tap it or press Escape and the text is gone while focus stays in the field. While loading a small spinner spins where the trailing icon was, and the field stays editable. With a floating label the label rests inside the box like a placeholder and glides up to the top edge on focus, when filled or when the browser autofills. With a counter the numbers turn amber at 90% and red at the limit. Enter submits the form like a native field. On a phone the text is 16px so iOS never zooms, the keypad follows the type (email, url, tel, decimal for amounts), the md box grows to 44px and the clear button has a 44px hit area.

## Installation

```bash
npx wingo-ui@latest add input
```

The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add https://wingo-ui.com/r/input.json`

Files:

- `components/ui/input.tsx`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))
- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))
- [Spinner](https://wingo-ui.com/components/spinner) ([Markdown](https://wingo-ui.com/components/spinner.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))

## Usage

```tsx
import { useRef, useState } from "react"
import { CircleCheck, MapPin } from "lucide-react"
import { Input } from "@/components/ui/input"

export function ListingDetails() {
  const [title, setTitle] = useState("")
  const [user, setUser] = useState("emma.carter")
  const [checking, setChecking] = useState(false)
  const timer = useRef<ReturnType<typeof setTimeout>>(undefined)

  return (
    <form className="flex flex-col gap-4">
      <Input
        name="title"
        label="Listing title"
        placeholder="e.g. Trek FX 2 city bike"
        value={title}
        onValueChange={setTitle}
        error={title && title.length < 10 ? "The title needs at least 10 characters." : undefined}
        showCount
        maxLength={70}
        required
        enterKeyHint="next"
      />
      <Input name="price" label="Price" prefix="$" inputMode="decimal" selectOnFocus defaultValue="1,250" />
      <Input name="city" label="City" leftIcon={<MapPin />} clearable autoComplete="address-level2" />
      <Input
        label="Username"
        value={user}
        onValueChange={(next) => {
          setUser(next)
          // the check runs 1.4s after the last keystroke
          setChecking(true)
          clearTimeout(timer.current)
          timer.current = setTimeout(() => setChecking(false), 1400)
        }}
        loading={checking}
        rightIcon={<CircleCheck className="text-success-soft-foreground" />}
        autoCapitalize="none"
        enterKeyHint="done"
      />
    </form>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `ReactNode` | `""` | Renders the field anatomy around the box: the label above it, linked with htmlFor. With floatingLabel it moves inside the box. Leave it empty for a bare input (then pass aria-label). |
| `description?` | `ReactNode` | `""` | Helper text under the box, linked with aria-describedby. The error takes its place while it shows. |
| `error?` | `ReactNode` | `""` | Replaces the description with an alert icon and the text, slides in and marks the field invalid (red ring, aria-invalid). Empty means no error. |
| `required?` | `boolean` | `false` | The red mark after the label (hidden from screen readers) and the native required attribute. |
| `invalid?` | `boolean` | `false` | The invalid look and aria-invalid without a message, for forms that show the error somewhere else. |
| `disabled?` | `boolean` | `false` | Dims the field to 50% and blocks input; the native input is disabled, so the form does not submit it. |
| `readOnly?` | `boolean` | `false` | The value stays visible, selectable and submitted, but cannot change; the box drops its hover tint and the clear button. The demo fills in a sample value. |
| `placeholder?` | `string` | `""` | Hint text while the field is empty. With a floating label it only shows while the label is floated (on focus, or always next to a prefix), never behind it. |
| `defaultValue?` | `string` | `""` | The starting text while uncontrolled. Pass value and onValueChange instead to control it; a value set by a form library straight on the element shows up in the counter after the next focus. |
| `prefix?` | `ReactNode` | `""` | Text before the input, like "https://" or "+40", in the muted addon look (long text truncates at 40% of the box), or an element rendered as is. A click on it lands in the input. |
| `suffix?` | `ReactNode` | `""` | Text after the input, like "USD" or "@company.com", or an element such as a small Apply button. Buttons inside keep the input focused while pressed. |
| `clearLabel?` | `string` | `"Clear"` | The clear button's accessible name and hover title. The demo passes Clear text (the City field always shows one). |
| `loadingLabel?` | `string` | `"Loading"` | What screen readers hear for the spinner while loading. The demo passes Checking. |
| `name?` | `string` | `""` | The form field name. It lands on the native input, so FormData, autofill and {...register('titlu')} from react-hook-form work as on a plain input. |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | Height 32 / 40 / 48px with 13 / 14 / 15px text (16px on phones so iOS never zooms); md grows to 44px on touch. Floating labels make the box 44 / 52 / 60px. |
| `variant?` | `"soft" \| "outline" \| "underline" \| "ghost"` | `"soft"` | soft is a filled well that works on every surface, outline a bordered box, underline a bottom line that grows a bar from the center on focus, ghost has no fill until hover (toolbars, table cells). |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | Corner shape, scaled with the size like the button's (md is 8 / 10 / 12px); full makes a pill. Ignored by underline. |
| `accent?` | `string` | `""` | Any CSS color for the focus ring, the outline border and underline bar on focus, and the caret. Empty uses the ring token (the brand orange). |
| `floatingLabel?` | `boolean` | `false` | The label sits inside the box like a placeholder and floats up, at 80%, on focus, when filled or when autofilled (CSS only, so it is right before hydration). The box grows to 44 / 52 / 60px for the room. Needs label; a prefix keeps it floated. |
| `loading?` | `boolean` | `false` | A small spinner at the end, in place of the right icon when there is one, and aria-busy. The input stays editable, e.g. while checking if a username is free. |
| `type?` | `InputType: "text" \| "email" \| "url" \| "tel" \| "search" \| "password" \| "number"` | `"text"` | The native type, which also picks the phone keypad (email, url, tel, search). number renders a text field with a decimal keypad, so maxLength works and a scroll never changes it; number-input adds steppers and locale parsing, password-input the reveal button. |
| `clearable?` | `boolean` | `false` | A clear button pops in while there is a value (never when disabled or read-only). It stays out of the Tab order; Escape clears from the keyboard. Focus stays in the input. |
| `showCount?` | `boolean` | `false` | The "12 / 70" counter at the end of the message row, amber from 90% and red at the limit. Needs maxLength. |
| `maxLength?` | `number` | `0` | The native character limit, also the counter's maximum. 0 means no limit. |
| `selectOnFocus?` | `boolean` | `false` | Selects the whole text when the field takes focus, by click, tap or Tab, so typing replaces it. For codes and amounts. |
| `value?` | `string` |  | The text, controlled. Without onValueChange typing does nothing (like React); use readOnly for a fixed value. |
| `onValueChange?` | `(value: string) => void` |  | Fires with the new text on every edit, including a clear. |
| `onChange?` | `(event: ChangeEvent<HTMLInputElement>) => void` |  | The native change event, untouched. A clear fires it too, like a keystroke. |
| `onClear?` | `() => void` |  | Called after the clear button or Escape emptied the field. Focus is already back in the input. |
| `leftIcon?` | `ReactNode` |  | A leading icon in the muted color; unsized svgs follow the size (14 / 16 / 18px). A click on it lands in the input. |
| `rightIcon?` | `ReactNode` |  | A trailing icon, e.g. a green check after a successful check. It pops in and out, and crossfades with the spinner while loading. |
| `clearIcon?` | `ReactNode` |  | Replaces the clear button's icon (a 16px X by default). |
| `fieldProps?` | `Partial<FieldProps>` |  | Anything else for the Field wrapper: optional and optionalLabel, labelAction, orientation, labelWidth, hideLabel, counterLabel, requiredIndicator. |
| `id?` | `string` |  | The native input's id. Defaults to useId(); inside a <Field> the Field's id wins. |
| `ref?` | `RefObject<HTMLInputElement \| null> or (node) => void` |  | The native input. Every other native prop lands there too: inputMode (defaults from the type), enterKeyHint, autoComplete, pattern, spellCheck, autoCapitalize, onBlur, onKeyDown, aria-* and data-*. |
| `classNames?` | `Partial<Record<"field" \| "label" \| "description" \| "error" \| "counter" \| "box" \| "input" \| "prefix" \| "suffix" \| "leftIcon" \| "rightIcon" \| "clear" \| "spinner" \| "floatingLabel", string>>` |  | Extra classes for the inner parts. box targets the field box, input the native element. |
| `className?` | `string` |  | Classes for the outermost element: the Field root when there is a label, description, error or counter, else the box. |

## Examples

- **Icons and addons**: A leading icon, text addons on both sides, and a small Apply button as the suffix: it checks the code for a second, keeps the input focused and swaps for the discount.
- **Variants and sizes**: soft, outline, underline and ghost at 32, 40 and 48px, with icons, a clear button and a suffix.
- **Loading**: An availability check 1.4s after typing stops: the check icon crossfades into a spinner and back; try emma for a taken name or a capital letter for the format error.
- **Clearable search**: The clear button pops in with the first letter; Escape clears from the keyboard and focus never leaves the field. Every word matches on its own, accents folded; the label is kept for screen readers only (fieldProps.hideLabel).
- **Floating labels**: An outline checkout form: labels rest inside the box and float up on focus or when filled; a prefix keeps the label up. Leave the email field to see its error.
- **Counter**: showCount with maxLength 70: amber from 63 characters, red at the limit. Add the suggestions to walk it there.
- **States**: Read-only (selectable, selected on focus), disabled with a prefix, and invalid without a message.
- **Accent and pill**: radius full with a blue accent: focus the field and the ring and caret match the button next to it.

## See also

- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))
- [Password Input](https://wingo-ui.com/components/password-input) ([Markdown](https://wingo-ui.com/components/password-input.md))
- [Number Input](https://wingo-ui.com/components/number-input) ([Markdown](https://wingo-ui.com/components/number-input.md))
- [Search Input](https://wingo-ui.com/components/search-input) ([Markdown](https://wingo-ui.com/components/search-input.md))
- [Masked Input](https://wingo-ui.com/components/masked-input) ([Markdown](https://wingo-ui.com/components/masked-input.md))
- [Textarea](https://wingo-ui.com/components/textarea) ([Markdown](https://wingo-ui.com/components/textarea.md))

---

Canonical: https://wingo-ui.com/components/input
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
