Skip to main content

Form & Field

Stable

Form provides layout and react-hook-form context. Field is the missing layer every project rebuilds: label + input + hint + error, automatically wired together with correct ARIA associations.

Form elementContext providerreact-hook-form

Interactive example

We'll never share your email.
I agree to the terms
Contact form with Field

Import

tsx
import { Form, Field } from '@vhyxui/react'

Field

Field is the differentiator. Developers rebuild this on every project. VhyxUI makes it first class.

Please enter a valid email address.
Field with error
At least 8 characters, including a number.
Field with hint
Required and optional indicators

Layouts

Vertical (default)
Disabled form

Props

Form

PropTypeDefaultDescription
formUseFormReturn<FieldValues>react-hook-form return value. When provided, errors propagate automatically from form state.
layout'vertical' | 'horizontal' | 'inline''vertical'Field layout direction within this form.
size'sm' | 'md' | 'lg''md'Size propagated to all form controls.
disabledbooleanfalseDisables all form controls at once.
onSubmit(data: FieldValues) => void | Promise<void>Called with validated form data on submission.

Also accepts all standard HTMLFormElement attributes.

Field

PropTypeDefaultDescription
name *stringField name — used to read errors from react-hook-form context.
labelReact.ReactNodeVisible label rendered above the input.
hintReact.ReactNodeHelper text rendered below the input.
errorstringError message. Overrides any error from react-hook-form context.
requiredbooleanShows required indicator (*) and sets aria-required on the child.
optionalbooleanShows an optional indicator next to the label.
layout'vertical' | 'horizontal'Overrides the Form layout for this specific field only.
childrenReact.ReactNodeThe input component to render.

Accessibility

  • Field generates unique IDs and connects label[for] → input automatically.
  • Hint and error text are connected via aria-describedby — announced by screen readers.
  • Error text has role="alert" — announced immediately when it appears.
  • aria-invalid propagated to child input when an error is present.
  • Required indicator (*) is decorative — screen readers hear "required" from aria-required.
  • Disabled Form sets disabled on all child inputs and aria-disabled where needed.

Keyboard navigation

KeyAction
TabMove focus to the next field or focusable element.
Shift + TabMove focus to the previous field or focusable element.
EnterSubmit the form when focus is on a submit button.

Agent contract

Default VhyxSeal contract shipped with every Form.

Default contract
{
  "type": "action",
  "intent": "submit-form",
  "description": "Collects field values and submits them to the server or handler",
  "requires": [],
  "requiredPermissions": [],
  "consequence": "Sends form data to the configured handler — may create or update records",
  "affects": [
    "form"
  ],
  "reversible": false,
  "safetyLevel": "medium",
  "requiresConfirmation": false,
  "destructive": false,
  "contractVersion": "0.0.1",
  "fingerprint": "vhyxs_082c611e"
}

Individual field inputs carry their own contracts. The Form contract represents the submission action.

Theming

Override these CSS tokens to theme Form and Field.

--vhyx-text-smLabel font size
--vhyx-weight-mediumLabel font weight
--vhyx-color-dangerError text color
--vhyx-color-danger-textError message color
--vhyx-color-text-mutedHint text color
--vhyx-color-text-subtleOptional indicator color
--vhyx-space-1-5Label bottom gap
--vhyx-space-4Field gap (between label and input)

Examples

react-hook-form + zod

tsx
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Form, Field, Input, Button } from '@vhyxui/react'

const schema = z.object({
  email: z.string().email('Please enter a valid email'),
  password: z.string().min(8, 'At least 8 characters required'),
})

function LoginForm() {
  const form = useForm({ resolver: zodResolver(schema) })

  return (
    <Form form={form} onSubmit={form.handleSubmit(onSubmit)}>
      <Field name="email" label="Email" required>
        <Input
          {...form.register('email')}
          type="email"
          placeholder="you@example.com"
          error={!!form.formState.errors.email}
        />
      </Field>
      <Field name="password" label="Password" hint="At least 8 characters" required>
        <Input
          {...form.register('password')}
          type="password"
          error={!!form.formState.errors.password}
        />
      </Field>
      <Button type="submit" loading={form.formState.isSubmitting}>
        Log in
      </Button>
    </Form>
  )
}

Manual error control

tsx
<Form>
  <Field name="username" label="Username" error={serverError}>
    <Input value={username} onChange={e => setUsername(e.target.value)} />
  </Field>
</Form>