Form

A form component built on react-hook-form, with validation, error handling, and layout support.

Import

import { Form, FormTextInput, FormSelect } from '@flxui/uikit/business'

Basic Usage

import { Form, FormTextInput, FormSelect } from '@flxui/uikit/business'
 
function Demo() {
  const handleSubmit = (data) => {
    console.log(data)
  }
 
  return (
    <Form onSubmit={handleSubmit}>
      <FormTextInput name="username" label="Username" required />
      <FormTextInput name="email" label="Email" type="email" required />
      <FormSelect name="role" label="Role" data={['Admin', 'User', 'Guest']} />
    </Form>
  )
}

With Validation

Pass rules (a standard react-hook-form RegisterOptions object) to any Form* field:

<Form onSubmit={(data) => console.log(data)} defaultValues={{ email: '' }}>
  <FormTextInput
    name="email"
    label="Email"
    rules={{
      required: 'Email is required',
      pattern: {
        value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
        message: 'Invalid email address'
      }
    }}
  />
</Form>

With Zod Validation

Form doesn’t expose a resolver prop directly, so you can’t pass a Zod schema straight to <Form>. Instead, build your own useForm() with zodResolver and hand it to Form via the form prop — the same escape hatch documented under Form Props for bringing your own form instance:

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Form, FormTextInput, FormNumberInput } from '@flxui/uikit/business'
 
const schema = z.object({
  email: z.string().email('Enter a valid email address'),
  age: z.number().min(18, 'Must be at least 18')
})
 
type FormValues = z.infer<typeof schema>
 
function Demo() {
  const form = useForm<FormValues>({
    resolver: zodResolver(schema),
    defaultValues: { email: '', age: 18 }
  })
 
  return (
    <Form form={form} onSubmit={(data) => console.log(data)}>
      <FormTextInput name="email" label="Email" />
      <FormNumberInput name="age" label="Age" />
    </Form>
  )
}
⚠️

Once you pass your own form instance, Form’s own mode, reValidateMode, and defaultValues props become no-ops — they only configure the useForm() that Form creates internally when you don’t supply form. Set mode, reValidateMode, and defaultValues directly on your own useForm({ resolver, mode, defaultValues, ... }) call instead, as shown above.

Individual Form* fields’ own rules prop still works alongside a Zod resolver, but the two validate independently — react-hook-form runs both, so it’s easy to end up with duplicate or conflicting error messages for the same field if you validate it in both places. Pick one source of truth per field: either the Zod schema, or rules, not both.

Horizontal Layout

<Form layout="horizontal" onSubmit={(data) => console.log(data)}>
  <FormTextInput name="firstName" label="First Name" />
  <FormTextInput name="lastName" label="Last Name" />
</Form>

layout also accepts 'none', which skips the Flex wrapper entirely and renders children as-is — useful when you’re composing your own layout around the fields.

Without Actions

import { Form, FormTextInput } from '@flxui/uikit/business'
import { Button } from '@flxui/uikit'
 
function Demo() {
  return (
    <Form withActions={false} onSubmit={(data) => console.log(data)}>
      <FormTextInput name="search" label="Search" />
      <Button type="submit">Search</Button>
    </Form>
  )
}

Custom Actions

<Form
  onSubmit={(data) => console.log(data)}
  onCancel={() => console.log('cancelled')}
  actionsProps={{
    cancelText: 'Reset',
    confirmText: 'Save Changes'
  }}
>
  <FormTextInput name="name" label="Name" />
</Form>

The cancel button only renders when cancelText is truthy. Passing actionsProps={{ cancelText: null }} (or '') hides it, leaving just the confirm button.

With Error Message

errorMessage isn’t just a plain string prop for display — passing it is equivalent to what Form sets internally when your onSubmit handler throws. It’s rendered through an Alert and run through DOMPurify.sanitize, so basic HTML is allowed:

<Form errorMessage="Failed to submit form. Please try again." onSubmit={(data) => console.log(data)}>
  <FormTextInput name="name" label="Name" />
</Form>

If onSubmit throws (or rejects), Form catches it and displays the thrown error’s .message in the same error banner automatically — you don’t need to catch it yourself just to show a message. Provide onError to customize how the thrown value is turned into a display string.

Compound API

Form also exposes its building blocks as static properties, in case you want to assemble a custom layout instead of using Form’s built-in layout/withActions props:

<Form.Layout layout="horizontal">
  <FormTextInput name="firstName" label="First Name" />
</Form.Layout>
 
<Form.Actions cancelText="Reset" confirmText="Save" />
 
<Form.ErrorMessage message="Something went wrong" />
PropertyComponent
Form.ActionsFormActions
Form.ErrorMessageFormErrorMessage
Form.LayoutFormLayout

Form Props

PropTypeDefaultDescription
errorMessagestring-Error message to display (also set automatically if onSubmit throws)
formMode'onChange' | 'onBlur' | 'onSubmit'-react-hook-form validation mode, passed to the internal useForm
reValidateMode'onChange' | 'onBlur' | 'onSubmit''onChange'react-hook-form re-validation mode
defaultValuesDefaultValues<T>-Default form values
formUseFormReturn<T>-Bring your own useForm() instance instead of letting Form create one internally
withActionsbooleantrueShow the built-in FormActions (Cancel/Confirm buttons)
actionsPropsFormActionsProps-Props forwarded to the built-in FormActions
errorMessagePropsOmit<FormErrorMessageProps, 'message'>-Props forwarded to the built-in FormErrorMessage
layout'horizontal' | 'vertical' | 'none''vertical'Layout direction for the field wrapper
layoutPropsOmit<FormLayoutProps, 'layout'>-Props forwarded to the built-in FormLayout (it’s a Flex, so accepts FlexProps)
stopPropagationboolean-Calls e.stopPropagation() on submit
preventDefaultboolean-Calls e.preventDefault() on submit
onSubmitSubmitHandler<T>-Required. Submit handler; may be async
onError() => any-Turns a thrown/rejected error from onSubmit into the string shown in the error banner
onCancel() => void-Called when the built-in Cancel button is clicked
⚠️

Two props are declared on FormProps but aren’t actually wired up in the current implementation, so passing them has no effect: onRefresh and onUnmount (the real prop is named onFormUnMount, and it also isn’t read anywhere internally). Don’t rely on either for cleanup logic yet.

FormActions Props

PropTypeDefaultDescription
loadingboolean-Shows a loading state on the confirm button
disabledboolean-Disables the confirm button
onCancelMouseEventHandler-Cancel button click handler
onConfirmMouseEventHandler-Confirm button click handler (button is type="submit" regardless)
cancelTextReactNode'Cancel'Cancel button text — falsy hides the button entirely
confirmTextReactNode'Confirm'Confirm button text
cancelPropsButtonProps & ElementProps<'button'>-Extra props for the Cancel Button
confirmPropsButtonProps & ElementProps<'button'>-Extra props for the Confirm Button

Also accepts all FlexProps (the actions row is a Flex with justify="flex-end").

FormErrorMessage Props

PropTypeDefaultDescription
messagestring-Required. Rendered via dangerouslySetInnerHTML after DOMPurify.sanitize — basic HTML is allowed
onDismiss() => void-Shows a close button and is called when it’s clicked
autoScrollboolean-Scrolls the message into view whenever message changes
closablebooleanfalseShows a close button even without onDismiss

Also accepts all AlertProps except children.

Form Field Components

The fields below all follow the same shape: a name prop that maps to a react-hook-form field, an optional rules prop for validation, and — for most of them — an automatically-rendered error message wired up via getFieldState. They must be used inside a Form (or any FormProvider), since they call useFormContext() internally.

⚠️

A few exported names are easy to guess wrong: it’s FormTextareaInput (not FormTextArea), FormRatingInput (not FormRating), and FormSegmentedControl (not FormSegmentControl — note the “ed”). Import using the names below.

⚠️

Not every field renders errors automatically. FormTimeRangePicker does not call getFieldState/ErrorMessage at all, so validation errors on it won’t display unless you surface them yourself. FormDatePicker renders its error manually with inline styles rather than through a shared error component, so it may look slightly different from the rest.

FormCheckbox

import { FormCheckbox } from '@flxui/uikit/business'
 
;<FormCheckbox name="agree" label="I agree to the terms" />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules

Also accepts all CheckboxProps.

FormCheckboxGroup

Renders a set of checkboxes from a data array, with a single validation state for the group:

<FormCheckboxGroup
  name="fruits"
  label="Favorite fruits"
  data={[
    { value: 'apple', label: 'Apple' },
    { value: 'banana', label: 'Banana' }
  ]}
/>
PropTypeDefaultDescription
namestring-Field name
rulesRegisterOptions-Validation rules
dataCheckboxProps[]-Checkboxes to render
directionFlexProps['direction']-Layout direction of the checkboxes
gapFlexProps['gap']'sm'Gap between checkboxes

Also accepts all CheckboxGroupProps except children.

FormDatePicker

<FormDatePicker name="startDate" label="Start date" />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules
placeholderstringInput placeholder
clearablebooleanAllow clearing the value
labelReactNodeField label

Also accepts all DatePickerProps.

FormMultiSelect

<FormMultiSelect name="tags" label="Tags" data={['React', 'Vue', 'Svelte']} />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules

Also accepts all MultiSelectProps (including data).

FormNumberInput

<FormNumberInput name="quantity" label="Quantity" min={0} />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules

Also accepts all NumberInputProps.

FormTimeRangePicker

<FormTimeRangePicker name="range" label="Time range" clearable />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules
valueTimeRangeControlled value (rarely needed inside a Form)
onChange(value?: TimeRange) => voidChange callback
clearablebooleanAllow clearing the value
labelReactNodeField label

Also accepts all TimeRangePickerBaseProps. No built-in error rendering — see the callout above.

FormPhoneInput

A single phone number input, formatted using the package’s built-in country dial-code masks:

<FormPhoneInput name="phone" label="Phone number" />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules
defaultCountrystringDefault country dial code
onSelect(value: string, country: CountryData | {}) => voidCalled (alongside onChange) when the formatted value changes

Also accepts all PhoneInputProps except onSelect.

FormPhoneInputV2

Pairs a country FormSelect with a FormPhoneInput, side by side, with a single consolidated error message for both fields:

<FormPhoneInputV2 countryKey="country" phoneKey="phone" selectProps={{ placeholder: 'Country' }} />
PropTypeDescription
countryKeystringField name for the country select
phoneKeystringField name for the phone input
defaultCountrystringDefault country dial code
rulesRegisterOptionsValidation rules for the phone field
countryRulesRegisterOptionsValidation rules for the country field
onSelect(value: string, country: CountryData | {}) => voidCalled (alongside onChange) when the formatted value changes
selectPropsOmit<SelectProps, 'data'> & { filterData?: (item, index, array) => boolean }Props for the country select; filterData filters the built-in country list
rootPropsBoxPropsProps for the outer wrapper

The country list is fixed — it’s built from the package’s bundled country data, not something you pass in. Use selectProps.filterData to narrow it down (e.g. to a specific set of supported countries) rather than trying to supply your own list.

Also accepts all PhoneInputProps except onSelect.

FormCopyText

⚠️

Despite the Form* naming convention, FormCopyText is not a react-hook-form field — it has no name/rules props and doesn’t call useFormContext. It’s a standalone read-only value display with a copy-to-clipboard button, usable anywhere (inside or outside a Form).

<FormCopyText value="sk-live-abc123..." />
PropTypeDefaultDescription
valuestring-Required. The text to display and copy
timeoutnumber3000How long the “Copied” state shows, in ms
onClick() => void-Called when the copy button is clicked, alongside copying
valuePropsTypographyProps-Props for the displayed value’s Typography
tooltipPropsTooltipProps-Props for the copy button’s Tooltip
sizenumber16Icon size

Also accepts all BoxProps.

FormTextInput

<FormTextInput name="username" label="Username" />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules

Also accepts all TextInputProps. The field’s value defaults to '' when the underlying react-hook-form value is undefined, so TextInput stays controlled from the first render even before the field has been touched.

FormPasswordInput

<FormPasswordInput name="password" label="Password" />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules

Also accepts all PasswordInputProps. It ships its own default classNames (wrapper, input, visibility toggle, etc.); any classNames you pass are merged in per-key, so overriding e.g. classNames.input won’t affect the others.

FormTextareaInput

⚠️
The exported name is FormTextareaInput, not FormTextArea.
<FormTextareaInput name="bio" label="Bio" />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules

Also accepts all TextareaProps.

FormSelect

<FormSelect name="role" label="Role" data={['Admin', 'User', 'Guest']} />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules

onChange receives (value, option) — the selected option object, not just the raw value — if you pass your own handler alongside the field binding. Also accepts all SelectProps (including data).

FormSwitch

<FormSwitch name="notifications" label="Enable notifications" />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules
labelReactNodeField label
onChange(checked: boolean) => voidCalled (alongside the field’s own update) with the new checked state

Also accepts all SwitchProps except onChange and label (both are redeclared above with narrower types).

FormRatingInput

⚠️
The exported name is FormRatingInput, not FormRating.
<FormRatingInput name="satisfaction" label="How satisfied were you?" />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules
labelInputWrapperProps['label']Field label
withAsteriskInputWrapperProps['withAsterisk']Show a required asterisk next to the label
wrapperPropsOmit<InputWrapperProps, 'children'>Props for the surrounding Input.Wrapper

Also accepts all RatingProps. Unlike the other fields, it’s wrapped in Input.Wrapper directly rather than relying on the underlying primitive’s own label/error rendering, since Rating doesn’t support those itself.

FormSegmentedControl

⚠️

The exported name is FormSegmentedControl, not FormSegmentControl (note the “ed”). It also renders its error differently from every other field here — as plain error.message text rather than through the shared <ErrorMessage /> helper — so it won’t correctly display errors that come from nested or array-type validation paths the way the others do.

<FormSegmentedControl name="plan" label="Plan" data={['Monthly', 'Yearly']} />
PropTypeDescription
namestringField name
rulesRegisterOptionsValidation rules
labelReactNodeField label

Also accepts all SegmentedControlProps.

FormRadioGroup

<FormRadioGroup
  name="plan"
  label="Plan"
  data={[
    { value: 'free', label: 'Free' },
    { value: 'pro', label: 'Pro', tooltip: 'Includes priority support' }
  ]}
/>
PropTypeDefaultDescription
namestring-Field name
rulesRegisterOptions-Validation rules
dataRadioGroupItemData[]-Radios to render — each is a RadioProps plus optional tooltip/tooltipProps
directionFlexProps['direction']-Layout direction of the radios
gapFlexProps['gap']'md'Gap between radios

Also accepts all RadioGroupProps except children.

Each item in data can optionally carry a tooltip. By default the tooltip renders as a HoverCard (so it can hold richer content); set tooltipProps={{ useTooltip: true }} on that item to render it as a simple Tooltip instead:

{
  value: 'enterprise',
  label: 'Enterprise',
  tooltip: 'Contact sales for pricing',
  tooltipProps: { useTooltip: true }
}

tooltipProps doubles as both the useTooltip switch and, when using the default HoverCard mode, the props forwarded to that HoverCard (it extends HoverCardProps). It has no effect when useTooltip: true is set, since Tooltip doesn’t receive it.

Not yet documented

FormProMultiSelect and FormDateTimePicker follow the same name/rules pattern shown throughout this page. Their source wasn’t available to verify prop-for-prop, so they’re left out for now — send them over whenever you’d like these filled in.