Free shadcn/ui input component for React with label, search, password, file and validation examples. Theme-aware, accessible, copy or install in one command.

Installation

1 Configure registry — once per project
{
  "registries": {
    "@designrevision": "https://registry.designrevision.com/r/{name}.json"
  }
}

Add to your existing components.json. Requires shadcn/ui v2.3+.

2 Install the component

Packages

utils

Props

No props documented yet.

Copied!
components/ui/input.tsx
import * as React from "react"

import { cn } from "@/lib/utils"

function Input({ className, type, ...props }: React.ComponentProps<"input">) {
  return (
    <input
      type={type}
      data-slot="input"
      className={cn(
        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
        className
      )}
      {...props}
    />
  )
}

export { Input }

Examples

With label

The canonical form field — a `Label` bound to the input by `htmlFor`, with muted helper text below.

Packages

input
label

Props

No props documented yet.

Copied!
components/ui/input-label-01.tsx
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function InputLabel01() {
  return (
    <div className="grid w-full max-w-sm gap-2">
      <Label htmlFor="email">Email</Label>
      <Input id="email" type="email" placeholder="[email protected]" />
      <p className="text-sm text-muted-foreground">
        We&apos;ll only use this to send you a receipt.
      </p>
    </div>
  )
}

Search

A leading search icon and a trailing `⌘K` hint built with the `Kbd` component. The icon is `pointer-events-none` so clicks fall through to the field.

Packages

lucide-react
input
kbd

Props

No props documented yet.

Copied!
components/ui/input-search-01.tsx
import { Search } from "lucide-react"

import { Input } from "@/components/ui/input"
import { Kbd } from "@/components/ui/kbd"

export default function InputSearch01() {
  return (
    <div className="relative w-full max-w-sm">
      <Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
      <Input type="search" placeholder="Search…" className="pl-9 pr-12" />
      <Kbd className="absolute right-2 top-1/2 -translate-y-1/2">⌘K</Kbd>
    </div>
  )
}

Password

A show/hide toggle that swaps the input `type` between `password` and `text` — built with a ghost icon `Button` and an `aria-label`.

Packages

lucide-react
input
label
button

Props

No props documented yet.

Copied!
components/ui/input-password-01.tsx
"use client"

import * as React from "react"
import { Eye, EyeOff } from "lucide-react"

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function InputPassword01() {
  const [visible, setVisible] = React.useState(false)

  return (
    <div className="grid w-full max-w-sm gap-2">
      <Label htmlFor="password">Password</Label>
      <div className="relative">
        <Input
          id="password"
          type={visible ? "text" : "password"}
          placeholder="Enter your password"
          className="pr-10"
        />
        <Button
          type="button"
          variant="ghost"
          size="icon-sm"
          onClick={() => setVisible((v) => !v)}
          aria-label={visible ? "Hide password" : "Show password"}
          className="absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground"
        >
          {visible ? <EyeOff /> : <Eye />}
        </Button>
      </div>
    </div>
  )
}

With button

An inline field and a submit `Button` in one row — the pattern behind newsletter and waitlist sign-ups.

Packages

input
button

Props

No props documented yet.

Copied!
components/ui/input-button-01.tsx
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"

export default function InputButton01() {
  return (
    <form className="flex w-full max-w-sm items-center gap-2">
      <Input type="email" placeholder="[email protected]" aria-label="Email" />
      <Button type="submit">Subscribe</Button>
    </form>
  )
}

States

Invalid (`aria-invalid` switches on the destructive ring and border, paired with `aria-describedby`), disabled, and read-only fields.

Packages

input
label

Props

No props documented yet.

Copied!
components/ui/input-validation-01.tsx
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function InputValidation01() {
  return (
    <div className="grid w-full max-w-sm gap-6">
      <div className="grid gap-2">
        <Label htmlFor="email-invalid">Email</Label>
        <Input
          id="email-invalid"
          type="email"
          defaultValue="not-an-email"
          aria-invalid="true"
          aria-describedby="email-invalid-error"
        />
        <p id="email-invalid-error" className="text-sm text-destructive">
          Enter a valid email address.
        </p>
      </div>
      <div className="grid gap-2">
        <Label htmlFor="email-disabled">Disabled</Label>
        <Input
          id="email-disabled"
          type="email"
          placeholder="[email protected]"
          disabled
        />
      </div>
      <div className="grid gap-2">
        <Label htmlFor="email-readonly">Read only</Label>
        <Input
          id="email-readonly"
          type="email"
          defaultValue="[email protected]"
          readOnly
        />
      </div>
    </div>
  )
}

File

A native `type="file"` input styled to match — the upload button comes from `file:` pseudo-element classes, no JavaScript replacement needed.

Packages

input
label

Props

No props documented yet.

Copied!
components/ui/input-file-01.tsx
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function InputFile01() {
  return (
    <div className="grid w-full max-w-sm gap-2">
      <Label htmlFor="attachment">Attachment</Label>
      <Input id="attachment" type="file" />
      <p className="text-sm text-muted-foreground">PDF or DOCX, up to 5 MB.</p>
    </div>
  )
}

With add-ons

Leading and trailing text add-ons (`https://` … `.com`) wrap the field; the border and focus ring move to the group so it reads as one control.

Packages

input
label

Props

No props documented yet.

Copied!
components/ui/input-addon-01.tsx
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function InputAddon01() {
  return (
    <div className="grid w-full max-w-sm gap-2">
      <Label htmlFor="site">Website</Label>
      <div className="flex h-9 w-full items-center rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 dark:bg-input/30">
        <span className="select-none pl-3 text-sm text-muted-foreground">
          https://
        </span>
        <Input
          id="site"
          type="text"
          placeholder="acme"
          className="h-auto border-0 bg-transparent px-1 shadow-none focus-visible:border-0 focus-visible:ring-0 dark:bg-transparent"
        />
        <span className="select-none pr-3 text-sm text-muted-foreground">
          .com
        </span>
      </div>
    </div>
  )
}

Character counter

A live `current/max` count rendered in `tabular-nums`, backed by the input’s `maxLength`.

Packages

input
label

Props

No props documented yet.

Copied!
components/ui/input-counter-01.tsx
"use client"

import * as React from "react"

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function InputCounter01() {
  const maxLength = 40
  const [value, setValue] = React.useState("")

  return (
    <div className="grid w-full max-w-sm gap-2">
      <Label htmlFor="title">Title</Label>
      <div className="relative">
        <Input
          id="title"
          value={value}
          maxLength={maxLength}
          onChange={(e) => setValue(e.target.value)}
          placeholder="A short, descriptive title"
          className="pr-16"
        />
        <span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs tabular-nums text-muted-foreground">
          {value.length}/{maxLength}
        </span>
      </div>
    </div>
  )
}

Frequently asked questions

Yes. It is the canonical shadcn/ui input — a native input element styled with the same CSS variable tokens and aria-invalid error handling — so it drops into any shadcn/ui project. On top of it we ship ready-made examples for labels, search, passwords, files, add-ons, and validation.
Render the Label component with an htmlFor that matches the input id. Clicking the label then focuses the field and screen readers announce the two together. The first example shows the full pattern with helper text.
Wrap the input in a relative container and absolutely position the icon, adding left padding (pl-9) so the text clears it. Mark the icon pointer-events-none so clicks pass through to the field. The Search example also adds a ⌘K hint with the Kbd component.
Keep a piece of state for visibility and switch the input type between password and text. Place a small ghost icon button inside the field that toggles the state, and give it an aria-label so its purpose is announced.
Set aria-invalid on the input — that switches on the destructive border and ring automatically, no extra class needed. Link the error message with aria-describedby so assistive tech reads it alongside the field.
Yes. The input is styled with CSS variable tokens like --input, --ring, and --destructive, so it follows your theme automatically, including dark mode and any palette from a shadcn-compatible theme generator.