Skip to content

Date Range Picker

Alpha
Facilitates the selection of date ranges through an input and calendar-based interface.
mm
dd
yyyy
-
mm
dd
yyyy
vue
<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.

bash
npm install -D @internationalized/date

Install the component from your command line.

bash
npm install radix-vue

Anatomy

Import all parts and piece them together.

vue
<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

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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 v-model.

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 true, indicates that the user must check the date field before the owning form can be submitted.

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

EmitPayload
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; }[]; }
modelValue
{ start: DateValue | undefined; end: DateValue | undefined; }

Input

Contains the date picker date field segments

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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)

Trigger

The button that toggles the popover. By default, the will position itself against the trigger.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
align
'start' | 'center' | 'end'

The preferred alignment against the trigger. May change when collisions occur.

alignOffset
number

An offset in pixels from the start or end alignment options.

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

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 true, overrides the side andalign preferences to prevent collisions with boundary edges.

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 true, hover/focus/click interactions will be disabled on elements outside the DismissableLayer. Users will need to click twice on outside elements to interact with them: once to close the DismissableLayer, and again to trigger the element.

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. partial will keep the content in the boundary as long as the trigger is at least partially in the boundary whilst "always" will keep the content in the boundary regardless.

trapFocus
boolean

Whether focus should be trapped within the MenuContent

updatePositionStrategy
'always' | 'optimized'

Strategy to update the position of the floating element on every animation frame.

EmitPayload
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 DismissableLayer. Can be prevented.

interactOutside
[event: PointerDownOutsideEvent | FocusOutsideEvent]

Event handler called when an interaction happens outside the DismissableLayer. Specifically, when a pointerdown event happens outside or focus moves outside of it. Can be prevented.

openAutoFocus
[event: Event]

Event handler called when auto-focusing on open. Can be prevented.

pointerDownOutside
[event: PointerDownOutsideEvent]

Event handler called when the a pointerdown event happens outside of the DismissableLayer. Can be prevented.

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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 . Must be rendered inside .

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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 against. If this part is not used, the content will position alongside the .

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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

Contains the navigation buttons and the heading segments.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

NextButton

Calendar navigation button. It navigates the calendar one month/year/decade in the future based on the current calendar view.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

Heading

Heading for displaying the current month and year

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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

Grid

Container for wrapping the calendar grid.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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 Head

Container for wrapping the grid head.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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

Cell Trigger

Interactable container for displaying the cell dates. Clicking it selects the date.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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

Accessibility

Keyboard Interactions

KeyDescription
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.