# Input (/ui/components/react/input)



<Playground component="input" />

## Usage [#usage]

```tsx
import { Input } from '@appica/ui-react/input'
```

```tsx
<Input type="email" placeholder="you@example.com" />
```

`Input` is a styled text field built on [Base UI's Input](https://base-ui.com/react/components/input). Use it like a native `<input>` — every native attribute (`type`, `value`, `onChange`, `name`, …) passes straight through. Give it a label the usual way: an external `<label htmlFor>` pointing at its `id`, or an `aria-label`.

Add an icon or prefix with `startSlot` / `endSlot`, and a clear button with `clearable`. When any of those are present, `Input` renders a wrapper around the field so the adornments sit inside the same frame; otherwise it renders the bare input.

## Examples [#examples]

### Variants [#variants]

`variant` switches the appearance between a bordered `outline` (default) and a filled `soft` field.

```tsx
import { Input } from '@appica/ui-react/input'

export default function InputVariants() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Input variant="outline" placeholder="Outline" aria-label="Outline" />
      <Input variant="soft" placeholder="Soft" aria-label="Soft" />
    </div>
  )
}
```

### Sizes [#sizes]

`inputSize` scales the height, padding, and text — `sm`, `md` (default), or `lg`. (It's named `inputSize` so it doesn't collide with the native `size` attribute.)

```tsx
import { Input } from '@appica/ui-react/input'

export default function InputSizes() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Input inputSize="sm" placeholder="Small" aria-label="Small" />
      <Input inputSize="md" placeholder="Medium" aria-label="Medium" />
      <Input inputSize="lg" placeholder="Large" aria-label="Large" />
    </div>
  )
}
```

### Start & end slots [#start--end-slots]

`startSlot` and `endSlot` take any node — not just icons — and render it inside the field frame. Use them for a leading icon with a static text suffix, an [`Avatar`](/ui/components/react/avatar), or an interactive control like a password-visibility [`Button`](/ui/components/react/button).

```tsx
'use client'

import { useState } from 'react'
import { Input } from '@appica/ui-react/input'
import { Avatar, AvatarFallback } from '@appica/ui-react/avatar'
import { Button } from '@appica/ui-react/button'
import { Mail, Lock, Eye, EyeOff, UserFilled } from '@appica/icons-react'

export default function InputSlots() {
  const [show, setShow] = useState(false)
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Input
        startSlot={<Mail />}
        endSlot={<span className="text-foreground-muted text-sm">@example.com</span>}
        placeholder="yourname"
        aria-label="Email username"
      />
      <Input
        startSlot={
          <Avatar size="2xs">
            <AvatarFallback>
              <UserFilled />
            </AvatarFallback>
          </Avatar>
        }
        placeholder="Display name"
        aria-label="Display name"
      />
      <Input
        type={show ? 'text' : 'password'}
        startSlot={<Lock />}
        defaultValue="hidden@password"
        endSlot={
          <Button
            className="-me-1.5"
            variant="ghost"
            size="icon-sm"
            aria-label={show ? 'Hide password' : 'Show password'}
            onClick={() => setShow((v) => !v)}
          >
            {show ? <EyeOff /> : <Eye />}
          </Button>
        }
        aria-label="Password"
      />
    </div>
  )
}
```

### Clearable [#clearable]

Set `clearable` to add a clear (✕) button that appears once the field has a value and empties it on click. Pair it with `onClear` to react to the reset.

```tsx
import { Input } from '@appica/ui-react/input'
import { Search } from '@appica/icons-react'

export default function InputClearable() {
  return (
    <Input
      className="max-w-70"
      clearable
      startSlot={<Search />}
      defaultValue="Wireless headphones"
      aria-label="Search products"
    />
  )
}
```

### Disabled, read-only & error states [#disabled-read-only--error-states]

`disabled` greys out the field and blocks interaction; `readOnly` keeps it focusable and selectable but prevents edits. Set `aria-invalid` to paint the error state — it's mirrored to `data-invalid`. Inside a [`Field`](/ui/components/react/field), the input also inherits the field's `disabled` and `invalid` state, which is the better choice when you're rendering a validation message.

```tsx
import { Input } from '@appica/ui-react/input'

export default function InputStates() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      <Input placeholder="Disabled" disabled aria-label="Disabled" />
      <Input defaultValue="Read-only value" readOnly aria-label="Read-only" />
      <Input defaultValue="Input with error" aria-invalid aria-label="Input with error" />
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The text aligns to the start edge and the `startSlot`/`endSlot` adornments swap sides to follow the resolved direction. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="input" />

## API reference [#api-reference]

`Input` wraps [Base UI's Input](https://base-ui.com/react/components/input) and forwards every remaining native `<input>` prop (`type`, `name`, `onChange`, `aria-*`, …); the `variant`, `inputSize`, `clearable`, `startSlot`, `endSlot`, `onClear`, and `inputProps` props are Appica additions. Inside a Base UI [`Field`](/ui/components/react/field) it picks up the field's `disabled` and `invalid` state, and exposes `data-disabled` / `data-invalid` for styling.

When adornments or `clearable` are present the field renders inside a wrapper, so `className` lands on that wrapper. Use `inputProps` to reach the inner `<input>` itself (for example to give it its own `className`).

| Prop           | <ColMinWidth width="200">Type</ColMinWidth> | Default     | <ColMinWidth width="220">Description</ColMinWidth>                                          |
| -------------- | ------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------- |
| `variant`      | <Code>'outline' \| 'soft'</Code>            | `'outline'` | Field appearance — bordered or filled.                                                      |
| `inputSize`    | <Code>'sm' \| 'md' \| 'lg'</Code>           | `'md'`      | Scales height, padding, and text. Named `inputSize` to avoid the native `size` attribute.   |
| `startSlot`    | `ReactNode`                                 | —           | Adornment rendered before the field, inside the frame.                                      |
| `endSlot`      | `ReactNode`                                 | —           | Adornment rendered after the field, inside the frame.                                       |
| `clearable`    | `boolean`                                   | `false`     | Show a clear (✕) button once the field has a value.                                         |
| `onClear`      | <Code>() => void</Code>                     | —           | Called when the clear button is pressed.                                                    |
| `inputProps`   | <Code>Omit\<InputProps, 'size'></Code>      | —           | Props for the inner `<input>` in wrapper mode (its own `className`, handlers, …).           |
| `aria-invalid` | <Code>boolean \| 'true' \| 'false'</Code>   | —           | Marks the error state; mirrored to `data-invalid`.                                          |
| `disabled`     | `boolean`                                   | `false`     | Disables the field and removes it from the tab order.                                       |
| `placeholder`  | `string`                                    | `' '`       | Placeholder text.                                                                           |
| `value`        | `string`                                    | —           | Controlled value. Pair with `onChange`.                                                     |
| `defaultValue` | `string`                                    | —           | Uncontrolled initial value.                                                                 |
| `ref`          | <Code>Ref\<HTMLInputElement></Code>         | —           | Ref to the underlying `<input>`.                                                            |
| `className`    | `string`                                    | —           | Extra classes, merged via `tailwind-merge` (lands on the wrapper when adornments are used). |

## Accessibility [#accessibility]

* Renders a native `<input>` (`role="textbox"`). Give it an accessible name — an external `<label htmlFor>` pointing at its `id`, or an `aria-label`.
* The clear button is labelled **Clear input**, kept out of the tab order (`tabIndex={-1}`), and appears only when the field has a value.
* `aria-invalid` is bridged to `data-invalid` so the error state styles with or without a wrapping `Field`.
* `disabled` removes the field from the tab order and exposes `data-disabled` for styling.
