ComponentsFormField
FormField
Connect a form control to its label, help text, and error message.
On this page
Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.
View registry filesImport 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 |