---
title: Input
description: Single-line text field for forms and data entry.
group: Components
order: 39
---

Use a single-line text field for forms and data entry. Pair it with Field for labels and help text.

```tsx
import { Input } from "@/components/ui/input"

export function Example() {
  return <Input placeholder="Email" type="email" />
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add input
```

## Usage

```tsx
import { Input } from "@/components/ui/input"
```

```tsx
<Input />
```

## Field

Wrap the input with `Field`, `FieldLabel`, and `FieldDescription` to add a label and helper text.

```tsx
import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export function Example() {
  return (
    <Field>
      <FieldLabel htmlFor="username">Username</FieldLabel>
      <FieldDescription>
        Choose a unique username for your account.
      </FieldDescription>
      <Input id="username" placeholder="your-username" />
    </Field>
  )
}
```

## Field Group

Use `FieldGroup` to stack related fields.

```tsx
import { Button } from "@/components/ui/button"
import {
  Field,
  FieldDescription,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export function Example() {
  return (
    <FieldGroup>
      <Field>
        <FieldLabel htmlFor="name">Name</FieldLabel>
        <Input id="name" placeholder="Jane Doe" />
      </Field>
      <Field>
        <FieldLabel htmlFor="email">Email</FieldLabel>
        <Input id="email" type="email" placeholder="jane@example.com" />
        <FieldDescription>
          We'll send updates to this address.
        </FieldDescription>
      </Field>
      <div className="flex gap-2">
        <Button type="button" variant="outline">
          Reset
        </Button>
        <Button type="button">Submit</Button>
      </div>
    </FieldGroup>
  )
}
```

## Disabled

Set `disabled` to prevent input.

```tsx
import { Input } from "@/components/ui/input"

export function Example() {
  return (
    <div className="grid gap-2 opacity-60">
      <label
        htmlFor="email-disabled"
        className="text-sm font-medium tracking-wide"
      >
        Email
      </label>
      <Input
        id="email-disabled"
        type="email"
        placeholder="Email"
        disabled
      />
    </div>
  )
}
```

## Invalid

Set `aria-invalid` when validation fails.

```tsx
import { Input } from "@/components/ui/input"

export function Example() {
  return (
    <div className="grid gap-2">
      <label
        htmlFor="email-invalid"
        className="text-sm font-medium tracking-wide text-destructive"
      >
        Email
      </label>
      <Input
        id="email-invalid"
        type="email"
        placeholder="Email"
        aria-invalid
        defaultValue="not-an-email"
      />
      <p className="text-sm text-destructive">
        Enter a valid email address.
      </p>
    </div>
  )
}
```

## File

Use `type="file"` for uploads. Wrap it in a labeled control to keep **Choose file** on the left and the filename on the right. Browsers do not let you reposition the native “No file chosen” text with CSS alone.

```tsx
"use client"

import { useState } from "react"
import { Input } from "@/components/ui/input"

export function Example() {
  const [fileName, setFileName] = useState("No file chosen")

  return (
    <div className="grid gap-2">
      <label
        htmlFor="picture"
        className="text-sm font-medium tracking-wide"
      >
        Picture
      </label>
      <p className="text-sm text-muted-foreground">
        Select a picture to upload.
      </p>
      <label
        htmlFor="picture"
        className="relative flex h-9 w-full cursor-pointer items-center overflow-hidden rounded-md border border-border bg-muted px-3 text-sm tracking-wide"
      >
        <span className="pointer-events-none font-medium">Choose file</span>
        <span className="pointer-events-none ml-auto truncate pl-3 text-muted-foreground">
          {fileName}
        </span>
        <Input
          id="picture"
          type="file"
          className="absolute inset-0 cursor-pointer opacity-0"
          onChange={(event) =>
            setFileName(event.target.files?.[0]?.name ?? "No file chosen")
          }
        />
      </label>
    </div>
  )
}
```

## Button

Place a button beside the input for search or submit.

```tsx
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"

export function Example() {
  return (
    <div className="flex w-full items-center gap-2">
      <Input type="search" placeholder="Search..." className="flex-1" />
      <Button type="button">
        Search
      </Button>
    </div>
  )
}
```

## Grid

Lay out several inputs side by side with CSS grid.

```tsx
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export function Example() {
  return (
    <FieldGroup className="grid gap-4 sm:grid-cols-2">
      <Field>
        <FieldLabel htmlFor="first-name">First Name</FieldLabel>
        <Input id="first-name" placeholder="Alex" />
      </Field>
      <Field>
        <FieldLabel htmlFor="last-name">Last Name</FieldLabel>
        <Input id="last-name" placeholder="Morgan" />
      </Field>
    </FieldGroup>
  )
}
```

## Badge

Add a `Badge` in the label for recommended or beta fields.

```tsx
import { Badge } from "@/components/ui/badge"
import { Field, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export function Example() {
  return (
    <Field>
      <FieldLabel htmlFor="webhook" className="w-full justify-between">
        Webhook URL
        <Badge variant="secondary">Beta</Badge>
      </FieldLabel>
      <Input
        id="webhook"
        type="url"
        placeholder="https://example.com/webhook"
      />
    </Field>
  )
}
```

## Input Group

Use [Input Group](/docs/components/input-group) for icons, text, or buttons inside the field.

```tsx
import { Field, FieldLabel } from "@/components/ui/field"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
  InputGroupText,
} from "@/components/ui/input-group"

export function Example() {
  return (
    <Field>
      <FieldLabel htmlFor="website">Website URL</FieldLabel>
      <InputGroup variant="filled">
        <InputGroupAddon>
          <InputGroupText>https://</InputGroupText>
        </InputGroupAddon>
        <InputGroupInput id="website" placeholder="example.com" />
      </InputGroup>
    </Field>
  )
}
```

## Button Group

Use [Button Group](/docs/components/button-group) to attach buttons beside the field.

```tsx
import { Button, ButtonGroup } from "@/components/ui/button"
import {
  buttonGroupItemClass,
  useButtonGroup,
} from "@/components/ui/button-group"
import { cn } from "@/lib/utils"

function SearchIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>
      <circle cx="11" cy="11" r="8" />
      <path d="m21 21-4.3-4.3" />
    </svg>
  )
}

function GroupInput({
  className,
  ...props
}: React.ComponentProps<"input">) {
  const group = useButtonGroup()

  return (
    <input
      data-slot="button-group-input"
      className={cn(
        "h-9 min-w-0 flex-1 border border-border bg-transparent px-3 text-sm text-foreground outline-none placeholder:text-muted-foreground",
        buttonGroupItemClass(group),
        className,
      )}
      {...props}
    />
  )
}

export function Example() {
  return (
    <ButtonGroup className="w-full max-w-sm" aria-label="Search">
      <GroupInput placeholder="Search…" />
      <Button variant="outline" size="icon" aria-label="Search">
        <SearchIcon />
      </Button>
    </ButtonGroup>
  )
}
```

## API Reference

### Input

| Prop | Type | Default |
| --- | --- | --- |
| type | string | "text" |
| placeholder | string | — |
| disabled | boolean | false |
| aria-invalid | boolean | — |
| className | string | — |

Also accepts the other native `input` attributes (`id`, `name`, `value`, `defaultValue`, `onChange`, and so on).

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

```tsx
<Input type="file" />
```

## Source

```tsx
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";

function Input({ className, type, ...props }: ComponentProps<"input">) {
  return (
    <input
      type={type}
      className={cn(
        "h-9 w-full min-w-0 rounded-md border border-border bg-muted px-3 py-1 text-sm tracking-wide transition-colors duration-200 ease-out outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-40 aria-invalid:border-destructive aria-invalid:bg-destructive/5",
        className,
      )}
      data-slot="input"
      {...props}
    />
  );
}

export { Input };
```
