MDK Logo

Form Components

Form inputs, selects, checkboxes, and form validation components

Form components for building data entry interfaces.

Prerequisites

  • Complete the installation

  • 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 provides FormProvider context.
  • FormField — wraps react-hook-form's Controller and 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 in form-fields.tsx.
  • See the directory's README.md and QUICK_REFERENCE.md for 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>
  )
}
@tetherto/mdk-react-devkit

Pre-built input field

import { FormInput } from '@tetherto/mdk-react-devkit'

When to use FormInput

Use FormInput when a standard text-like field should bind directly to react-hook-form with the usual label, description, control and validation message. Use the lower-level FormField composition when the field needs custom layout or control behaviour.

FormInput example

<FormInput
  control={form.control}
  name="email"
  label="Email"
  type="email"
  placeholder="user@example.com"
  description="We'll never share your email"
/>
@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.

DatePicker props

PropTypeRequiredDefaultDescription
selectedDatenoSelected date.
onSelect(date?: Date) => voidnoSetter.
placeholderstringno"Pick a date"Trigger button placeholder.
dateFormatstringno"MM/dd/yyyy"date-fns format string.
disabledbooleannofalseDisable the trigger.
triggerClassNamestringnoClass names on the trigger button.
calendarClassNamestringnoClass names on the day-picker.

DateRangePicker props

Adds showPresets, presets: PresetItem[], allowFutureDates, and modalClassName. selected / onSelect use DateRange.

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

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

PropTypeRequiredDefaultDescription
optionsCascaderOption[]-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
valueCascaderValue | CascaderValue[] | undefined-Current selected value(s) - For single select: CascaderValue (e.g., ['category', 'option']) - For multiple select: CascaderValue[] (e.g., [['cat1', 'opt1'], ['cat2', 'opt2']])
onChange((value: CascaderValue | CascaderValue[] | null) => void) | undefined-Callback when selection changes - For single select: receives CascaderValue or null - For multiple select: receives CascaderValue[] or null
multipleboolean | undefinedfalseEnable multiple selection mode - true: Shows checkboxes, allows multiple selections, displays selected items as tags - false: Shows radio buttons, allows single selection
placeholderstring | undefined'Select...'Placeholder text shown in the input when no selections are made
disabledboolean | undefinedfalseDisable the entire cascader (input and all options)
classNamestring | undefined-Custom className for the root cascader element
dropdownClassNamestring | undefined-Custom className for the dropdown panels container

Checkbox

Checkbox component with full customization

agent-ready

Props

PropTypeRequiredDefaultDescription
sizeCheckboxSize | undefined'md'Size variant of the checkbox
color"success" | "warning" | "error" | "primary" | "default" | undefined'primary'Color variant when checked
radiusBorderRadius | undefined'none'Border radius variant
classNamestring | undefined-Custom className for the root element
indicatorClassNamestring | undefined-Custom className for the indicator element
onCheckedChange(((checked: CheckedState) => void) & ((checked: CheckedState) => void)) | undefined-Callback when the checked state changes

CurrencyToggler

CurrencyToggler component for switching between currencies

agent-ready

Props

PropTypeRequiredDefaultDescription
valuestring--
classNamestring | undefined--
currencies(string | CurrencyItem)[]--
onChange(currency: string) => void--

DatePicker

Single-date selection component built on react-day-picker with the MDK dark theme. Controlled via selected / onSelect.

agent-ready

Props

PropTypeRequiredDefaultDescription
selectedDate | undefined-Currently selected date
onSelect((date: Date | undefined) => void) | undefined-Callback when date changes
placeholderstring | undefined"Pick a date"Placeholder text when no date is selected
dateFormatstring | undefined"MM/dd/yyyy"Date format for display
disabled(boolean & (Matcher | Matcher[])) | undefinedfalseWhether the picker is disabled
triggerClassNamestring | undefined-Custom className for the trigger button
calendarClassNamestring | undefined-Custom className for the calendar

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

PropTypeRequiredDefaultDescription
selectedDateRange | undefined-Selected date range
onSelect((range: DateRange | undefined) => void) | undefined-Callback when date range changes
placeholderstring | undefined"Pick a date range"Placeholder text when no range is selected
dateFormatstring | undefined"MM/dd/yyyy"Date format for display
disabled(boolean & (Matcher | Matcher[])) | undefinedfalseWhether the picker is disabled
showPresetsboolean | undefinedtrueWhether to show preset buttons
presetsPresetItem[] | undefined-Custom preset items
allowFutureDatesboolean | undefinedfalseWhether to allow future dates
triggerClassNamestring | undefined-Custom className for the trigger button
calendarClassNamestring | undefined-Custom className for the calendar
modalClassNamestring | undefined-Custom className for the modal

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

PropTypeRequiredDefaultDescription
formUseFormReturn<TFieldValues>--
childrenReact.ReactNode--

FormCascader

Pre-built Cascader field component with integrated form state. Perfect for hierarchical selections like categories and subcategories.

agent-ready

Props

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
optionsCascaderOption[]--
multipleboolean | undefined--
cascaderPropsOmit<CascaderProps & React.RefAttributes<HTMLDivElement>, "onChange" | "value" | "options" | "placeholder"> | undefined--

FormCheckbox

Pre-built Checkbox field component with integrated form state.

agent-ready

Props

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
checkboxProps({ size?: CheckboxSize | undefined; color?: ComponentColor | undefined; radius?: BorderRadius | undefined; className?: string | undefined; indicatorClassName?: string | undefined; onCheckedChange?: ((checked: CheckedState) => void) | undefi… /* see source */--
layout"row" | "column" | undefined--

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

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
datePickerPropsOmit<{ selected?: Date | undefined; onSelect?: ((date: Date | undefined) => void) | undefined; placeholder?: string | undefined; dateFormat?: string | undefined; disabled?: boolean | undefined; triggerClassName?: string | undefined; calenda… /* see source */--

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

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
typeReact.HTMLInputTypeAttribute | undefined--
variant"search" | "default" | undefined--
inputPropsOmit<Omit<InputProps, "ref"> & React.RefAttributes<HTMLInputElement>, "type" | "variant"> | undefined--

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

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

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
optionsFormRadioOption[]--
orientation"horizontal" | "vertical" | undefined--
radioGroupPropsOmit<{ orientation?: "horizontal" | "vertical" | undefined; noGap?: boolean | undefined; className?: string | undefined; } & Omit<RadioGroupProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>, "defaultV… /* see source */--

FormSelect

Pre-built Select field component with integrated form state.

agent-ready

Props

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
optionsFormSelectOption[]--
selectPropsOmit<SelectSharedProps & { value?: string | undefined; defaultValue?: string | undefined; onValueChange?(value: string): void; } & { allowClear?: boolean | undefined; }, "defaultValue" | "onValueChange"> | undefined--

FormSwitch

Pre-built Switch field component with integrated form state.

agent-ready

Props

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
switchPropsOmit<{ size?: ComponentSize | undefined; color?: ComponentColor | undefined; radius?: BorderRadius | undefined; className?: string | undefined; thumbClassName?: string | undefined; } & Omit<SwitchProps & React.RefAttributes<HTMLButtonElemen… /* see source */--
layout"row" | "column" | undefined--

FormTagInput

Pre-built TagInput field component with integrated form state. Perfect for multi-select with search and tag display.

agent-ready

Props

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
optionsTagInputOption[] | undefined--
allowCustomTagsboolean | undefined--
variant"search" | "default" | undefined--
tagInputPropsOmit<TagInputProps & React.RefAttributes<HTMLInputElement | TagInputRef>, "label" | "value" | "placeholder" | "onTagsChange"> | undefined--

FormTextArea

Pre-built TextArea field component with integrated form state.

agent-ready

Props

PropTypeRequiredDefaultDescription
labelstring | undefined--
descriptionstring | undefined--
placeholderstring | undefined--
textAreaProps(Omit<TextAreaProps, "ref"> & React.RefAttributes<HTMLTextAreaElement>) | undefined--

Input

Text input with optional label, prefix/suffix slots, and a search variant. Forwards refs and all native <input> attributes.

agent-ready

Props

PropTypeRequiredDefaultDescription
errorstring | undefined-Validation error message. When provided, displays error styling (red border) and the message below the input.
labelstring | undefined-Optional label displayed above the input
prefixReact.ReactNode-Prefix element displayed before the input (left side)
variant"search" | "default" | undefined'default'Variant of the input - default: Standard text input - search: Input with magnifying glass icon on the right
sizeInputSize | undefined'default'Size of the input - default: padding 10px 12px, icon 16px - medium: padding 6px 12px, icon 12px
wrapperClassNamestring | undefined-Custom className for the root wrapper
suffixReact.ReactNode-Suffix element displayed after the input (right side)

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

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

PropTypeRequiredDefaultDescription
optionsMultiSelectOption[]--
valuestring[] | undefined-Controlled selected values. Omit to use defaultValue for uncontrolled mode.
defaultValuestring[] | undefined-Initial values for uncontrolled mode. Ignored when value is provided.
onValueChange((next: string[]) => void) | undefined--
placeholderReact.ReactNode--
disabledboolean | undefined--
sizeMultiSelectSize | undefined--
variantMultiSelectVariant | undefined--
emptyMessageReact.ReactNode-Rendered inside the popover when options is empty.
maxSelectedDisplaynumber | undefined-Max number of selected chips rendered in the trigger before collapsing the rest into a "+N more" badge. undefined (default) renders every chip.
classNamestring | undefined--
contentClassNamestring | undefined--
aria-labelstring | undefined-Accessible label - applied to the trigger button.
idstring | undefined--
namestring | undefined--

Radio

Radio button component (use within RadioGroup)

agent-ready

Props

PropTypeRequiredDefaultDescription
sizeComponentSize | undefined'md'Size variant of the radio
color"success" | "warning" | "error" | "primary" | "default" | undefined'default'Color variant when checked
radiusBorderRadius | undefined'full'Border radius variant (full makes it circular)
classNamestring | undefined-Custom className for the root element
labelstring | undefined-Label text (or use children for custom content)
childrenReact.ReactNode-Children content (takes precedence over label)
indicatorClassNamestring | undefined-Custom className for the indicator element

RadioCard

RadioCard component - button-like radio option

Supports an onChange callback when selected.

agent-ready

Props

PropTypeRequiredDefaultDescription
sizeComponentSize | undefined'md'Size variant of the radio
color"success" | "warning" | "error" | "primary" | "default" | undefined'default'Color variant when checked
radiusBorderRadius | undefined'full'Border radius variant (full makes it circular)
classNamestring | undefined-Custom className for the root element
labelstring | undefined-Label text (or use children for custom content)
childrenReact.ReactNode-Children content (takes precedence over label)
indicatorClassNamestring | undefined-Custom className for the indicator element

RadioGroup

RadioGroup component - container for Radio items

agent-ready

Props

PropTypeRequiredDefaultDescription
orientation"horizontal" | "vertical" | undefined'vertical'Layout orientation
noGapboolean | undefinedfalseRemove gap between radio items
classNamestring | undefined-Custom className for the group

Select

Dropdown select built on Radix UI. Supports default and colored variants, multiple sizes, and full keyboard navigation.

agent-ready

Props

PropTypeRequiredDefaultDescription
allowClearboolean | undefinedfalseShow a clear button when a value is selected

Switch

Switch component for toggle controls with full customization

agent-ready

Props

PropTypeRequiredDefaultDescription
sizeComponentSize | undefined'md'Size variant of the switch
color"success" | "warning" | "error" | "primary" | "default" | undefined'default'Color variant when checked
radiusBorderRadius | undefined'none'Border radius variant
classNamestring | undefined-Custom className for the root element
thumbClassNamestring | undefined-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

PropTypeRequiredDefaultDescription
valuestring[] | undefined-Controlled tags (array of tag values)
onTagsChange((tags: string[]) => void) | undefined-Callback when tags change (add/remove)
onInputChange((value: string) => void) | undefined-Callback when input value changes (typing). Receives current input value. Useful for async option loading or custom filtering.
onSubmit((tags: string[]) => void) | undefined-Callback when user presses Enter (submit). Receives current tags. Called after adding a tag from selection or typed text, if applicable.
optionsTagInputOption[] | undefined-Options to show in the dropdown when input is focused
placeholderstring | undefined-Placeholder when input is empty
disabledboolean | undefined-Disabled state
allowCustomTagsboolean | undefinedtrueWhether to allow adding custom tags by typing and pressing Enter
filterOptions((options: TagInputOption[], query: string) => TagInputOption[]) | undefined-Filter options by input value. Receives options and query, returns filtered options. When undefined, filters by case-insensitive includes.
variant"search" | "default" | undefined'search'Input variant - 'search' shows magnifying glass icon
labelstring | undefined-Label for the input
idstring | undefined-HTML id for the input
classNamestring | undefined-Custom className for the root
wrapperClassNamestring | undefined-Custom className for the wrapper
dropdownMinHeightstring | undefined-Minimum height of the dropdown (CSS value, e.g. '100px', '6rem')
dropdownMaxHeightstring | undefined'12rem'Maximum height of the dropdown (CSS value, e.g. '300px', '20rem')
sizeComponentSize | undefined'lg'Size of the tag input — matches Select sizes - sm: 24px height - md: 32px height - lg: 40px height
renderDropdown((props: TagInputDropdownProps) => React.ReactNode) | undefined-Render custom dropdown content. When provided, replaces the default dropdown. Use this to apply your own styling or structure.

TextArea

TextArea component with label support and error handling

agent-ready

Props

PropTypeRequiredDefaultDescription
errorstring | undefined-Validation error message. When provided, displays error styling (red border) and the message below the textarea.
labelstring | undefined-Optional label displayed above the textarea
wrapperClassNamestring | undefined-Custom className for the root wrapper

On this page