Component
Field
Label, help text, error message and accessibility attributes for a form control.
npx -y github:anwarlemu/agentcard-brand-cli#release add fieldWe reply within a day.
Enter a web address, like acme.com.
Usage
Wrap Input, Textarea, TagsInput or SelectTrigger. Derive error from the current value and clear it when valid. Use required for required fields and htmlFor when the control has an explicit id.
- Labels inside the field as placeholders
- Bold or caps labels
- Errors shown only by color
Dependencies: label.
Example
"use client";
import { useState } from "react";
import { Field } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
function isWebAddress(value: string) {
const text = value.trim();
if (!text || /\s/.test(text)) return false;
try {
const url = new URL(text.includes("://") ? text : `https://${text}`);
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password &&
url.hostname.includes(".") && url.hostname.split(".").every((part) => /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(part));
} catch { return false; }
}
export function Site() {
const [website, setWebsite] = useState("acme");
const valid = isWebAddress(website);
return (
<div className="grid max-w-170 gap-4">
<Field label="Work email" htmlFor="field-email" required help="We reply within a day.">
<Input id="field-email" type="email" autoComplete="email" />
</Field>
<Field label="Website" htmlFor="field-site" error={valid ? undefined : "Enter a web address, like acme.com."} help="Web address accepted.">
<Input id="field-site" value={website} onChange={(event) => setWebsite(event.target.value)} autoComplete="url" inputMode="url" />
</Field>
</div>
);
}Source
"use client";
import * as React from "react";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
type FieldState = { id: string; description?: string; invalid: boolean; required: boolean };
const FieldContext = React.createContext<FieldState | undefined>(undefined);
/** Connect a control to its Field without replacing its own IDs or accessibility descriptions. */
function useFieldControl<T extends { id?: string; required?: boolean; "aria-invalid"?: React.AriaAttributes["aria-invalid"]; "aria-describedby"?: string }>(props: T) {
const field = React.useContext(FieldContext);
return {
...props,
id: props.id ?? field?.id,
required: props.required ?? field?.required,
"aria-invalid": props["aria-invalid"] ?? (field?.invalid || undefined),
"aria-describedby": [props["aria-describedby"], field?.description].filter(Boolean).join(" ") || undefined,
};
}
// Validation belongs to the form; Field shares its current error, hint and required state with the control.
function Field({ label, htmlFor, help, error, required = false, className, children }: {
label: React.ReactNode; htmlFor?: string; help?: React.ReactNode; error?: React.ReactNode;
required?: boolean; className?: string; children: React.ReactNode;
}) {
const generatedId = React.useId();
const id = htmlFor ?? generatedId;
const message = error || help;
const description = message ? `${id}-description` : undefined;
return (
<FieldContext.Provider value={{ id, description, invalid: Boolean(error), required }}>
<div data-slot="field" data-invalid={error ? true : undefined} className={cn("flex flex-col gap-1.5", className)}>
<Label htmlFor={id} className="block font-display text-caption leading-snug text-fg-muted">
{label}
{required && <><span aria-hidden className="ml-0.5 text-danger">*</span><span className="sr-only"> (required)</span></>}
</Label>
{children}
<p id={`${id}-description`} aria-live="polite" aria-atomic="true" className={cn("text-body-sm leading-snug empty:hidden", error ? "text-danger" : "text-fg-muted")}>{message}</p>
</div>
</FieldContext.Provider>
);
}
export { Field, useFieldControl };Reference: agentcard-website src/app/globals.css (.v7-gate-field); live https://www.agentcard.sh/get-started.