Inputs are used to capture structured data from workspace admins, admins, supervisors, and workspace members. They are styled to be calm, readable, and appropriate for long work sessions with strong focus-visible affordances and theme-safe tokens.
Default input styling with label association, calm surface, and accessible focus ring.
import { Input } from "@/design-system/components/Input";
export function Example() {
return (
<Input
id="student-name"
label="Student name"
placeholder="Enter full name"
autoComplete="name"
/>
);
}
Error styling is token-driven and sets aria-invalid/aria-describedby via the Input component.
import { Input } from "@/design-system/components/Input";
export function Example() {
return (
<Input
id="goal-name"
label="Goal name"
placeholder="Enter goal name"
error="Goal name is required."
/>
);
}
Use helper text for format requirements, side effects, or system usage. Keep it short and scannable.
import { Input } from "@/design-system/components/Input";
export function Example() {
return (
<Input
id="student-id"
label="Student ID"
placeholder="e.g. THR-10429"
helper="Used for imports, reports, and audit logs."
/>
);
}
Use size variants to match surface density: tables/toolbars (sm), forms (md), primary workflows (lg).
import { Input } from "@/design-system/components/Input";
export function Example() {
return (
<div className="stack-sm">
<Input className="input--sm" label="Small" placeholder="Small input" />
<Input label="Default" placeholder="Default input" />
<Input className="input--lg" label="Large" placeholder="Large input" />
</div>
);
}
File inputs are styled using the same token-driven surface and provide a premium file selector button.
PDF or image. Max size enforced by server validation.
export function Example() {
return (
<div className="stack-sm">
<label className="input-label" htmlFor="upload">
Upload attachment
</label>
<input
id="upload"
className="input input--file"
type="file"
accept=".pdf,.png,.jpg,.jpeg"
/>
<p className="input-helper-text">
PDF or image. Max size enforced by server validation.
</p>
</div>
);
}
Use input groups for prefixes/suffixes and structured identifiers. Uses logical borders for RTL safety.
Use your organization slug (no spaces).
export function Example() {
return (
<div className="stack-sm">
<label className="input-label" htmlFor="website">
Website
</label>
<div className="input-group" role="group" aria-label="Website">
<span className="input-group__addon" aria-hidden="true">
https://
</span>
<input
id="website"
className="input input-group__control"
placeholder="aggarly"
autoComplete="off"
/>
<span className="input-group__addon" aria-hidden="true">
.com
</span>
</div>
<p className="input-helper-text">
Use your organization slug (no spaces).
</p>
</div>
);
}
Common enterprise pattern: search inputs with an icon prefix and an attached action button.
import { Button } from "@/design-system/components/Button";
function IconSearch() {
return (
<svg width="16" height="16" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M9 15a6 6 0 1 1 0-12 6 6 0 0 1 0 12Z" stroke="currentColor" strokeWidth="2" />
<path d="M14 14l3 3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}
export function Example() {
return (
<div className="stack-sm">
<label className="input-label" htmlFor="search">
Search students
</label>
<div className="input-group" role="group" aria-label="Search students">
<span className="input-group__addon input-group__addon--icon" aria-hidden="true">
<IconSearch />
</span>
<input
id="search"
className="input input-group__control"
placeholder="Name, ID, workspace member email…"
autoComplete="off"
/>
<Button variant="ghost" className="input-group__btn">
Search
</Button>
</div>
</div>
);
}
Group sizing keeps addons and attached buttons aligned. Use sm for toolbars and lg for primary workflows.
import { Button } from "@/design-system/components/Button";
export function Example() {
return (
<div className="stack-md">
<div className="stack-sm">
<label className="input-label" htmlFor="group-sm">Small group</label>
<div className="input-group input-group--sm" role="group" aria-label="Small group">
<span className="input-group__addon" aria-hidden="true">@</span>
<input id="group-sm" className="input input-group__control" placeholder="username" />
<Button size="sm" variant="ghost" className="input-group__btn">Invite</Button>
</div>
</div>
<div className="stack-sm">
<label className="input-label" htmlFor="group-lg">Large group</label>
<div className="input-group input-group--lg" role="group" aria-label="Large group">
<span className="input-group__addon" aria-hidden="true">+1</span>
<input id="group-lg" className="input input-group__control" placeholder="(555) 000-0000" />
<Button size="lg" variant="ghost" className="input-group__btn">Verify</Button>
</div>
</div>
</div>
);
}
Visual pattern for filter chips and attached dropdown actions. Connect your menu logic in product code.
This section focuses on styling + layout; dropdown behavior is implemented in your UI layer (e.g., Popover/Menu patterns).
import { Button } from "@/design-system/components/Button";
function IconChevronDown() {
return (
<svg width="16" height="16" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M5 7.5L10 12.5L15 7.5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export function Example() {
return (
<div className="stack-sm">
<label className="input-label" htmlFor="query">
Filter + search
</label>
<div className="input-group" role="group" aria-label="Filter and search">
<Button
variant="ghost"
className="input-group__btn"
trailingIcon={<IconChevronDown />}
aria-label="Select filter"
>
Status
</Button>
<input
id="query"
className="input input-group__control"
placeholder="Search…"
autoComplete="off"
/>
<Button variant="ghost" className="input-group__btn">
Apply
</Button>
</div>
<p className="input-helper-text">
This shows the visual pattern. Hook up your dropdown/menu logic in product code.
</p>
</div>
);
}
Use inputMode, autoComplete, placeholder, maxLength, and pattern for predictable enterprise data capture. Apply formatting in your UI logic when needed.
Use inputMode + maxLength; format onChange if needed.
ISO is recommended for exports and audit logs.
export function Example() {
return (
<div className="inputs-grid">
<div className="stack-sm">
<label className="input-label" htmlFor="phone">Phone (mask pattern)</label>
<input
id="phone"
className="input"
inputMode="tel"
autoComplete="tel-national"
placeholder="(555) 000-0000"
maxLength={14}
/>
<p className="input-helper-text">
Use inputMode + maxLength; apply formatting in your onChange handler if needed.
</p>
</div>
<div className="stack-sm">
<label className="input-label" htmlFor="cc">Card number (mask pattern)</label>
<input
id="cc"
className="input"
inputMode="numeric"
autoComplete="cc-number"
placeholder="1234 5678 9012 3456"
maxLength={19}
/>
</div>
<div className="stack-sm">
<label className="input-label" htmlFor="dob">Date (text format)</label>
<input
id="dob"
className="input"
inputMode="numeric"
placeholder="YYYY-MM-DD"
autoComplete="bday"
pattern="\d{4}-\d{2}-\d{2}"
/>
<p className="input-helper-text">ISO format recommended for exports and audit logs.</p>
</div>
</div>
);
}
Native date controls provide locale-aware pickers where available. Use text ISO formats when exporting or when UI requires strict formatting.
export function Example() {
return (
<div className="inputs-grid">
<div className="stack-sm">
<label className="input-label" htmlFor="date">Date</label>
<input id="date" className="input" type="date" />
</div>
<div className="stack-sm">
<label className="input-label" htmlFor="datetime">Date & time</label>
<input id="datetime" className="input" type="datetime-local" />
</div>
<div className="stack-sm">
<label className="input-label" htmlFor="month">Month</label>
<input id="month" className="input" type="month" />
</div>
</div>
);
}