Input
Text input primitive. Located at apps/web/components/ui/input.tsx.
Usage
<Input
label="Email address"
placeholder="you@example.com"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
error={validationError}
helperText="We'll never share your email."
/>
Props
label— optional, renders above the inputhelperText— optional, below the input in muted texterror— optional, renders in error color below (replaces helperText)- All native
<input>props pass through
Variants
Default
<Input label="Project name" />
With validation error
<Input
label="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
error={email && !email.includes("@") ? "Invalid email" : undefined}
/>
Border turns error-colored, helper text replaced with error text.
Password
<Input
type="password"
label="Password"
placeholder="Minimum 8 characters"
/>
Nothing special — just relies on the native password input. No "show password" toggle in the current impl.
Layout
Height: h-10 default (40px). Radius: --radius-sm (6px). Border: border-line. On focus: border-accent ring-1 ring-accent/20.
Always pair with a <label> either via the label prop or manually. Never rely on placeholder as label.
Don't
- Don't use
<input>native — use<Input>for consistency - Don't wrap in
<div>if you need vertical spacing — the parent'sflex flex-col gap-4handles it - Don't put inline styles for border color — the primitive handles focus/hover/error states