Form composition
Form provider and compound parts for building custom form fields
The <Form> provider plus the seven compound parts (FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage) are the building blocks for any form. Use them when no prebuilt field fits, or when you need full control over a field's layout.
For standalone inputs that you can drop in without <Form>, see Controls.
Prerequisites
- Complete the @mdk/core installation and add the dependency
- Familiarity with React hook form
Components
| Component | Description |
|---|---|
Form | Form wrapper with validation and submission handling |
FormControl | Slot wrapper that wires ARIA attributes onto a control |
FormDescription | Helper text paragraph rendered below a field |
FormField | Controller wrapper that provides field context to descendants |
FormItem | Layout wrapper grouping a label, control, description, and message |
FormLabel | Label that auto-links to the form field input |
FormMessage | Validation message paragraph for a form field |
Form
Form wrapper with validation and submission handling built on react-hook-form.
The package includes a complete form system built on React hook form. <Form> is the context provider; the other six components on this page are the building blocks you compose inside it. See Prebuilt fields for ready-made wrappers that render a labelled, validated field in a single tag.
Import
import {
Form,
FormField,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
useFormField,
} from '@mdk/core'Props
| Prop | Type | Default | Description |
|---|---|---|---|
form | UseFormReturn | required | The instance returned by useForm() |
children | ReactNode | required | Form content |
onSubmit | FormEventHandler | none | Submit handler. Wrap with form.handleSubmit(...) to get parsed values |
Any other standard <form> element attribute (id, action, noValidate, onReset, ref, data-*, aria-*, etc.) is forwarded to the underlying DOM element.
Basic usage
import { useForm } from 'react-hook-form'
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage, Input, Button } from '@mdk/core'
function MyForm() {
const form = useForm({
defaultValues: { email: '' },
})
const onSubmit = (values) => {
console.log(values)
}
return (
<Form form={form} onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="email@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</Form>
)
}Built-in validators
The package re-exports common Zod schemas and validator helpers so you can wire useForm to a resolver without rolling your own.
import { validators, loginSchema, registerSchema } from '@mdk/core'Styling
.mdk-form: Root<form>element
FormField
Wraps react-hook-form's Controller and provides field context to descendants so FormItem, FormLabel, FormControl, and FormMessage can read field state.
Import
import { FormField } from '@mdk/core'Props
FormField is a thin wrapper over React hook form's Controller and accepts the same props (control, name, defaultValue, rules, render, etc.). It additionally provides field context to descendants like FormLabel and FormControl so they can wire up ARIA attributes automatically.
Basic usage
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>FormItem
Layout wrapper for a form field. Generates a unique id used by FormLabel, FormControl, FormDescription, and FormMessage for accessibility linking.
Import
import { FormItem } from '@mdk/core'Props
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | none | Extra classes merged onto the wrapper |
Any other standard <div> attribute (id, ref, data-*, aria-*, etc.) is forwarded to the underlying DOM element.
Basic usage
<FormField
control={form.control}
name="bio"
render={({ field }) => (
<FormItem>
<FormLabel>Bio</FormLabel>
<FormControl>
<TextArea {...field} />
</FormControl>
<FormDescription>Max 200 characters</FormDescription>
<FormMessage />
</FormItem>
)}
/>Styling
.mdk-form-item: Root element
FormLabel
Label that auto-links to the form field input via generated ids. Applies error styling when the field has a validation error.
Import
import { FormLabel } from '@mdk/core'Props
Accepts every prop the underlying Label accepts. The htmlFor attribute is set automatically from the FormItem context, and an error modifier class is applied when the field has a validation error.
Basic usage
<FormItem>
<FormLabel>Email address</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
</FormItem>Styling
.mdk-form-label: Root element.mdk-form-label--error: Applied when the field has a validation error
FormControl
Slot-based wrapper that injects id, aria-describedby, and aria-invalid onto its child input element without adding an extra DOM wrapper.
Import
import { FormControl } from '@mdk/core'Props
FormControl uses Radix Slot to merge its props onto its single child without adding an extra DOM node. It autoinjects id, aria-describedby, and aria-invalid based on the surrounding FormItem/FormField context.
Basic usage
<FormItem>
<FormLabel>Phone</FormLabel>
<FormControl>
<Input type="tel" {...field} />
</FormControl>
<FormMessage />
</FormItem>FormControl must wrap exactly one child element.
FormDescription
Optional helper text paragraph displayed below the input. Auto-linked to the control via aria-describedby.
Import
import { FormDescription } from '@mdk/core'Props
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | none | Extra classes merged onto the wrapper |
Any other standard <p> attribute (id, ref, data-*, aria-*, etc.) is forwarded to the underlying DOM element.
Basic usage
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormDescription>Must be at least 8 characters</FormDescription>
<FormMessage />
</FormItem>Styling
.mdk-form-description: Root element
FormMessage
Displays the validation error message from react-hook-form field state. Falls back to children when no error is present and always renders to prevent layout shift.
Import
import { FormMessage } from '@mdk/core'Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | none | Fallback content shown when the field has no validation error |
className | string | none | Extra classes merged onto the wrapper |
Any other standard <p> attribute (id, ref, data-*, aria-*, etc.) is forwarded to the underlying DOM element.
FormMessage always renders to prevent layout shift. When an error is present it shows the validation message and applies role="alert"; otherwise it shows children (or a non-breaking space placeholder) and adds the --empty modifier class.
Basic usage
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>Styling
.mdk-form-message: Root element.mdk-form-message--empty: Applied when no error and no children are present

