Field
A form-field wrapper for labels, descriptions, validation messages, and
layout. Use the props-based API for the common case, or the
composable parts (FieldLabel,
FieldDescription, FieldError,
FieldGroup, FieldSet) for richer layouts. Both
APIs work together — the props-based form is unchanged and fully supported.
Props-based
The simplest form: pass label, hint,
state, and required. The control goes in the
default slot.
import { Field, Input } from "@usevyre/react";
<Field label="Email" hint="We'll never share it." required>
<Input type="email" placeholder="you@example.com" />
</Field> <script setup>
import { Field, Input } from "@usevyre/vue";
</script>
<template>
<Field label="Email" hint="We'll never share it." required>
<Input type="email" placeholder="you@example.com" />
</Field>
</template> Composable
Compose a field from explicit parts when you need custom message placement
or conditional error vs. description. FieldError renders
nothing when it has no children, so you can keep it always mounted.
Used for sign-in and receipts.
import {
Field, FieldLabel, FieldDescription, FieldError, Input,
} from "@usevyre/react";
const [val, setVal] = useState("");
const invalid = val.length > 0 && !val.includes("@");
<Field state={invalid ? "error" : "idle"}>
<FieldLabel required htmlFor="email">Email</FieldLabel>
<Input
id="email"
type="email"
value={val}
onChange={(e) => setVal(e.target.value)}
/>
{invalid ? (
<FieldError>Enter a valid email address.</FieldError>
) : (
<FieldDescription>Used for sign-in and receipts.</FieldDescription>
)}
</Field> <script setup>
import { ref, computed } from "vue";
import {
Field, FieldLabel, FieldDescription, FieldError, Input,
} from "@usevyre/vue";
const val = ref("");
const invalid = computed(() => val.value.length > 0 && !val.value.includes("@"));
</script>
<template>
<Field :state="invalid ? 'error' : 'idle'">
<FieldLabel required for="email">Email</FieldLabel>
<Input id="email" type="email" v-model="val" />
<FieldError v-if="invalid">Enter a valid email address.</FieldError>
<FieldDescription v-else>Used for sign-in and receipts.</FieldDescription>
</Field>
</template> Field group
Wrap multiple fields in FieldGroup to lay them out
horizontally (equal columns) or vertically.
import { FieldGroup, Field, Input } from "@usevyre/react";
<FieldGroup orientation="horizontal">
<Field label="First name"><Input placeholder="Ada" /></Field>
<Field label="Last name"><Input placeholder="Lovelace" /></Field>
</FieldGroup> <script setup>
import { FieldGroup, Field, Input } from "@usevyre/vue";
</script>
<template>
<FieldGroup orientation="horizontal">
<Field label="First name"><Input placeholder="Ada" /></Field>
<Field label="Last name"><Input placeholder="Lovelace" /></Field>
</FieldGroup>
</template> Complete form
A real registration form composing many fields together —
FieldGroup for the name row, validated Input
with conditional FieldError, a RadioGroup for the
plan, a Textarea bio, a FieldSet for grouped
notification options, and a Checkbox with an inline label.
Everything is controlled state — submit is disabled until it's valid.
import { useState } from "react";
import {
Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet,
Input, Textarea, Checkbox, RadioGroup, Button,
} from "@usevyre/react";
const [form, setForm] = useState({
name: "", email: "", plan: "pro", bio: "", notify: "email", terms: false,
});
const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));
const emailInvalid = form.email.length > 0 && !form.email.includes("@");
<form onSubmit={handleSubmit}>
<FieldGroup orientation="horizontal">
<Field label="First name" required>
<Input value={form.name} onChange={(e) => set("name", e.target.value)} />
</Field>
<Field label="Last name">
<Input placeholder="Lovelace" />
</Field>
</FieldGroup>
<Field state={emailInvalid ? "error" : "idle"}>
<FieldLabel required htmlFor="email">Email</FieldLabel>
<Input
id="email"
type="email"
value={form.email}
onChange={(e) => set("email", e.target.value)}
/>
{emailInvalid ? (
<FieldError>Enter a valid email address.</FieldError>
) : (
<FieldDescription>Used for sign-in and receipts.</FieldDescription>
)}
</Field>
<Field>
<FieldLabel>Plan</FieldLabel>
<RadioGroup
value={form.plan}
onChange={(v) => set("plan", v)}
options={[
{ value: "free", label: "Free", description: "For hobby projects" },
{ value: "pro", label: "Pro", description: "For small teams" },
{ value: "ent", label: "Enterprise", description: "SSO, audit log, SLA" },
]}
/>
</Field>
<Field>
<FieldLabel htmlFor="bio">Bio</FieldLabel>
<Textarea
id="bio"
rows={3}
value={form.bio}
onChange={(e) => set("bio", e.target.value)}
/>
<FieldDescription>Markdown is supported.</FieldDescription>
</Field>
<FieldSet legend="Notifications">
<RadioGroup
value={form.notify}
onChange={(v) => set("notify", v)}
orientation="horizontal"
options={[
{ value: "email", label: "Email" },
{ value: "sms", label: "SMS" },
{ value: "none", label: "None" },
]}
/>
</FieldSet>
<Field>
<div style={{ display: "flex", alignItems: "flex-start", gap: 8 }}>
<Checkbox
id="terms"
checked={form.terms}
onCheckedChange={(c) => set("terms", c)}
/>
<label htmlFor="terms">I accept the terms and conditions</label>
</div>
</Field>
<Button type="submit" variant="primary">Create account</Button>
</form> <script setup>
import { ref, computed } from "vue";
import {
Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet,
Input, Textarea, Checkbox, RadioGroup, Button,
} from "@usevyre/vue";
const form = ref({
name: "", email: "", plan: "pro", bio: "", notify: "email", terms: false,
});
const emailInvalid = computed(
() => form.value.email.length > 0 && !form.value.email.includes("@")
);
</script>
<template>
<form @submit.prevent="handleSubmit">
<FieldGroup orientation="horizontal">
<Field label="First name" required>
<Input v-model="form.name" />
</Field>
<Field label="Last name">
<Input placeholder="Lovelace" />
</Field>
</FieldGroup>
<Field :state="emailInvalid ? 'error' : 'idle'">
<FieldLabel required for="email">Email</FieldLabel>
<Input id="email" type="email" v-model="form.email" />
<FieldError v-if="emailInvalid">Enter a valid email address.</FieldError>
<FieldDescription v-else>Used for sign-in and receipts.</FieldDescription>
</Field>
<Field>
<FieldLabel>Plan</FieldLabel>
<RadioGroup
v-model="form.plan"
:options="[
{ value: 'free', label: 'Free', description: 'For hobby projects' },
{ value: 'pro', label: 'Pro', description: 'For small teams' },
{ value: 'ent', label: 'Enterprise', description: 'SSO, audit log, SLA' },
]"
/>
</Field>
<Field>
<FieldLabel for="bio">Bio</FieldLabel>
<Textarea id="bio" :rows="3" v-model="form.bio" />
<FieldDescription>Markdown is supported.</FieldDescription>
</Field>
<FieldSet legend="Notifications">
<RadioGroup
v-model="form.notify"
orientation="horizontal"
:options="[
{ value: 'email', label: 'Email' },
{ value: 'sms', label: 'SMS' },
{ value: 'none', label: 'None' },
]"
/>
</FieldSet>
<Field>
<div style="display:flex; align-items:flex-start; gap:8px">
<Checkbox id="terms" v-model="form.terms" />
<label for="terms">I accept the terms and conditions</label>
</div>
</Field>
<Button type="submit" variant="primary">Create account</Button>
</form>
</template> Field props
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible field label (props-based API). |
hint | string | — | Helper text below the control. Turns into an error message when state="error". |
state | "idle" | "error" | "success" | "warning" | "idle" | Validation state — also styles the hint. |
required | boolean | false | Appends a required asterisk to the label. |
htmlFor / for | string | — | Links the label to a control id. |
className / class | string | — | Additional CSS class. |
Composable parts
Props
| Prop | Type | Default | Description |
|---|---|---|---|
FieldLabel | required?: boolean, htmlFor/for | — | Explicit label; required adds the asterisk. |
FieldDescription | — | — | Muted helper text below the control. |
FieldError | — | — | Error message (role="alert"); renders nothing when empty. |
FieldGroup | orientation?: "vertical" | "horizontal" | "vertical" | Lays out multiple fields/controls in a row or column. |
FieldSet | legend?: string | — | Semantic <fieldset> with optional <legend> for grouped inputs. |
Valid props
| Prop | Values | Default |
|---|---|---|
state | "idle"|"error"|"success"|"warning" | idle |
required | true|false | false |
Common AI mistakes
- Applying state prop directly to Input→ Wrap Input in <Field state="error"> to apply validation styling
- Mixing props label/hint AND FieldLabel/FieldError for the same field→ Pick one: either props-based (label/hint/state) OR composable parts
Quick examples
<Field label="Email" state="error" hint="Invalid email format">
<Input type="email" placeholder="you@example.com" />
</Field><Field label="Search">
<Input leftElement={<SearchIcon />} placeholder="Search..." />
</Field>