Date Picker Field component
FormFieldSet is one component that dispatches to 12 different field implementations based on its type prop — text, number, email, password, select, radio, checkbox, switch, date picker, phone, file, and more — sharing one validation system and one Form context, so every field type is styled, labeled, and validated consistently without hand-building 12 separate wrappers.
Playground
Change a prop on the right and the preview updates. Switch to Code to copy what you see.
Recipes
Copy-paste patterns for the ways Date Picker Field actually gets used — each one a real, self-contained example.
Single date
A calendar popover on a trigger that shows the chosen date. Values are ISO strings (YYYY-MM-DD).
<Form onSubmit={(data) => console.log(data)}>
<FormFieldSet
type="datepicker"
name="dob"
label="Date of birth"
/>
</Form>Date range
range mode gives a { start, end } value and a two-month calendar — for a booking or reporting window.
<FormFieldSet
type="datepicker"
name="stay"
label="Trip dates"
range
/>Date and time
withTime adds a time picker alongside the calendar; values carry the time (YYYY-MM-DDTHH:mm).
<FormFieldSet
type="datepicker"
name="startsAt"
label="Starts at"
withTime
/>Range with quick presets
On a range picker, showPresets adds a shortcut panel (Today, Last 7 days, This month, …) so the common windows are one click away.
<FormFieldSet
type="datepicker"
name="period"
label="Reporting period"
range
showPresets
/>Bounded dates
minDate and maxDate constrain the selectable range — here, no dates in the past.
<FormFieldSet
type="datepicker"
name="appointment"
label="Appointment"
minDate={new Date()}
/>One field, twelve types
type picks the field implementation; every other prop after that is shared (label, description, size, variant, color, labelPlacement) plus whichever props are specific to that type (options for select/radio/checkbox, length for otp, accept for file). FormFieldSet must live inside a Form for validation/submission to work — it registers itself with the nearest Form context on mount.
<Form onSubmit={(data) => console.log(data)}>
<FormFieldSet type="text" name="name" label="Name" />
<FormFieldSet type="email" name="email" label="Email" validation={{ rules: { required: true, email: true } }} />
<FormFieldSet
type="select"
name="role"
label="Role"
options={[
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
{ label: "Viewer", value: "viewer" },
]}
/>
<FormFieldSet type="switch" name="notifications" label="Email notifications" />
<Button type="submit">Save</Button>
</Form>Validation
validation.rules covers the common cases declaratively — required, email, minLength/maxLength, min/max, pattern, and a custom function rule for anything bespoke. Errors are computed by Form and shown per-field automatically once a field is touched (or immediately, depending on Form's showErrorsOn setting).
<FormFieldSet
type="password"
name="password"
label="Password"
validation={{
rules: {
required: true,
minLength: 8,
custom: (value) => /[A-Z]/.test(String(value)) || "Needs at least one uppercase letter",
},
}}
/>Controlled and uncontrolled
Every field type works both ways: pass value + onChange for a fully controlled field, or defaultValue and let Form track the value internally through its own formData state — the same convention across all 12 types, so switching a field between controlled and uncontrolled doesn't change how you read its value elsewhere (Form's formData always reflects the current value either way).
Label placement
labelPlacement controls where the label sits relative to the control — top (the default), left, right, or floating (the label starts inside the control and animates up once it has a value or focus). All 12 field types support the full set.
<FormFieldSet type="text" name="company" label="Company" labelPlacement="floating" />
<FormFieldSet type="text" name="role" label="Role" labelPlacement="left" />Repeating fields with LoopFields
For a variable-length group of fields (a list of email addresses, multiple phone numbers), LoopFields handles the add/remove/reindex bookkeeping — each item gets a stable identity across removals so validation state and values stay attached to the right item, not just the right position.
<LoopFields name="emails" minItems={1}>
{({ index, name }) => (
<FormFieldSet type="email" name={`${name}`} label={`Email ${index + 1}`} />
)}
</LoopFields>Props
The full public API for Date Picker Field. Every prop is stable and intentionally typed.
DatePickerField — Content
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue | string | { start: string; end: string } | — | Controlled / uncontrolled value. YYYY-MM-DD, or YYYY-MM-DDTHH:mm when withTime. |
| startIcon / endIcon / prefix / suffix | ReactNode | — | Adornments on the trigger. |
| label | ReactNode | — | Field label. |
| description | ReactNode | — | Helper text below the control. |
| error | ReactNode | boolean | — | Error message, or true for an invalid state with no message. |
value / defaultValue
string | { start: string; end: string }
default: —
Controlled / uncontrolled value. YYYY-MM-DD, or YYYY-MM-DDTHH:mm when withTime.
startIcon / endIcon / prefix / suffix
ReactNode
default: —
Adornments on the trigger.
label
ReactNode
default: —
Field label.
description
ReactNode
default: —
Helper text below the control.
error
ReactNode | boolean
default: —
Error message, or true for an invalid state with no message.
DatePickerField — Events
| Prop | Type | Default | Description |
|---|---|---|---|
| onChange | (next) => void | — | Called when the selection changes. |
| onClear | () => void | — | Called in addition to onChange when the clear action is used. |
onChange
(next) => void
default: —
Called when the selection changes.
onClear
() => void
default: —
Called in addition to onChange when the clear action is used.
DatePickerField — Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| range | boolean | false | Enables range mode (value becomes { start, end }). |
| withTime | boolean | false | Enables time selection alongside the date. |
| showPresets / presets | boolean / Array<preset name> | false / built-in set | Preset shortcut buttons (Today, This week, …). |
| clearable | boolean | true | Shows a clear action (hidden when required). |
| minDate / maxDate | Date | string | — | Earliest/latest selectable date. |
| blockedDates | Array<Date | string> | [] | Disabled dates list. |
| disabled | boolean | false | Disables the control. |
| required | boolean | false | Marks the field required. |
range
boolean
default: false
Enables range mode (value becomes { start, end }).
withTime
boolean
default: false
Enables time selection alongside the date.
showPresets / presets
boolean / Array<preset name>
default: false / built-in set
Preset shortcut buttons (Today, This week, …).
clearable
boolean
default: true
Shows a clear action (hidden when required).
minDate / maxDate
Date | string
default: —
Earliest/latest selectable date.
blockedDates
Array<Date | string>
default: []
Disabled dates list.
disabled
boolean
default: false
Disables the control.
required
boolean
default: false
Marks the field required.
DatePickerField — Layout & positioning
| Prop | Type | Default | Description |
|---|---|---|---|
| rangeTrigger | "single" | "dual" | "single" | Range only: one trigger vs. separate start/end inputs. |
| monthsToShow | 1 | 2 | 1 (2 for range) | Calendar months visible at once. |
| labelPlacement | "top" | "left" | "right" | "floating" | "top" | Label position relative to the control. |
| colSpan | number | — | Sets `grid-column: span N` on the field wrapper — a real home for a layout hint in a spread config object (instead of leaking to the <input>). |
rangeTrigger
"single" | "dual"
default: "single"
Range only: one trigger vs. separate start/end inputs.
monthsToShow
1 | 2
default: 1 (2 for range)
Calendar months visible at once.
labelPlacement
"top" | "left" | "right" | "floating"
default: "top"
Label position relative to the control.
colSpan
number
default: —
Sets `grid-column: span N` on the field wrapper — a real home for a layout hint in a spread config object (instead of leaking to the <input>).
DatePickerField — Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| displayFormat | "pretty" | "iso" | "pretty" | Trigger display format. |
| size | "xs" | "xs-sm" | "sm" | "md" | "lg" | "xl" | "md" | Control size. |
| variant | "bordered" | "soft" | "filled" | "underlined" | "bordered" | Visual variant. "filled" (text types) is borderless with a neutral fill at rest, border + ring on focus; other field types fall back to "bordered". "default"/"underline" are legacy aliases. |
| color | "default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger" | string | "default" | Semantic tone. |
| colorDepth | number | 500 | Shade depth for color. |
| radius | "none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full" | "md" | Corner radius. `rounded` is a legacy alias. |
displayFormat
"pretty" | "iso"
default: "pretty"
Trigger display format.
size
"xs" | "xs-sm" | "sm" | "md" | "lg" | "xl"
default: "md"
Control size.
variant
"bordered" | "soft" | "filled" | "underlined"
default: "bordered"
Visual variant. "filled" (text types) is borderless with a neutral fill at rest, border + ring on focus; other field types fall back to "bordered". "default"/"underline" are legacy aliases.
color
"default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger" | string
default: "default"
Semantic tone.
colorDepth
number
default: 500
Shade depth for color.
radius
"none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full"
default: "md"
Corner radius. `rounded` is a legacy alias.
DatePickerField — Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| wrapperClassName | string | — | Class on the field wrapper (the element that also carries colSpan), separate from `className` on the control. |
| className | string | — | Class applied to the root element. |
| style | CSSProperties | — | Inline styles for the root element. |
wrapperClassName
string
default: —
Class on the field wrapper (the element that also carries colSpan), separate from `className` on the control.
className
string
default: —
Class applied to the root element.
style
CSSProperties
default: —
Inline styles for the root element.
Form — Content
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | FormFieldSet fields and other form content. |
children
ReactNode
default: —
FormFieldSet fields and other form content.
Form — Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| validationMode | "blur" | "change" | "blur" | When fields are (re-)validated. |
| showErrorsOn | "blur" | "change" | "blur" | When validation errors start showing. |
| validateOnMount | boolean | false | Runs validation immediately on mount. |
| initialValues | FormData | {} | Initial values for uncontrolled fields. |
| disableAutofill | boolean | true | Adds autocomplete/autofill-defeating attributes to the form. |
validationMode
"blur" | "change"
default: "blur"
When fields are (re-)validated.
showErrorsOn
"blur" | "change"
default: "blur"
When validation errors start showing.
validateOnMount
boolean
default: false
Runs validation immediately on mount.
initialValues
FormData
default: {}
Initial values for uncontrolled fields.
disableAutofill
boolean
default: true
Adds autocomplete/autofill-defeating attributes to the form.
Form — Events
| Prop | Type | Default | Description |
|---|---|---|---|
| onSubmit | (formData: FormData) => void | Promise<void> | — | Called with collected field values on submit (required). |
| onError | (errors: FormErrors) => void | — | Called when submit is blocked by validation errors. |
onSubmit
(formData: FormData) => void | Promise<void>
default: —
Called with collected field values on submit (required).
onError
(errors: FormErrors) => void
default: —
Called when submit is blocked by validation errors.
Form — Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | "" | Class applied to the form element. |
className
string
default: ""
Class applied to the form element.
Accessibility
- Every field type renders a real, correctly-associated <label> (or aria-label for labelPlacement="floating" edge cases) tied to its control via htmlFor/id, generated automatically when you don't pass an explicit id.
- Validation errors render with role="alert" and are linked to their field via aria-describedby, so a screen reader announces the error in context with the field it belongs to.
- labelPlacement="floating" keeps a real, associated label at all times — it's a visual animation, not a placeholder-only pattern that would leave the field unlabeled once the placeholder text disappears.
Best practices
- Group related fields under one Form even when you only care about validating a subset at a time (as SteppedForm does internally) — Form's full-schema validation on submit is what catches anything missed by earlier per-step or per-field checks.
- Prefer declarative validation.rules over a custom function whenever the built-in rules cover the case — they're what Form's error-timing logic (showErrorsOn, validateOnMount) is built around, and stay consistent across every field type.
- Use LoopFields instead of manually managing an array of FormFieldSets in your own state — the reindexing-on-removal behavior is easy to get subtly wrong by hand (stale keys, misattributed errors after a middle item is removed).
- Match labelPlacement to information density: "top" for typical forms, "left"/"right" for compact settings-style rows, "floating" for a tighter visual footprint where a persistent top label would waste vertical space.