@usevyre/react @usevyre/vue

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.

We'll never share it.
import { Field, Input } from "@usevyre/react";

<Field label="Email" hint="We'll never share it." required>
  <Input type="email" placeholder="you@example.com" />
</Field>

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>

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>

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.

Used for sign-in and receipts.

Markdown is supported.

Notifications
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>

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.