Input

Single-line text field for forms and data entry.

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

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

Usage

import { Input } from "@/components/ui/input"
<Input />

Field

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

Choose a unique username for your account.

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.

We'll send updates to this address.

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.

Enter a valid email address.

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.

Select a picture to upload.

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 for icons, text, or buttons inside the field.

https://
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 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

PropTypeDefault
typestring"text"
placeholderstring
disabledbooleanfalse
aria-invalidboolean
classNamestring

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

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