Date Range Picker
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import {
DateRangePickerArrow,
DateRangePickerCalendar,
DateRangePickerCell,
DateRangePickerCellTrigger,
DateRangePickerContent,
DateRangePickerField,
DateRangePickerGrid,
DateRangePickerGridBody,
DateRangePickerGridHead,
DateRangePickerGridRow,
DateRangePickerHeadCell,
DateRangePickerHeader,
DateRangePickerHeading,
DateRangePickerInput,
DateRangePickerNext,
DateRangePickerPrev,
DateRangePickerRoot,
DateRangePickerTrigger,
Label,
} from 'radix-vue'
</script>
<template>
<div class="flex flex-col gap-2">
<Label class="text-sm text-gray9" for="date-field">Birthday</Label>
<DateRangePickerRoot
id="date-field"
:is-date-unavailable="date => date.day === 19"
>
<DateRangePickerField
v-slot="{ segments }"
class="flex select-none bg-white items-center rounded-lg text-center text-green10 border border-transparent p-1 data-[invalid]:border-red-500"
>
<template v-for="item in segments.start" :key="item.part">
<DateRangePickerInput
v-if="item.part === 'literal'"
:part="item.part"
type="start"
>
{{ item.value }}
</DateRangePickerInput>
<DateRangePickerInput
v-else
:part="item.part"
class="rounded-md p-0.5 focus:outline-none focus:shadow-[0_0_0_2px] focus:shadow-black aria-[valuetext=Empty]:text-green9"
type="start"
>
{{ item.value }}
</DateRangePickerInput>
</template>
<span class="mx-2">
-
</span>
<template v-for="item in segments.end" :key="item.part">
<DateRangePickerInput
v-if="item.part === 'literal'"
:part="item.part"
type="end"
>
{{ item.value }}
</DateRangePickerInput>
<DateRangePickerInput
v-else
:part="item.part"
class="rounded-md p-0.5 focus:outline-none focus:shadow-[0_0_0_2px] focus:shadow-black aria-[valuetext=Empty]:text-green9"
type="end"
>
{{ item.value }}
</DateRangePickerInput>
</template>
<DateRangePickerTrigger class="ml-4 focus:shadow-[0_0_0_2px] focus:shadow-black">
<Icon icon="radix-icons:calendar" class="w-6 h-6" />
</DateRangePickerTrigger>
</DateRangePickerField>
<DateRangePickerContent
:side-offset="4"
class="rounded-xl bg-white shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_theme(colors.green7)] will-change-[transform,opacity] data-[state=open]:data-[side=top]:animate-slideDownAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade"
>
<DateRangePickerArrow class="fill-white" />
<DateRangePickerCalendar
v-slot="{ weekDays, grid }"
class="p-4"
>
<DateRangePickerHeader class="flex items-center justify-between">
<DateRangePickerPrev
class="inline-flex items-center cursor-pointer text-black justify-center rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
>
<Icon icon="radix-icons:chevron-left" class="w-6 h-6" />
</DateRangePickerPrev>
<DateRangePickerHeading class="text-[15px] text-black font-medium" />
<DateRangePickerNext
class="inline-flex items-center cursor-pointer text-black justify-center rounded-[9px] bg-transparent w-8 h-8 hover:bg-black hover:text-white active:scale-98 active:transition-all focus:shadow-[0_0_0_2px] focus:shadow-black"
>
<Icon icon="radix-icons:chevron-right" class="w-6 h-6" />
</DateRangePickerNext>
</DateRangePickerHeader>
<div
class="flex flex-col space-y-4 pt-4 sm:flex-row sm:space-x-4 sm:space-y-0"
>
<DateRangePickerGrid v-for="month in grid" :key="month.value.toString()" class="w-full border-collapse select-none space-y-1">
<DateRangePickerGridHead>
<DateRangePickerGridRow class="mb-1 flex w-full justify-between">
<DateRangePickerHeadCell
v-for="day in weekDays" :key="day"
class="w-8 rounded-md text-xs !font-normal text-black"
>
{{ day }}
</DateRangePickerHeadCell>
</DateRangePickerGridRow>
</DateRangePickerGridHead>
<DateRangePickerGridBody>
<DateRangePickerGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="flex w-full"
>
<DateRangePickerCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
>
<DateRangePickerCellTrigger
:day="weekDate"
:month="month.value"
class="relative flex items-center justify-center rounded-full whitespace-nowrap text-sm font-normal text-black w-8 h-8 outline-none focus:shadow-[0_0_0_2px] focus:shadow-black data-[disabled]:text-black/30 data-[selected]:!bg-green10 data-[selected]:text-white hover:bg-green5 data-[highlighted]:bg-green5 data-[unavailable]:pointer-events-none data-[unavailable]:text-black/30 data-[unavailable]:line-through before:absolute before:top-[5px] before:hidden before:rounded-full before:w-1 before:h-1 before:bg-white data-[today]:before:block data-[today]:before:bg-green9 "
/>
</DateRangePickerCell>
</DateRangePickerGridRow>
</DateRangePickerGridBody>
</DateRangePickerGrid>
</div>
</DateRangePickerCalendar>
</DateRangePickerContent>
</DateRangePickerRoot>
</div>
</template>
Credit
This component was built taking inspiration from the implementation in melt-ui.
Features
- Full keyboard navigation
- Can be controlled or uncontrolled
- Focus is fully managed
- Localization support
- Accessible by default
- Supports both date and date-time formats
Preface
The component depends on the @internationalized/date package, which solves a lot of the problems that come with working with dates and times in JavaScript.
We highly recommend reading through the documentation for the package to get a solid feel for how it works, and you'll need to install it in your project to use the date-related components.
Installation
Install the date package.
npm install -D @internationalized/date
Install the component from your command line.
npm install radix-vue
Anatomy
Import all parts and piece them together.
<script setup>
import {
DateRangePickerAnchor,
DateRangePickerArrow,
DateRangePickerCalendar,
DateRangePickerCell,
DateRangePickerCellTrigger,
DateRangePickerClose,
DateRangePickerContent,
DateRangePickerField,
DateRangePickerGrid,
DateRangePickerGridBody,
DateRangePickerGridHead,
DateRangePickerGridRow,
DateRangePickerHeadCell,
DateRangePickerHeader,
DateRangePickerHeading,
DateRangePickerInput,
DateRangePickerNext,
DateRangePickerPrev,
DateRangePickerRoot,
DateRangePickerTrigger,
} from 'radix-vue'
</script>
<template>
<DateRangePickerRoot>
<DateRangePickerField>
<DateRangePickerInput />
<DateRangePickerTrigger />
</DateRangePickerField>
<DateRangePickerAnchor />
<DateRangePickerContent>
<DateRangePickerClose />
<DateRangePickerArrow />
<DateRangePickerCalendar>
<DateRangePickerHeader>
<DateRangePickerPrev />
<DateRangePickerHeading />
<DateRangePickerNext />
</DateRangePickerHeader>
<DateRangePickerGrid>
<DateRangePickerGridHead>
<DateRangePickerGridRow>
<DateRangePickerHeadCell />
</DateRangePickerGridRow>
</DateRangePickerGridHead>
<DateRangePickerGridBody>
<DateRangePickerGridRow>
<DateRangePickerCell>
<DateRangePickerCellTrigger />
</DateRangePickerCell>
</DateRangePickerGridRow>
</DateRangePickerGridBody>
</DateRangePickerGrid>
</DateRangePickerCalendar>
</DateRangePickerContent>
</DateRangePickerRoot>
</template>
API Reference
Root
Contains all the parts of a date picker
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
defaultOpen | false | boolean The open state of the popover when it is initially rendered. Use when you do not need to control its open state. |
defaultPlaceholder | DateValue The default placeholder date | |
defaultValue | { start: DateValue; end: DateValue; } The default value for the calendar | |
disabled | false | boolean Whether or not the date field is disabled |
fixedWeeks | false | boolean Whether or not to always display 6 weeks in the calendar |
granularity | 'day' | 'hour' | 'minute' | 'second' The granularity to use for formatting times. Defaults to day if a CalendarDate is provided, otherwise defaults to minute. The field will render segments for each part of the date up to and including the specified granularity | |
hideTimeZone | boolean Whether or not to hide the time zone segment of the field | |
hourCycle | 12 | 24 The hour cycle used for formatting times. Defaults to the local preference | |
id | string Id of the element | |
isDateDisabled | Matcher A function that returns whether or not a date is disabled | |
isDateUnavailable | Matcher A function that returns whether or not a date is unavailable | |
locale | 'en' | 'tr' | 'th' | 'en' | 'ach' | 'af' | 'am' | 'an' | 'ar' | 'ast' | 'az' | 'be' | 'bg' | 'bn' | 'br' | 'bs' | 'ca' | 'cak' | 'ckb' | 'cs' | 'cy' | 'da' | 'de' | 'dsb' | 'el' | 'eo' | 'es' | ... 49 more ... Whether or not the calendar is readonly |
maxValue | DateValue The maximum date that can be selected | |
minValue | DateValue The locale to use for formatting dates | |
modal | false | boolean The modality of the popover. When set to true, interaction with outside elements will be disabled and only popover content will be visible to screen readers. |
modelValue | { start: DateValue; end: DateValue; } The controlled checked state of the calendar. Can be bound as | |
name | string The name of the date field. Submitted with its owning form as part of a name/value pair. | |
numberOfMonths | 1 | number The number of months to display at once |
open | boolean The controlled open state of the popover. | |
pagedNavigation | false | boolean This property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month |
placeholder | DateValue The placeholder date, which is used to determine what month to display when no date is selected. This updates as the user navigates the calendar and can be used to programatically control the calendar view | |
preventDeselect | false | boolean Whether or not to prevent the user from deselecting a date without selecting another date first |
readonly | false | boolean Whether or not the date field is readonly |
required | boolean When | |
weekdayFormat | 'narrow' | 'narrow' | 'short' | 'long' The format to use for the weekday strings provided via the weekdays slot prop |
weekStartsOn | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 The day of the week to start the calendar on |
Emit | Payload |
---|---|
update:modelValue | [date: DateValue] Event handler called whenever the model value changes |
update:open | [value: boolean] Event handler called when the open state of the submenu changes. |
update:placeholder | [date: DateValue] Event handler called whenever the placeholder value changes |
Field
Contains the date picker date field segments and trigger
Slots (default) | Payload |
---|---|
segments | { start: { part: SegmentPart; value: string; }[]; end: { part: SegmentPart; value: string; }[]; } |
Data Attribute | Value |
---|---|
[data-readonly] | Present when readonly |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
Input
Contains the date picker date field segments
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
part* | 'day' | 'month' | 'year' | 'hour' | 'minute' | 'second' | 'dayPeriod' | 'literal' | 'timeZoneName' The part of the date to render | |
type* | 'start' | 'end' The type of field to render (start or end) |
Data Attribute | Value |
---|---|
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
Trigger
The button that toggles the popover. By default, the DateRangePickerContent
will position itself against the trigger.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Content
The component that pops out when the popover is open.
Prop | Default | Type |
---|---|---|
align | 'start' | 'center' | 'end' The preferred alignment against the trigger. May change when collisions occur. | |
alignOffset | number An offset in pixels from the | |
arrowPadding | number The padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. | |
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
avoidCollisions | boolean When | |
collisionBoundary | Element | (Element | null)[] | null The element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. | |
collisionPadding | number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>> The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { top: 20, left: 20 }. | |
disableOutsidePointerEvents | boolean When | |
forceMount | boolean Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
hideWhenDetached | boolean Whether to hide the content when the trigger becomes fully occluded. | |
prioritizePosition | boolean Force content to be position within the viewport. Might overlap the reference element, which may not be desired. | |
side | 'top' | 'right' | 'bottom' | 'left' The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled. | |
sideOffset | number The distance in pixels from the trigger. | |
sticky | 'partial' | 'always' The sticky behavior on the align axis. | |
trapFocus | boolean Whether focus should be trapped within the | |
updatePositionStrategy | 'always' | 'optimized' Strategy to update the position of the floating element on every animation frame. |
Emit | Payload |
---|---|
closeAutoFocus | [event: Event] Event handler called when auto-focusing on close. Can be prevented. |
escapeKeyDown | [event: KeyboardEvent] Event handler called when the escape key is down. Can be prevented. |
focusOutside | [event: FocusOutsideEvent] Event handler called when the focus moves outside of the |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent] Event handler called when an interaction happens outside the |
openAutoFocus | [event: Event] Event handler called when auto-focusing on open. Can be prevented. |
pointerDownOutside | [event: PointerDownOutsideEvent] Event handler called when the a |
Arrow
An optional arrow element to render alongside the popover. This can be used to help visually link the anchor with the PopoverContent. Must be rendered inside PopoverContent.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
height | number The height of the arrow in pixels. | |
width | number The width of the arrow in pixels. |
Arrow
An optional arrow element to render alongside the popover. This can be used to help visually link the anchor with the DateRangePickerContent
. Must be rendered inside DateRangePickerContent
.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
height | number The height of the arrow in pixels. | |
width | number The width of the arrow in pixels. |
Close
The button that closes an open date picker.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Anchor
An optional element to position the DateRangePickerContent
against. If this part is not used, the content will position alongside the DateRangePickerTrigger
.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
element | Measurable |
Calendar
Contains all the parts of a calendar
Slots (default) | Payload |
---|---|
date | DateValue |
grid | Grid<DateValue>[] |
weekDays | string[] |
formatter | Formatter |
Data Attribute | Value |
---|---|
[data-readonly] | Present when readonly |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
Header
Contains the navigation buttons and the heading segments.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Prev Button
Calendar navigation button. It navigates the calendar one month/year/decade in the past based on the current calendar view.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Data Attribute | Value |
---|---|
[data-disabled] | Present when disabled |
NextButton
Calendar navigation button. It navigates the calendar one month/year/decade in the future based on the current calendar view.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Data Attribute | Value |
---|---|
[data-disabled] | Present when disabled |
Heading
Heading for displaying the current month and year
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Slots (default) | Payload |
---|---|
headingValue | string Current month and year |
Data Attribute | Value |
---|---|
[data-disabled] | Present when disabled |
Grid
Container for wrapping the calendar grid.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Data Attribute | Value |
---|---|
[data-readonly] | Present when readonly |
[data-disabled] | Present when disabled |
Grid Head
Container for wrapping the grid head.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Grid Body
Container for wrapping the grid body.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Grid Row
Container for wrapping the grid row.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Head Cell
Container for wrapping the head cell. Used for displaying the week days.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Cell
Container for wrapping the calendar cells.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
date* | DateValue |
Data Attribute | Value |
---|---|
[data-disabled] | Present when disabled |
Cell Trigger
Interactable container for displaying the cell dates. Clicking it selects the date.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
day* | DateValue | |
month* | DateValue |
Data Attribute | Value |
---|---|
[data-selected] | Present when selected |
[data-value] | The ISO string value of the date. |
[data-disabled] | Present when disabled |
[data-unavailable] | Present when unavailable |
[data-today] | Present when today |
[data-outside-view] | Present when the date is outside the current month it is displayed in. |
[data-outside-visible-view] | Present when the date is outside the months that are visible on the calendar. |
[data-selection-start] | Present when the date is the start of the selection. |
[data-selection-end] | Present when the date is the end of the selection. |
[data-highlighted] | Present when the date is highlighted by the user as they select a range. |
[data-focused] | Present when focused |
Accessibility
Keyboard Interactions
Key | Description |
---|---|
Tab | When focus moves onto the date field, focuses the first segment. |
Space |
When the focus is on either DateRangePickerNext or DateRangePickerPrev , it navigates the calendar. Otherwise, it selects the date. If the focus is on DateRangePickerTrigger , it opens/closes the popover.
|
Enter |
When the focus is on either DateRangePickerNext or DateRangePickerPrev , it navigates the calendar. Otherwise, it selects the date. If the focus is on DateRangePickerTrigger , it opens/closes the popover.
|
ArrowLeftArrowRight |
Navigates between the date field segments. If the focus is on the DateRangePickerCalendar , it navigates between the dates.
|
ArrowUpArrowDown | Increments/changes the value of the segment. If the focus is on the DateRangePickerCalendar , it navigates between the dates. |
0-9 |
When the focus is on a numeric DateRangePickerInput , it types in the number and focuses the next segment if the next input would result in an invalid value.
|
Backspace | Deletes a digit from the focused numeric segments. |
AP | When the focus is on the day period, it sets it to AM or PM. |