DateRangeField
Overview
A segmented date range input that renders two groups of keyboard-editable date segments — one for the start date and one for the end date — while submitting native form values for both. Use it whenever a user must enter a bounded interval fast, such as booking stay dates, reporting windows, or filter ranges where typing beats calendar clicking. For a range chosen from a calendar popup, prefer SDateRangePicker; for a single date, use SDateField.
Usage
Features
- 🧩 Dual segmented groups — independent start and end segment groups (day/month/year, optional time), each part an editable
role="spinbutton"segment - ⌨️ Cross-group focus movement — arrow keys navigate within a group, and at the group edges
ArrowLeft/ArrowRightmove focus into the other group, with directions reversed in RTL - 🎚 Controlled / uncontrolled —
v-model/defaultValueaccept aDateRange{ start, end }, backed byuseControllableState - ⛓ Range validation —
minValue/maxValue/isDateUnavailableper date, and a start later than end automatically marks the rootdata-invalid - 📝 Dual native form values — two visually hidden inputs (
startName/endName, falling back tonamefor the start) submit the ISO values - 🕐 Granularity & time —
granularity(day/hour/minute/second), 12/24-hourhourCycle, anddayPeriodsegments apply to both groups - 🧩 Customizable separation — a
separatorprop (default–) plusleading/trailingslots, or aseparatorslot for fully custom content - 🚫 Disabled / readonly — both states render proper
data-*/aria-*attributes and block editing across both groups
Component family
SDateRangeField— the styled wrapper that forwards props to the Aria compact and injectsdateRangeFieldVariants(extendingdateFieldVariants) classesDateRangeFieldCompact(Aria) — data-driven composition ofDateRangeFieldRoot+ per-segmentDateRangeFieldInputfor both groups, plusleading/trailing/separatorslots; import from@vean/aria/date-range-fieldfor unstyled usageDateRangeFieldRoot/DateRangeFieldInput(Aria) — the state owner (dual segment values, validation, hidden inputs, cross-group focus) and a single editable segment bound to the shareduseDateFieldcomposable
Demos
Basic
Selected: Sun Apr 19 2026 00:00:00 GMT+0000 (Coordinated Universal Time) to Sun Apr 26 2026 00:00:00 GMT+0000 (Coordinated Universal Time)
Custom Styling
API
Aria Layer
4DateRangeField
Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | ClassValue | — | Additional class names applied to the root element. |
size | ThemeSize | — | Visual size of the component. |
ui | Partial<DateRangeFieldUi> | — | Per-slot class overrides for the component. |
inputProps | DateRangeFieldInputProps | — | Properties forwarded to the input element. |
separator | string | — | Separator. |
defaultValue | DateRange | — | Default value. |
modelValue | DateRange | — | Current model value. |
startName | string | — | Start name. |
endName | string | — | End name. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
dir | Direction | — | Reading direction of the component. |
id | string | — | Id. |
placeholder | DateValue | — | Placeholder. |
name | string | — | The name of the field. Submitted with its owning form as part of a name/value pair. |
required | boolean | — | When `true`, indicates that the user must set the value before the owning form can be submitted. |
readonly | boolean | — | Whether the component is readonly. |
step | DateStep | — | Step. |
locale | string | — | Locale. |
defaultPlaceholder | DateValue | — | Default placeholder. |
maxValue | DateValue | — | Max value. |
minValue | DateValue | — | Min value. |
isDateUnavailable | DateMatcher | — | Whether the date is unavailable. |
granularity | Granularity | — | Granularity. |
hourCycle | HourCycle | — | Hour cycle. |
hideTimeZone | boolean | — | Whether hide time zone. |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:modelValue | DateRange | Emitted when the model value changes. |
update:placeholder | DateValue | Emitted when the placeholder value changes. |
update:startValue | DateValue | null | Emitted when the start value changes. |
update:endValue | DateValue | null | Emitted when the end value changes. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
leading | — | Custom content for the leading slot. |
trailing | — | Custom content for the trailing slot. |
separator | — | Custom content for the separator slot. |
DateRangeFieldCompact
Props
| Prop | Type | Default | Description |
|---|---|---|---|
inputProps | DateRangeFieldInputProps | — | Properties forwarded to the input element. |
separator | string | — | Separator. |
defaultValue | DateRange | — | Default value. |
modelValue | DateRange | — | Current model value. |
startName | string | — | Start name. |
endName | string | — | End name. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
dir | Direction | — | Reading direction of the component. |
id | string | — | Id. |
placeholder | DateValue | — | Placeholder. |
name | string | — | The name of the field. Submitted with its owning form as part of a name/value pair. |
required | boolean | — | When `true`, indicates that the user must set the value before the owning form can be submitted. |
readonly | boolean | — | Whether the component is readonly. |
step | DateStep | — | Step. |
locale | string | — | Locale. |
defaultPlaceholder | DateValue | — | Default placeholder. |
maxValue | DateValue | — | Max value. |
minValue | DateValue | — | Min value. |
isDateUnavailable | DateMatcher | — | Whether the date is unavailable. |
granularity | Granularity | — | Granularity. |
hourCycle | HourCycle | — | Hour cycle. |
hideTimeZone | boolean | — | Whether hide time zone. |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:modelValue | DateRange | Emitted when the model value changes. |
update:placeholder | DateValue | Emitted when the placeholder value changes. |
update:startValue | DateValue | null | Emitted when the start value changes. |
update:endValue | DateValue | null | Emitted when the end value changes. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
leading | — | Custom content for the leading slot. |
trailing | — | Custom content for the trailing slot. |
separator | — | Custom content for the separator slot. |
DateRangeFieldInput
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | DateRangeType | — | Type. |
part | SegmentPart | — | Part. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
DateRangeFieldRoot
Props
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | DateRange | — | Default value. |
modelValue | DateRange | — | Current model value. |
startName | string | — | Start name. |
endName | string | — | End name. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
dir | Direction | — | Reading direction of the component. |
id | string | — | Id. |
placeholder | DateValue | — | Placeholder. |
name | string | — | The name of the field. Submitted with its owning form as part of a name/value pair. |
required | boolean | — | When `true`, indicates that the user must set the value before the owning form can be submitted. |
readonly | boolean | — | Whether the component is readonly. |
step | DateStep | — | Step. |
locale | string | — | Locale. |
defaultPlaceholder | DateValue | — | Default placeholder. |
maxValue | DateValue | — | Max value. |
minValue | DateValue | — | Min value. |
isDateUnavailable | DateMatcher | — | Whether the date is unavailable. |
granularity | Granularity | — | Granularity. |
hourCycle | HourCycle | — | Hour cycle. |
hideTimeZone | boolean | — | Whether hide time zone. |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:modelValue | DateRange | Emitted when the model value changes. |
update:placeholder | DateValue | Emitted when the placeholder value changes. |
update:startValue | DateValue | null | Emitted when the start value changes. |
update:endValue | DateValue | null | Emitted when the end value changes. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
default | DateRangeFieldSlotProps | Custom content for the default slot. |
Notes
Architecture and benchmark differences
DateRangeFieldRoot owns the { start, end } value via useControllableState, keeps separate startSegmentValues/endSegmentValues shallowRefs for the two groups, and runs validation through isInvalid (including a start-later-than-end check). Each DateRangeFieldInput binds the same useDateField composable used by the time-field family for per-part keydown logic. Cross-group movement is handled by the root’s moveFocus: at the physical end of the start group the next arrow moves into the end group, and at the beginning of the end group the previous arrow returns, with the physical key mapped from the dir so RTL swaps ArrowLeft/ArrowRight. Most benchmark libraries implement a range as two separate text inputs or one text input with a separator; the dual segmented-field pattern with cross-group keyboard focus comes from the reka-ui (Radix) date-field lineage.
| Capability | VeanUI | Ant Design | Element Plus | Mantine | Naive UI | shadcn |
|---|---|---|---|---|---|---|
| Aria/styled split | ✅ | — | — | — | — | — |
| Dual segmented editable groups | ✅ | — | — | — | — | — |
| Cross-group focus movement | ✅ | — | — | — | — | — |
| RTL direction reversal | ✅ | — | — | — | — | — |
| Controlled / uncontrolled | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| Keyboard increment/typing | ✅ | — | — | — | — | — |
| Range validation (start ≤ end) | ✅ | ✅ | ✅ | ✅ | ✅ | — |
isDateUnavailable | ✅ | — | — | — | — | — |
| Granularity (minute/second) | ✅ | — | — | — | — | — |
| Dual native form values | ✅ | ✅ | ✅ | ✅ | — | — |
| Disabled / readonly | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| Separator prop / slot | ✅ | — | — | — | — | — |
Cautions
- The value is a
DateRange— an object{ start, end }ofDateValues, not astring. Use it with the other date-family components. defaultValueis only read on mount — usev-modelfor external control.- A start later than end is marked invalid but the values are never swapped automatically — clear or correct them in the parent.
- Form submission uses two hidden inputs: the start one takes
startName(or the sharedname), the end one takesendName. Set both to submit distinct fields;required/min/maxare reflected but do not run custom validation. - Deleting the last digit of one group emits
update:modelValuewith that sideundefined; segments fall back to placeholder styling. - Each group renders
role="group"with per-segmentrole="spinbutton"; add anaria-labelto the whole control so screen readers announce both groups.
FAQ
How do I move focus between the two groups with the keyboard?
Use ArrowLeft/ArrowRight — at the first segment of the end group ArrowLeft jumps to the last start segment, and at the last segment of the start group ArrowRight jumps to the first end segment. In RTL the physical keys are reversed.
How do I submit two form values?
Pass name (fallback for the start) plus startName and endName for distinct field names — each group renders a visually hidden native input carrying its ISO value.
How do I restrict the selectable range?
Pass minValue/maxValue — dates outside are marked data-invalid and rejected. isDateUnavailable accepts a predicate for arbitrary exclusions, and a start later than end is invalid on its own.
How is SDateRangeField different from SDateRangePicker?
SDateRangeField is a pure dual segmented input with no popup — fastest for typing and form submission. SDateRangePicker adds a calendar popover on top of the same field for visual selection.
How do I customize the separator?
Pass separator (e.g. "→") to change the text, or use the separator slot to render anything (an icon, a styled label). leading/trailing slots place content around the two groups.