Copy to clipboard
We’ll never share your email.
<Field class="w-full max-w-sm">
<FieldLabel>Email</FieldLabel>
<Input type="email" placeholder="[email protected]" />
<FieldDescription>We'll never share your email.</FieldDescription>
</Field>
<Field class="w-full max-w-sm">
<FieldLabel>Email</FieldLabel>
<Input type="email" placeholder="[email protected]" />
<FieldDescription>We'll never share your email.</FieldDescription>
</Field><Field class="w-full max-w-sm">
<FieldLabel>Email</FieldLabel>
<Input type="email" placeholder="[email protected]" />
<FieldDescription>We'll never share your email.</FieldDescription>
</Field>
Installation
Copy to clipboard
npx bearnie add field
npx bearnie add fieldnpx bearnie add field
bunx bearnie add field
bunx bearnie add fieldbunx bearnie add field
pnpm dlx bearnie add field
pnpm dlx bearnie add fieldpnpm dlx bearnie add field
yarn dlx bearnie add field
yarn dlx bearnie add fieldyarn dlx bearnie add field
Usage
import {
Field,
FieldGroup,
FieldLabel,
FieldDescription,
FieldError,
} from "@/components/bearnie/field"<Field>
<FieldLabel>Email</FieldLabel>
<Input type="email" />
<FieldDescription>Helper text</FieldDescription>
<FieldError>Error message</FieldError>
</Field>Field automatically links its label to the control (for/id), connects the
description and error via aria-describedby, and sets aria-invalid on the
control when the field is invalid — no manual id wiring needed.
Examples
With error
Set invalid on the Field to show the FieldError and mark the control invalid.
Copy to clipboard
Username must be at least 3 characters.
<Field invalid class="w-full max-w-sm">
<FieldLabel>Username</FieldLabel>
<Input value="a" />
<FieldError>Username must be at least 3 characters.</FieldError>
</Field>
<Field invalid class="w-full max-w-sm">
<FieldLabel>Username</FieldLabel>
<Input value="a" />
<FieldError>Username must be at least 3 characters.</FieldError>
</Field><Field invalid class="w-full max-w-sm">
<FieldLabel>Username</FieldLabel>
<Input value="a" />
<FieldError>Username must be at least 3 characters.</FieldError>
</Field>
Form with FieldGroup
Copy to clipboard
<form class="w-full max-w-sm">
<FieldGroup>
<Field>
<FieldLabel required>Name</FieldLabel>
<Input name="name" required />
</Field>
<Field>
<FieldLabel>Message</FieldLabel>
<Textarea name="message" placeholder="Tell us more…" />
<FieldDescription>Max 500 characters.</FieldDescription>
</Field>
<Field class="grid-flow-col items-center justify-start gap-2">
<Checkbox name="terms" />
<FieldLabel>I agree to the terms</FieldLabel>
</Field>
<Button type="submit">Submit</Button>
</FieldGroup>
</form>
<form class="w-full max-w-sm">
<FieldGroup>
<Field>
<FieldLabel required>Name</FieldLabel>
<Input name="name" required />
</Field>
<Field>
<FieldLabel>Message</FieldLabel>
<Textarea name="message" placeholder="Tell us more…" />
<FieldDescription>Max 500 characters.</FieldDescription>
</Field>
<Field class="grid-flow-col items-center justify-start gap-2">
<Checkbox name="terms" />
<FieldLabel>I agree to the terms</FieldLabel>
</Field>
<Button type="submit">Submit</Button>
</FieldGroup>
</form><form class="w-full max-w-sm">
<FieldGroup>
<Field>
<FieldLabel required>Name</FieldLabel>
<Input name="name" required />
</Field>
<Field>
<FieldLabel>Message</FieldLabel>
<Textarea name="message" placeholder="Tell us more…" />
<FieldDescription>Max 500 characters.</FieldDescription>
</Field>
<Field class="grid-flow-col items-center justify-start gap-2">
<Checkbox name="terms" />
<FieldLabel>I agree to the terms</FieldLabel>
</Field>
<Button type="submit">Submit</Button>
</FieldGroup>
</form>
Runtime validation
Toggle data-invalid on the field element from your own validation code — ARIA
attributes and error visibility update automatically:
const field = input.closest("[data-field]");
input.addEventListener("blur", () => {
if (input.value.length < 3) {
field.setAttribute("data-invalid", "true");
} else {
field.removeAttribute("data-invalid");
}
});const field = input.closest("[data-field]");
input.addEventListener("blur", () => {
if (input.value.length < 3) {
field.setAttribute("data-invalid", "true");
} else {
field.removeAttribute("data-invalid");
}
});
Anatomy
Field- Wraps one control with its label, description, and error; handles all ARIA wiringFieldGroup- Spacing container for multiple fields in a formFieldLabel- Label, automatically linked to the field’s controlFieldDescription- Helper text, linked viaaria-describedbyFieldError- Error message; hidden until the field is invalid
Props
Field
| Prop | Type | Default | Description |
|---|---|---|---|
| invalid | boolean | false | Shows the error, sets aria-invalid on the control |
| class | string | - | Additional CSS classes |
FieldLabel
| Prop | Type | Default | Description |
|---|---|---|---|
| for | string | - | Control ID (set automatically if omitted) |
| required | boolean | false | Shows a required asterisk |
| class | string | - | Additional CSS classes |
FieldGroup, FieldDescription, and FieldError accept class for additional CSS classes.
Notes
Input, Textarea, Select, and Checkbox style themselves automatically when
aria-invalid is set (destructive border and focus ring), so invalid states work
with no extra classes.