Examples
Label and description
Keep the visible label and supporting instruction in one logical group.
Use the name people see in invitations.
Field with a label and description
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
} from "@phuctech/ui/components/field";
export function Example() {
return (
<Field invalid>
<FieldLabel>Workspace name</FieldLabel>
<FieldControl defaultValue="PhucTech" aria-invalid="true" />
<FieldDescription>Use the name people see in invitations.</FieldDescription>
<FieldError match>Choose a name with at least three characters.</FieldError>
</Field>
);
}
Required value
Mark required form semantics on the control while keeping the instruction visible.
Required to create a workspace.
Required Field
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
} from "@phuctech/ui/components/field";
export function Example() {
return (
<Field invalid>
<FieldLabel>Workspace name</FieldLabel>
<FieldControl defaultValue="PhucTech" aria-invalid="true" />
<FieldDescription>Use the name people see in invitations.</FieldDescription>
<FieldError match>Choose a name with at least three characters.</FieldError>
</Field>
);
}Validation error
A specific error is connected to the invalid control and announced politely.
Use the name people see in invitations.
Invalid Field with a validation error
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
} from "@phuctech/ui/components/field";
export function Example() {
return (
<Field invalid>
<FieldLabel>Workspace name</FieldLabel>
<FieldControl defaultValue="PhucTech" aria-invalid="true" />
<FieldDescription>Use the name people see in invitations.</FieldDescription>
<FieldError match>Choose a name with at least three characters.</FieldError>
</Field>
);
}Input and Switch composition
Field groups different controls without taking ownership of business state.
Receive one summary each Monday.
Field compositions for text and binary controls
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
} from "@phuctech/ui/components/field";
export function Example() {
return (
<Field invalid>
<FieldLabel>Workspace name</FieldLabel>
<FieldControl defaultValue="PhucTech" aria-invalid="true" />
<FieldDescription>Use the name people see in invitations.</FieldDescription>
<FieldError match>Choose a name with at least three characters.</FieldError>
</Field>
);
}Long supporting content
Long guidance wraps naturally and remains readable at narrow widths.
Explain the trigger, the people affected, and what someone should check before enabling this automation in a production workspace.
Field with long supporting content
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
} from "@phuctech/ui/components/field";
export function Example() {
return (
<Field invalid>
<FieldLabel>Workspace name</FieldLabel>
<FieldControl defaultValue="PhucTech" aria-invalid="true" />
<FieldDescription>Use the name people see in invitations.</FieldDescription>
<FieldError match>Choose a name with at least three characters.</FieldError>
</Field>
);
}When to use
Use Field for one logical input and its guidance. It works with Input and other native or Base UI controls; validation remains the responsibility of the form or feature. For a binary setting, compose Field with Switch so the label and description stay connected while the feature owns the setting state.
Usage
import {
Field,
FieldControl,
FieldDescription,
FieldError,
FieldLabel,
} from "@phuctech/ui/components/field";
export function Example() {
return (
<Field invalid>
<FieldLabel>Workspace name</FieldLabel>
<FieldControl defaultValue="PhucTech" aria-invalid="true" />
<FieldDescription>Use the name people see in invitations.</FieldDescription>
<FieldError match>Choose a name with at least three characters.</FieldError>
</Field>
);
}
Use FieldControl when the Base UI field should own the input relationship. Use Input inside Field when you need the Input API, and connect IDs explicitly when you control the validation message.
Settings example
This example keeps the form state in the feature: Field only describes the relationship, while the screen owns validation, save/cancel, and loading behavior.
Settings form
Keep validation and save/cancel state in the feature while the shared components preserve the relationships.
Settings form with Field, Input, validation, and save/cancel actions
"use client";
import { useState } from "react";
import { Button } from "@phuctech/ui/components/button";
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@phuctech/ui/components/card";
import { Field, FieldLabel } from "@phuctech/ui/components/field";
import { Input } from "@phuctech/ui/components/input";
export function WorkspaceSettings() {
const [name, setName] = useState("PhucTech");
const [savedName, setSavedName] = useState("PhucTech");
const changed = name !== savedName;
return (
<Card>
<form onSubmit={(event) => { event.preventDefault(); setSavedName(name); }}>
<CardHeader><CardTitle>Workspace settings</CardTitle></CardHeader>
<CardContent>
<Field>
<FieldLabel htmlFor="workspace-name">Workspace name</FieldLabel>
<Input id="workspace-name" value={name} onChange={(event) => setName(event.target.value)} />
</Field>
</CardContent>
<CardFooter>
<Button type="submit" disabled={!changed}>Save changes</Button>
<Button type="button" variant="outline" disabled={!changed} onClick={() => setName(savedName)}>Discard</Button>
</CardFooter>
</form>
</Card>
);
}API
Tokens
Accessibility
- Keep the visible label, control, and error in one logical group.
- Use
aria-invalidonly when the value is invalid, and connect the explanation witharia-describedby. - Do not announce validation on every keystroke unless the task genuinely benefits from live feedback.
Do and don’t
Let the feature own the rule and loading state; let Field own the accessible grouping.
Put business requests, fetch calls, or form submission logic inside Field.