Form

Accessible field structure for labels, descriptions, and validation around existing controls.

Overview

Form is a lightweight layout and accessibility layer for Vinyaas inputs — not a form-state framework. Keep values, schemas, and submit handling in your own React state or server actions.

Compose fields in this order: Form → FormField → FormItem → FormLabel, FormControl, optional FormDescription, and optional FormMessage.

  • FormField owns unique ids plus invalid and disabled state.
  • FormControl merges those associations onto a single child control.
  • For Select and Combobox, wrap the trigger — not the root provider.
  • Pass error into FormField to light up labels, borders, and alert messages with destructive tokens.

Preview

Profile

Update the name and email on your public account.

We will use this email for account notifications.

Installation

Add the component with the CLI. It copies the files in and installs anything they depend on.

npx vinyaas add form

Usage

import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; export function ProfileForm() { return ( <Card className="border-border w-full max-w-md"> <CardHeader> <CardTitle>Profile</CardTitle> <CardDescription> Update the name and email on your public account. </CardDescription> </CardHeader> <CardContent> <Form onSubmit={(event) => { event.preventDefault(); }} > <FormField name="name"> <FormItem> <FormLabel>Name</FormLabel> <FormControl> <Input placeholder="Ada Lovelace" autoComplete="name" /> </FormControl> </FormItem> </FormField> <FormField name="email"> <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input type="email" placeholder="ada@example.com" autoComplete="email" /> </FormControl> <FormDescription> We will use this email for account notifications. </FormDescription> </FormItem> </FormField> <div className="flex justify-end pt-1"> <Button type="submit">Save changes</Button> </div> </Form> </CardContent> </Card> ); }

Examples

Basic

A profile card with name, email, description, and a primary save action.

Profile

Update the name and email on your public account.

We will use this email for account notifications.

Settings

Username, theme select, and preference rows with Checkbox and Switch.

Preferences

Control how your workspace looks and what you hear about.

Your public handle across the workspace.


Email me about new components and release notes.

Show a system notification when someone @mentions you.

Validation

Pass error strings into FormField to wire labels, controls, and alert messages.

Create workspace

Required fields show destructive labels and messages when invalid.

Shown on invoices and invite links.

Submit again after fixing the fields.

Disabled

Organization-managed fields stay readable but cannot be edited.

Billing

These fields are managed by your organization administrator.

Contact billing to rename the organization.

Dashboard filters

Compose Combobox, Select, and Date Picker for dashboard filtering.

Issue filters

Combobox, Select, and Date Picker share the same field structure.

Inclusive — issues created on this day are included.

In practice

A workspace settings surface with Alert, Avatar, Badge, Combobox, Date Picker, Radio Group, validation, and notification rows.

Workspace settings

Identity, visibility, ownership, and notification defaults for this workspace.

Pro
VL

Vinyaas Labs

billing@vinyaas.dev · 24 seats

vinyaas.dev/vinyaas

Used for billing milestones and changelog cutoffs.

Only invited members can discover projects.

Anyone with the link can view published docs.

Shown on the organization directory and invite emails.


Notifications

Defaults for new members joining this workspace.

Email owners when API keys are created or revoked.

Monday summary of deploys, reviews, and empty queues.


Sends a Pro seat invite. Billing updates on acceptance.

API

Other attributes for the underlying element are passed through.

NameTypeDefaultDescription
Form classNamestring—Merged onto the native form. Default spacing is gap-5.
FormField namestring—Optional stable id seed for the field and its helpers.
FormField errorstring—Validation message. When set, the field becomes invalid and FormMessage can render it.
FormField invalidboolean—Force invalid styling without providing an error string.
FormField disabledbooleanfalsePropagates disabled to FormControl and dims the label/description.
FormItemdiv—Vertical stack for label, control, description, and message. Accepts layout className.
FormLabelLabel—Label wired with htmlFor. Turns destructive when the field is invalid.
FormControlsingle element—Clones one child with id, aria-describedby, aria-invalid, data-invalid, and disabled. Wrap SelectTrigger / ComboboxTrigger for those controls.
FormDescriptionp—Helper copy referenced from the control via aria-describedby.
FormMessagep—Renders FormField.error or children. Uses role=alert. Returns null when empty.
useFormFieldhook—Reads the active field context (ids, error, invalid, disabled) from nested parts.

Accessibility

  • Each field generates stable ids. FormLabel sets htmlFor; FormControl applies id, aria-describedby, and aria-invalid.
  • Description and message ids are only referenced while those nodes are mounted, so aria-describedby does not point at missing elements.
  • FormMessage uses role="alert" so assistive technology announces validation text when it appears.
  • Invalid labels use the destructive token. Controls that support aria-invalid pick up border emphasis from existing input styles.
  • Native keyboard behavior is preserved. Form does not trap focus or override Tab order.