zeroUI

Input

A text field as an inset well: the void for a fill, a quiet outline, the accent for a caret. A click almost anywhere on the slab lands in it.

Install

terminal
# once per project: where @zeroui items come frompnpm dlx shadcn@latest registry add @zeroui=https://zeroui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zeroui/input

Examples

Basic

input-basic.tsx
import { Input } from "@/components/zero/input";export default function InputBasic() {  return (    <Input      placeholder="Search the board"      aria-label="Search the board"      className="max-w-xs"    />  );}

Sizes

input-sizes.tsx
import { Input } from "@/components/zero/input";export default function InputSizes() {  return (    <div className="grid w-full max-w-xs gap-4">      <Input size="sm" placeholder="Small" aria-label="Small" />      <Input size="md" placeholder="Medium" aria-label="Medium" />      <Input size="lg" placeholder="Large" aria-label="Large" />    </div>  );}

An icon or an action at either end

input-slots.tsx
import { useState } from "react";import {  ClearIcon,  EyeIcon,  EyeOffIcon,  SearchIcon,} from "@/components/zero/icons";import { Input, InputAction } from "@/components/zero/input";export default function InputSlots() {  const [query, setQuery] = useState("rail yards");  const [shown, setShown] = useState(false);  return (    <div className="grid w-full max-w-xs gap-4">      <Input        value={query}        onChange={(event) => setQuery(event.target.value)}        placeholder="Search the board"        aria-label="Search the board"        start={<SearchIcon />}        end={          query ? (            <InputAction aria-label="Clear" onClick={() => setQuery("")}>              <ClearIcon />            </InputAction>          ) : null        }      />      <Input        type={shown ? "text" : "password"}        defaultValue="lantern-42"        aria-label="Access key"        end={          <InputAction            aria-label={shown ? "Hide the key" : "Show the key"}            onClick={() => setShown((value) => !value)}          >            {shown ? <EyeOffIcon /> : <EyeIcon />}          </InputAction>        }      />    </div>  );}

Invalid and disabled

input-states.tsx
import { Input } from "@/components/zero/input";export default function InputStates() {  return (    <div className="grid w-full max-w-xs gap-4">      <Input defaultValue="no@" aria-invalid aria-label="Invalid" />      <Input placeholder="Disabled" disabled aria-label="Disabled" />    </div>  );}

Behaviour

Focus draws the selection frame, also while an InputAction inside it has focus. An invalid value turns the outline to danger: inside a Field that follows its validation, outside one it follows aria-invalid.

className styles the frame (its width, its margins); every other prop goes to the input itself.

Props

Input

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"32, 40 or 52 px tall.
cut"end" | "start" | "both" | "none""end"Which edges of the well are cut on the slant.
startReactNode—Before the text: usually an icon. The input gives up its padding on that side.
endReactNode—After the text: an icon or an InputAction (clear, show the password).

Also takes every prop of Base UI's Input.

InputAction

PropTypeDefaultDescription
aria-labelstring—Required: the action holds only an icon, so this is its name.

Also takes every prop of Base UI's Button.