Input
A hand-drawn Base UI input styled with shadcn conventions.
Input
A hand-drawn Base UI input across its types and states.
Default
Types
With value
Invalid
Disabled
Installation
npx shadcn@latest add https://sketchcn.chuwii.com/r/input.jsonStyles
Input leans on these rules from sketch.css. The shadcn CLI installs the file with sketch-provider; copying the component by hand means copying them too.
[data-sketch-outline] path {
stroke-dasharray: var(--sketch-dash, none);
animation: var(--sketch-dash-animation, none);
}
@keyframes sketch-dash-boil {
to {
stroke-dashoffset: -10;
}
}Read the whole file for the drawing variables, paper textures and reduced-motion rules.
Usage
import { Input } from "@/components/ui/input";
export function SearchField() {
return <Input placeholder="Sketch something" />;
}Props
Input adds no props of its own. Every prop is forwarded to the Base UI Input primitive, which documents them.
States
- Focus draws the outline in the ring colour.
aria-invaliddraws it in the destructive colour.disableddrops the opacity and blocks pointer events.
Examples
Labelled field with a hint
We only use this to send you sketches.
import { Input } from "@/components/ui/input";
export function EmailField() {
const id = useId();
return (
<div className="flex w-full flex-col gap-1.5">
<label htmlFor={id} className="text-sm">
Email
</label>
<Input id={id} type="email" placeholder="[email protected]" />
<span className="text-muted-foreground text-xs">
We only use this to send you sketches.
</span>
</div>
);
}Search bar
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export function SearchBar({ onSearch }: { onSearch: (term: string) => void }) {
const [term, setTerm] = useState("");
return (
<div className="flex w-full items-center gap-2">
<Input
value={term}
onChange={(event) => setTerm(event.target.value)}
placeholder="Search sketches"
/>
<Button size="icon" aria-label="Search" onClick={() => onSearch(term)}>
<Search />
</Button>
</div>
);
}Live validation
import { Input } from "@/components/ui/input";
export function UsernameField() {
const [username, setUsername] = useState("");
const isTooShort = username.length > 0 && username.length < 3;
return (
<div className="flex w-full flex-col gap-1.5">
<Input
value={username}
aria-invalid={isTooShort}
onChange={(event) => setUsername(event.target.value)}
placeholder="Username"
/>
{isTooShort && (
<span className="text-destructive text-xs">
Usernames need at least 3 characters.
</span>
)}
</div>
);
}Animation
The file-selector outline boils while the field is hovered, using the sketch-dash-boil keyframe from sketch.css.
// on the svg drawn over the file-selector button
"group-hover/input:[--sketch-dash:6_4] group-hover/input:[--sketch-dash-animation:sketch-dash-boil_1s_steps(2)_infinite]"<Input type="file" className="[--sketch-dash-animation:none]" />