Form
Form inputs, selects, checkboxes, and form validation components
Form components for building data entry interfaces.
Prerequisites
- Complete the installation
- Add the dependency to your app's
package.json
{
"dependencies": {
"@tetherto/mdk-react-devkit": "*",
"@tetherto/mdk-react-adapter": "*",
"@tetherto/mdk-ui-foundation": "*"
}
}Run npm install from the mdk/ui workspace root after your app is under apps/ so npm links workspace packages.
- Import styles:
import '@tetherto/mdk-react-devkit/styles.css'
Components
@tetherto/mdk-react-devkit
Core form workflow
import { Form, FormField } from '@tetherto/mdk-react-devkit'Form primitives built on react-hook-form. Use with a useForm() instance.
Pieces
Form— wraps a<form>and providesFormProvidercontext.FormField— wrapsreact-hook-form'sControllerand provides field context.FormItem— layout wrapper that generates IDs for accessibility linking.FormLabel,FormControl,FormDescription,FormMessage— slots.
Notes
- Pre-built field helpers (
FormInput,FormSelect,FormCheckbox,FormDatePicker,FormCascader, etc.) live inform-fields.tsx. - See the directory's
README.mdandQUICK_REFERENCE.mdfor the full pattern catalog.
Example
/**
* Runnable example for Form (react-hook-form + zod).
*/
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import {
Button,
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Input,
} from '@tetherto/mdk-react-devkit'
const schema = z.object({
name: z.string().min(2, 'At least 2 characters'),
email: z.string().email('Must be a valid email'),
})
type FormValues = z.infer<typeof schema>
export const FormExample = () => {
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { name: '', email: '' },
})
const onSubmit = (values: FormValues) => {
// eslint-disable-next-line no-console
console.log('submit', values)
}
return (
<Form form={form} onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Operator name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="ops@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" variant="primary">
Submit
</Button>
</Form>
)
}Related API
@tetherto/mdk-react-devkit
Pre-built input field
import { FormInput } from '@tetherto/mdk-react-devkit'Related API
@tetherto/mdk-react-devkit
Date range picker
import { DateRangePicker } from '@tetherto/mdk-react-devkit'Single-date and range-date pickers built on react-day-picker. The range
picker includes presets and a modal-style popover with Clear / Apply actions.
Data contracts
type DateRange = { from: Date | undefined; to?: Date | undefined };
type PresetItem = { label: string; value: DateRange };Example
/**
* Runnable example for DatePicker and DateRangePicker.
*/
import { useState } from 'react'
import type { DateRange } from '@tetherto/mdk-react-devkit'
import { DatePicker, DateRangePicker } from '@tetherto/mdk-react-devkit'
export const DatePickerExample = () => {
const [date, setDate] = useState<Date>()
const [range, setRange] = useState<DateRange>()
return (
<div className="mdk-example-row">
<DatePicker selected={date} onSelect={setDate} />
<DateRangePicker selected={range} onSelect={setRange} showPresets />
</div>
)
}@tetherto/mdk-react-devkit
Import the public APIs on this page from @tetherto/mdk-react-devkit.
Cascader
Two-panel hierarchical selector for picking a leaf value from a nested tree (categories → subcategories → leaf).
Features: - Two-column layout: categories on left, options on right - Single or multiple selection modes - Search/filter functionality via TagInput - Category-level selection (select/deselect all children) - Indeterminate state for partial selections - Tag display for multiple selections - Keyboard navigation support - Disabled state support
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
options | Required | CascaderOption[] | - | Hierarchical options to display in the cascader Parent options with children appear in the left panel Child options appear in the right panel when parent is selected |
className | Optional | string | - | Custom className for the root cascader element |
disabled | Optional | boolean | false | Disable the entire cascader (input and all options) |
dropdownClassName | Optional | string | - | Custom className for the dropdown panels container |
multiple | Optional | boolean | false | Enable multiple selection mode - true: Shows checkboxes, allows multiple selections, displays selected items as tags - false: Shows radio buttons, allows single selection |
onChange | Optional | ((value: CascaderValue | CascaderValue[] | null) => void) | - | Callback when selection changes - For single select: receives CascaderValue or null - For multiple select: receives CascaderValue[] or null |
placeholder | Optional | string | "Select..." | Placeholder text shown in the input when no selections are made |
value | Optional | CascaderValue | CascaderValue[] | - | Current selected value(s) - For single select: CascaderValue (e.g., ['category', 'option']) - For multiple select: CascaderValue[] (e.g., [['cat1', 'opt1'], ['cat2', 'opt2']]) |
Checkbox
Checkbox component with full customization
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
checked | Optional | CheckedState | - | Controlled checked state |
className | Optional | string | - | Custom className for the root element |
color | Optional | "success" | "warning" | "error" | "primary" | "default" | "primary" | Color variant when checked |
defaultChecked | Optional | CheckedState | - | Uncontrolled initial checked state |
disabled | Optional | boolean | false | Disable the input |
indicatorClassName | Optional | string | - | Custom className for the indicator element |
onCheckedChange | Optional | (((checked: CheckedState) => void) & ((checked: CheckedState) => void)) | - | Callback when the checked state changes |
radius | Optional | "small" | "none" | "medium" | "large" | "full" | "none" | Border radius variant |
size | Optional | "sm" | "md" | "lg" | "xs" | "md" | Size variant of the checkbox |
CurrencyToggler
CurrencyToggler component for switching between currencies
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
currencies | Required | (string | CurrencyItem)[] | - | List of currency options |
onChange | Required | (currency: string) => void | - | Fired with the selected currency value when a button is clicked |
value | Required | string | - | Currently selected currency value |
className | Optional | string | - | Additional class for the root element |
DatePicker
Single-date selection component built on react-day-picker with the MDK dark theme. Controlled via selected / onSelect
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
calendarClassName | Optional | string | - | Custom className for the calendar |
dateFormat | Optional | string | "MM/dd/yyyy" | Date format for display |
disabled | Optional | (boolean & (Matcher | Matcher[])) | false | Whether the picker is disabled |
onSelect | Optional | ((date: Date | undefined) => void) | - | Callback when date changes |
placeholder | Optional | string | "Pick a date" | Placeholder text when no date is selected |
selected | Optional | Date | - | Currently selected date |
triggerClassName | Optional | string | - | Custom className for the trigger button |
DateRangePicker
Date-range selection component with preset shortcuts (last 7/14/30/90 days) and a modal interface. Controlled via selected / onSelect
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
allowFutureDates | Optional | boolean | false | Whether to allow future dates |
calendarClassName | Optional | string | - | Custom className for the calendar |
dateFormat | Optional | string | "MM/dd/yyyy" | Date format for display |
disabled | Optional | (boolean & (Matcher | Matcher[])) | false | Whether the picker is disabled |
modalClassName | Optional | string | - | Custom className for the modal |
onSelect | Optional | ((range: DateRange | undefined) => void) | - | Callback when date range changes |
placeholder | Optional | string | "Pick a date range" | Placeholder text when no range is selected |
presets | Optional | PresetItem[] | - | Custom preset items |
selected | Optional | DateRange | - | Selected date range |
showPresets | Optional | boolean | true | Whether to show preset buttons |
triggerClassName | Optional | string | - | Custom className for the trigger button |
Form
React Hook Form provider wrapper. Pass the result of useForm() as form and render fields via FormField / FormItem / FormLabel / FormControl / FormMessage
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
children | Required | React.ReactNode | - | - |
form | Required | UseFormReturn<TFieldValues> | - | - |
FormCascader
Pre-built Cascader field component with integrated form state. Perfect for hierarchical selections like categories and subcategories
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
options | Required | CascaderOption[] | - | - |
cascaderProps | Optional | Omit<CascaderProps & React.RefAttributes<HTMLDivElement>, "onChange" | "value" | "options" | "placeholder"> | - | - |
description | Optional | string | - | - |
label | Optional | string | - | - |
multiple | Optional | boolean | - | - |
placeholder | Optional | string | - | - |
FormCheckbox
Pre-built Checkbox field component with integrated form state
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
checkboxProps | Optional | ({ checked?: CheckedState | undefined; defaultChecked?: CheckedState | undefined; disabled?: boolean | undefined; size?: CheckboxSize | undefined; color?: ComponentColor | undefined; radius?: BorderRadius | undefined; className?: string | u… /* see source */ | - | - |
description | Optional | string | - | - |
label | Optional | string | - | - |
layout | Optional | "row" | "column" | - | - |
placeholder | Optional | string | - | - |
FormControl
Slot-based wrapper that injects ARIA attributes onto its child input element without adding an extra DOM wrapper
agent-ready
FormDatePicker
Pre-built DatePicker field component with integrated form state
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
datePickerProps | Optional | object | - | - |
description | Optional | string | - | - |
label | Optional | string | - | - |
placeholder | Optional | string | - | - |
FormDescription
Optional helper text displayed below the input
agent-ready
FormField
Wraps react-hook-form's Controller and provides field context to descendants
agent-ready
FormInput
Pre-built Input field component with integrated form state. Reduces boilerplate by combining FormField, FormItem, FormLabel, FormControl, and FormMessage
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
description | Optional | string | - | - |
inputProps | Optional | Omit<Omit<InputProps, "ref"> & React.RefAttributes<HTMLInputElement>, "type" | "variant"> | - | - |
label | Optional | string | - | - |
placeholder | Optional | string | - | - |
type | Optional | React.HTMLInputTypeAttribute | - | - |
variant | Optional | "search" | "default" | - | - |
FormItem
Layout wrapper for a form field. Generates a unique ID for accessibility linking
agent-ready
FormLabel
Label that auto-links to the form field input via generated IDs. Applies error styling when the field has a validation error
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
htmlFor | Optional | string | - | Id of the input being labelled |
FormMessage
Displays the validation error message from react-hook-form field state. Falls back to children if no error is present. Always renders to prevent layout shift when errors appear
agent-ready
FormRadioGroup
Pre-built RadioGroup field component with integrated form state
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
options | Required | FormRadioOption[] | - | - |
description | Optional | string | - | - |
label | Optional | string | - | - |
orientation | Optional | "horizontal" | "vertical" | - | - |
placeholder | Optional | string | - | - |
radioGroupProps | Optional | object | - | - |
FormSelect
Pre-built Select field component with integrated form state
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
options | Required | FormSelectOption[] | - | - |
description | Optional | string | - | - |
label | Optional | string | - | - |
placeholder | Optional | string | - | - |
selectProps | Optional | object | - | - |
FormSwitch
Pre-built Switch field component with integrated form state
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
description | Optional | string | - | - |
label | Optional | string | - | - |
layout | Optional | "row" | "column" | - | - |
placeholder | Optional | string | - | - |
switchProps | Optional | object | - | - |
FormTagInput
Pre-built TagInput field component with integrated form state. Perfect for multi-select with search and tag display
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
allowCustomTags | Optional | boolean | - | - |
description | Optional | string | - | - |
label | Optional | string | - | - |
options | Optional | TagInputOption[] | - | - |
placeholder | Optional | string | - | - |
tagInputProps | Optional | object | - | - |
variant | Optional | "search" | "default" | - | - |
FormTextArea
Pre-built TextArea field component with integrated form state
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
description | Optional | string | - | - |
label | Optional | string | - | - |
placeholder | Optional | string | - | - |
textAreaProps | Optional | (Omit<TextAreaProps, "ref"> & React.RefAttributes<HTMLTextAreaElement>) | - | - |
Input
Text input with optional label, prefix/suffix slots, and a search variant. Forwards refs and all native <input> attributes
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
error | Optional | string | - | Validation error message. When provided, displays error styling (red border) and the message below the input |
id | Optional | string | auto-generated | HTML id for the input. Required when using label for accessibility |
label | Optional | string | - | Optional label displayed above the input |
prefix | Optional | React.ReactNode | - | Prefix element displayed before the input (left side) |
size | Optional | "default" | "medium" | "default" | Size of the input - default: padding 10px 12px, icon 16px - medium: padding 6px 12px, icon 12px |
suffix | Optional | React.ReactNode | - | Suffix element displayed after the input (right side) |
variant | Optional | "search" | "default" | "default" | Variant of the input - default: Standard text input - search: Input with magnifying glass icon on the right |
wrapperClassName | Optional | string | - | Custom className for the root wrapper |
Label
Accessible text label for form controls. Associates with an input via htmlFor and supports a required-mark indicator. Built on Radix Label
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
htmlFor | Optional | string | - | Id of the input being labelled |
MultiLevelSelect
Multi-level select component with collapsible sections
agent-ready
MultiSelect
Multi-select picker built on Radix Popover + Checkbox. Sibling to <Select> for cases where consumers need to pick more than one option (filter rows, multi-target actions, tag-style inputs). The popover stays open on toggle; Esc / outside-click closes it. Selected values render as removable chips in the trigger, with an optional "clear all" affordance and a +N more overflow chip via maxSelectedDisplay.
Controlled vs uncontrolled is decided by the presence of value (mirrors Radix Select's convention): pass value for controlled, defaultValue for uncontrolled
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
options | Required | MultiSelectOption[] | - | { value, label, disabled? } entries to render as option rows |
aria-label | Optional | string | - | Accessible label - applied to the trigger button |
className | Optional | string | - | Extra class on the trigger button |
contentClassName | Optional | string | - | Extra class on the popover content |
defaultValue | Optional | string[] | [] | Initial values for uncontrolled mode. Ignored when value is provided |
disabled | Optional | boolean | false | Disables the trigger (popover does not open) |
emptyMessage | Optional | React.ReactNode | "No options" | Rendered inside the popover when options is empty |
id | Optional | string | - | - |
maxSelectedDisplay | Optional | number | - | Max number of selected chips rendered in the trigger before collapsing the rest into a "+N more" badge. undefined (default) renders every chip |
name | Optional | string | - | - |
onValueChange | Optional | ((next: string[]) => void) | - | Fires with the next array on toggle / chip remove / clear-all |
placeholder | Optional | React.ReactNode | "Select..." | Rendered when nothing is selected |
size | Optional | "sm" | "md" | "lg" | "lg" | Trigger sizing tokens. Mirror the <Select> sizes |
value | Optional | string[] | - | Controlled selected values. Omit to use defaultValue for uncontrolled mode |
variant | Optional | "default" | "colored" | "default" | 'colored' paints the trigger in the primary tint (matches <Select>'s colored variant) |
Radio
Radio button component (use within RadioGroup)
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
value | Required | string | - | Value associated with this option |
children | Optional | React.ReactNode | - | Children content (takes precedence over label) |
className | Optional | string | - | Custom className for the root element |
color | Optional | "success" | "warning" | "error" | "primary" | "default" | "primary" | Color variant when checked |
indicatorClassName | Optional | string | - | Custom className for the indicator element |
label | Optional | string | - | Label text (or use children for custom content) |
radius | Optional | "small" | "none" | "medium" | "large" | "full" | "full" | Border radius variant (full makes it circular) |
size | Optional | "sm" | "md" | "lg" | "md" | Size variant of the radio |
RadioCard
RadioCard component - button-like radio option
Supports an onChange callback when selected
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
value | Required | string | - | Value associated with this option |
children | Optional | React.ReactNode | - | Children content (takes precedence over label) |
className | Optional | string | - | Custom className for the root element |
color | Optional | "success" | "warning" | "error" | "primary" | "default" | "primary" | Color variant when checked |
indicatorClassName | Optional | string | - | Custom className for the indicator element |
label | Optional | string | - | Label text (or use children for custom content) |
radius | Optional | "small" | "none" | "medium" | "large" | "full" | "full" | Border radius variant (full makes it circular) |
size | Optional | "sm" | "md" | "lg" | "md" | Size variant of the radio |
RadioGroup
RadioGroup component - container for Radio items
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
className | Optional | string | - | Custom className for the group |
noGap | Optional | boolean | false | Remove gap between radio items |
orientation | Optional | "horizontal" | "vertical" | "vertical" | Layout orientation |
Select
Dropdown select built on Radix UI. Supports default and colored variants, multiple sizes, and full keyboard navigation
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
allowClear | Optional | boolean | false | Show a clear button when a value is selected |
defaultValue | Optional | string | - | Uncontrolled initial value |
onValueChange | Optional | ((value: string) => void) | - | Setter for the value |
value | Optional | string | - | Controlled value |
Switch
Switch component for toggle controls with full customization
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
checked | Optional | boolean | - | Controlled checked state |
className | Optional | string | - | Custom className for the root element |
color | Optional | "success" | "warning" | "error" | "primary" | "default" | "default" | Color variant when checked |
defaultChecked | Optional | boolean | - | Uncontrolled initial checked state |
disabled | Optional | boolean | false | Disable the switch |
onCheckedChange | Optional | ((checked: boolean) => void) | - | Change handler |
radius | Optional | "small" | "none" | "medium" | "large" | "full" | "none" | Border radius variant |
size | Optional | "sm" | "md" | "lg" | "md" | Size variant of the switch |
thumbClassName | Optional | string | - | Custom className for the thumb element |
TagInput
Text input that converts comma- or Enter-separated entries into removable tag chips below the field
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
allowCustomTags | Optional | boolean | true | Whether to allow adding custom tags by typing and pressing Enter |
className | Optional | string | - | Additional class for the inner <input> |
disabled | Optional | boolean | false | Disabled state |
dropdownMaxHeight | Optional | string | "12rem" | Maximum height of the dropdown (CSS value, e.g. '300px', '20rem') |
dropdownMinHeight | Optional | string | - | Minimum height of the dropdown (CSS value, e.g. '100px', '6rem') |
filterOptions | Optional | ((options: TagInputOption[], query: string) => TagInputOption[]) | case-insensitive includes | Filter options by input value. Receives options and query, returns filtered options. When undefined, filters by case-insensitive includes |
id | Optional | string | auto-generated | HTML id for the input |
label | Optional | string | - | Label for the input |
onInputChange | Optional | ((value: string) => void) | - | Callback when input value changes (typing). Receives current input value. Useful for async option loading or custom filtering |
onSubmit | Optional | ((tags: string[]) => void) | - | Callback when user presses Enter (submit). Receives current tags. Called after adding a tag from selection or typed text, if applicable |
onTagsChange | Optional | ((tags: string[]) => void) | - | Callback when tags change (add/remove) |
options | Optional | TagInputOption[] | [] | Options to show in the dropdown when input is focused |
placeholder | Optional | string | "Search..." | Placeholder when input is empty |
renderDropdown | Optional | ((props: TagInputDropdownProps) => React.ReactNode) | - | Render custom dropdown content. When provided, replaces the default dropdown. Use this to apply your own styling or structure |
size | Optional | "sm" | "md" | "lg" | "lg" | Size of the tag input — matches Select sizes - sm: 24px height - md: 32px height - lg: 40px height |
value | Optional | string[] | [] | Controlled tags (array of tag values) |
variant | Optional | "search" | "default" | "search" | Input variant; 'search' shows a magnifying-glass icon that doubles as a clear-all button |
wrapperClassName | Optional | string | - | Custom className for the wrapper |
TextArea
TextArea component with label support and error handling
agent-ready
Props
| Prop | Status | Type / Options | Default | Description |
|---|---|---|---|---|
error | Optional | string | - | Validation error message. When provided, displays error styling (red border) and the message below the textarea |
id | Optional | string | auto-generated | HTML id for the textarea. Required when using label for accessibility |
label | Optional | string | - | Optional label displayed above the textarea |
wrapperClassName | Optional | string | - | Custom className for the root wrapper |