ComponentsFormField
FormField
Connect a form control to its label, help text, and error message.
On this page
Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.
View registry filesImplemented with React and browser APIs, without an additional behavior or rendering library.
Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.
Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.
Import the component into your page or component file.
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
Form,
} from "@dethink/components";Try the examples, then open the code to use them in your app.
Label, description, and error
FieldControl injects the wiring into the wrapped control — the description is announced with the field, and the error replaces it when invalid.
Lowercase letters, numbers, and hyphens only.
Show sourceexamples/form-field/basic.tsx
"use client";
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
Input,
} from "@dethink/components";
export function FormFieldBasic() {
return (
<div className="mx-auto grid max-w-md gap-6">
<Field id="ff-handle">
<FieldLabel>Handle</FieldLabel>
<FieldControl asChild>
<Input placeholder="acme-corp" />
</FieldControl>
<FieldDescription>
Lowercase letters, numbers, and hyphens only.
</FieldDescription>
</Field>
<Field id="ff-api-key" invalid>
<FieldLabel>API key</FieldLabel>
<FieldControl asChild>
<Input defaultValue="sk_live_…" />
</FieldControl>
<FieldError>This key was revoked on June 2.</FieldError>
</Field>
</div>
);
}Fieldset groups
FieldSet and FieldLegend name a related set; horizontal fields put the control before its label — the checkbox layout.
Show sourceexamples/form-field/group.tsx
"use client";
import {
Checkbox,
Field,
FieldControl,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
} from "@dethink/components";
export function FormFieldGroup() {
return (
<div className="mx-auto max-w-sm">
<FieldSet>
<FieldLegend>Notification channels</FieldLegend>
<FieldGroup>
<Field id="ffg-email" orientation="horizontal">
<FieldControl asChild>
<Checkbox name="channels" value="email" defaultChecked />
</FieldControl>
<FieldLabel>Email</FieldLabel>
</Field>
<Field id="ffg-slack" orientation="horizontal">
<FieldControl asChild>
<Checkbox name="channels" value="slack" />
</FieldControl>
<FieldLabel>Slack</FieldLabel>
</Field>
<Field id="ffg-sms" orientation="horizontal" disabled>
<FieldControl asChild>
<Checkbox name="channels" value="sms" />
</FieldControl>
<FieldLabel>SMS (requires verified phone)</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
</div>
);
}Examples that combine components for common tasks.
Create-project form
One anatomy across four control types — Input, Select, Textarea, and Switch — with on-submit validation rendered through FieldError so each error is announced with its field.
Show sourceexamples/form-field/recipe-project-form.tsx
"use client";
import { useState } from "react";
import {
Button,
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
Form,
Input,
Select,
SelectItem,
Switch,
Textarea,
} from "@dethink/components";
type Errors = Partial<Record<"name" | "region", string>>;
/**
* One Field wrapper per control — Input, Select, Textarea, Switch — shows the
* same anatomy carrying every control type. Validation runs on submit and
* errors render through FieldError so they are announced with the field.
*/
export function FormFieldRecipeProjectForm() {
const [errors, setErrors] = useState<Errors>({});
const [created, setCreated] = useState(false);
return (
<Form
className="mx-auto max-w-sm space-y-5"
onSubmit={(event) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
const nextErrors: Errors = {};
if (!String(data.get("project-name") ?? "").trim()) {
nextErrors.name = "Give the project a name.";
}
if (!data.get("project-region")) {
nextErrors.region = "Pick a region before creating.";
}
setErrors(nextErrors);
setCreated(Object.keys(nextErrors).length === 0);
}}
>
<Field id="proj-name" invalid={Boolean(errors.name)} required>
<FieldLabel>Project name</FieldLabel>
<FieldControl asChild>
<Input name="project-name" placeholder="apollo" />
</FieldControl>
{errors.name ? <FieldError>{errors.name}</FieldError> : null}
</Field>
<Select
label="Region"
name="project-region"
placeholder="Choose a region"
invalid={Boolean(errors.region)}
errorMessage={errors.region}
required
>
<SelectItem value="us-east">US East</SelectItem>
<SelectItem value="eu-west">EU West</SelectItem>
</Select>
<Field id="proj-desc">
<FieldLabel>Description</FieldLabel>
<FieldControl asChild>
<Textarea name="project-description" rows={2} />
</FieldControl>
<FieldDescription>
Optional, shown on the project card.
</FieldDescription>
</Field>
<Field id="proj-public" orientation="horizontal">
<FieldControl asChild>
<Switch name="project-public" />
</FieldControl>
<FieldLabel>Public project</FieldLabel>
</Field>
<div className="flex items-center justify-between">
<p aria-live="polite" className="text-muted-foreground text-sm">
{created ? "Project created ✓" : ""}
</p>
<Button type="submit">Create project</Button>
</div>
</Form>
);
}Field carries the shared state; the part components read it through context.
| Prop | What it does | Default |
|---|---|---|
idstring | Base id that wires the control, label, description, and error together. | generated |
orientation"vertical" | "horizontal" | Vertical stacks label over control; horizontal puts the control first — the checkbox/switch layout. | "vertical" |
required / disabled / readOnly / invalidboolean | Field-level states forwarded to the control and reflected in labels and messages. | false |
aselement | Semantic element the field renders as. | "div" |
| Prop | What it does | Default |
|---|---|---|
FieldControlasChild + slot props | Wraps the actual control and injects id, aria-describedby, aria-errormessage, and state attributes. | Not set |
FieldLabellabel props | Label associated with the control; click focuses/toggles it. | Not set |
FieldDescriptiontext props | Muted helper text announced via aria-describedby. | Not set |
FieldErrorchildren | errors: FieldErrorItem[] | Validation message announced via aria-errormessage; accepts a list of error items. | Not set |
FieldContentcontainer props | Groups label and description beside a horizontal control (checkbox, switch). | Not set |
FieldSet / FieldLegend / FieldGroupfieldset anatomy | Native fieldset semantics for related fields — the wrapper for radio groups and checkbox sets. | Not set |
Formform props | Styled form wrapper with consistent vertical rhythm. | Not set |