Date Range Picker

PohonGitHub
Facilitates the selection of date ranges through an input and calendar-based interface.
mm
dd
yyyy
-
mm
dd
yyyy
<script setup lang="ts">
import {
  ADateRangePickerArrow,
  ADateRangePickerCalendar,
  ADateRangePickerCell,
  ADateRangePickerCellTrigger,
  ADateRangePickerContent,
  ADateRangePickerField,
  ADateRangePickerGrid,
  ADateRangePickerGridBody,
  ADateRangePickerGridHead,
  ADateRangePickerGridRow,
  ADateRangePickerHeadCell,
  ADateRangePickerHeader,
  ADateRangePickerHeading,
  ADateRangePickerInput,
  ADateRangePickerNext,
  ADateRangePickerPrev,
  ADateRangePickerRoot,
  ADateRangePickerTrigger,
  ALabel,
} from 'akar';
</script>

<template>
  <div class="flex flex-col gap-2">
    <ALabel
      class="text-sm"
      for="booking"
    >
      Booking
    </ALabel>
    <ADateRangePickerRoot
      id="booking"
      :is-date-unavailable="date => date.day === 19"
    >
      <ADateRangePickerField
        v-slot="{ segments }"
        class="group text-sm color-text-highlighted px-2.5 py-1.5 rounded-md bg-background inline-flex gap-0.5 select-none ring ring-ring-accented ring-inset transition-colors items-center relative focus-visible:(ring-2 ring-primary ring-inset)"
      >
        <template
          v-for="item in segments.start"
          :key="item.part"
        >
          <ADateRangePickerInput
            class="text-center outline-hidden rounded transition-colors-280 data-[placeholder]:color-text-dimmed data-[segment=literal]:color-text-muted focus:bg-background-elevated"
            :class="{
              'w-11': item.part === 'year',
              'w-7': item.part === 'month' || item.part === 'day',
            }"
            :part="item.part"
            type="start"
          >
            {{ item.value }}
          </ADateRangePickerInput>
        </template>

        <span class="mx-2">
          -
        </span>

        <template
          v-for="item in segments.end"
          :key="item.part"
        >
          <ADateRangePickerInput
            class="text-center outline-hidden rounded transition-colors-280 data-[placeholder]:color-text-dimmed data-[segment=literal]:color-text-muted focus:bg-background-elevated"
            :class="{
              'w-11': item.part === 'year',
              'w-7': item.part === 'month' || item.part === 'day',
            }"
            :part="item.part"
            type="end"
          >
            {{ item.value }}
          </ADateRangePickerInput>
        </template>

        <ADateRangePickerTrigger class="p-1 rounded focus:outline-none focus:shadow-[0_0_0_2px] focus:shadow-black">
          <i
            class="i-lucide:calendar size-4"
          />
        </ADateRangePickerTrigger>
      </ADateRangePickerField>

      <ADateRangePickerContent
        :side-offset="4"
        class="will-change-[transform,opacity] border border-border rounded-xl bg-background shadow-sm"
      >
        <ADateRangePickerArrow class="fill-white stroke-gray-300" />
        <ADateRangePickerCalendar
          v-slot="{ weekDays, grid }"
          class="p-4"
        >
          <ADateRangePickerHeader class="flex items-center justify-between">
            <ADateRangePickerPrev
              class="text-sm color-text font-medium p-1.5 rounded-md inline-flex gap-1.5 items-center focus-visible:bg-background-elevated hover:bg-background-elevated"
            >
              <i
                class="i-lucide:chevron-left size-4"
              />
            </ADateRangePickerPrev>

            <ADateRangePickerHeading class="text-sm text-black font-medium" />
            <ADateRangePickerNext
              class="text-sm color-text font-medium p-1.5 rounded-md inline-flex gap-1.5 items-center focus-visible:bg-background-elevated hover:bg-background-elevated"
            >
              <i
                class="i-lucide:chevron-right size-4"
              />
            </ADateRangePickerNext>
          </ADateRangePickerHeader>
          <div
            class="pt-4 flex flex-col space-y-4 sm:(flex-row space-x-4 space-y-0)"
          >
            <ADateRangePickerGrid
              v-for="month in grid"
              :key="month.value.toString()"
              class="w-full select-none border-collapse space-y-1 focus:outline-none"
            >
              <ADateRangePickerGridHead>
                <ADateRangePickerGridRow class="mb-1 grid grid-cols-7 w-full">
                  <ADateRangePickerHeadCell
                    v-for="day in weekDays"
                    :key="day"
                    class="text-xs color-primary rounded-md"
                  >
                    {{ day }}
                  </ADateRangePickerHeadCell>
                </ADateRangePickerGridRow>
              </ADateRangePickerGridHead>
              <ADateRangePickerGridBody class="grid">
                <ADateRangePickerGridRow
                  v-for="(weekDates, index) in month.rows"
                  :key="`weekDate-${index}`"
                  class="grid grid-cols-7 place-items-center"
                >
                  <ADateRangePickerCell
                    v-for="weekDate in weekDates"
                    :key="weekDate.toString()"
                    :date="weekDate"
                  >
                    <ADateRangePickerCellTrigger
                      :day="weekDate"
                      :month="month.value"
                      class="m-0.5 rounded-full flex size-8 whitespace-nowrap transition-colors-280 items-center justify-center relative data-[disabled]:(color-text-dimmed cursor-not-allowed) data-[outside-view]:color-text-muted data-[unavailable]:(color-text-muted line-through pointer-events-none) data-[today]:font-semibold focus:outline-none data-[highlighted]:bg-primary/20 focus-visible:ring-2 focus-visible:ring-primary data-[today]:not-[[data-selected]]:color-primary hover:not-[[data-selected]]:bg-primary/20 akar:data-[selected]:color-text-inverted akar:data-[selected]:bg-primary"
                    />
                  </ADateRangePickerCell>
                </ADateRangePickerGridRow>
              </ADateRangePickerGridBody>
            </ADateRangePickerGrid>
          </div>
        </ADateRangePickerCalendar>
      </ADateRangePickerContent>
    </ADateRangePickerRoot>
  </div>
</template>

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.

pnpm add @internationalized/date

Anatomy

Import all parts and piece them together.

<script setup>
import {
  ADateRangePickerAnchor,
  ADateRangePickerArrow,
  ADateRangePickerCalendar,
  ADateRangePickerCell,
  ADateRangePickerCellTrigger,
  ADateRangePickerClose,
  ADateRangePickerContent,
  ADateRangePickerField,
  ADateRangePickerGrid,
  ADateRangePickerGridBody,
  ADateRangePickerGridHead,
  ADateRangePickerGridRow,
  ADateRangePickerHeadCell,
  ADateRangePickerHeader,
  ADateRangePickerHeading,
  ADateRangePickerInput,
  ADateRangePickerNext,
  ADateRangePickerPrev,
  ADateRangePickerRoot,
  ADateRangePickerTrigger,
} from 'akar';
</script>

<template>
  <ADateRangePickerRoot>
    <ADateRangePickerField>
      <ADateRangePickerInput />
      <ADateRangePickerTrigger />
    </ADateRangePickerField>

    <ADateRangePickerAnchor />

    <ADateRangePickerContent>
      <ADateRangePickerClose />
      <ADateRangePickerArrow />

      <ADateRangePickerCalendar>
        <ADateRangePickerHeader>
          <ADateRangePickerPrev />
          <ADateRangePickerHeading />
          <ADateRangePickerNext />
        </ADateRangePickerHeader>

        <ADateRangePickerGrid>
          <ADateRangePickerGridHead>
            <ADateRangePickerGridRow>
              <ADateRangePickerHeadCell />
            </ADateRangePickerGridRow>
          </ADateRangePickerGridHead>

          <ADateRangePickerGridBody>
            <ADateRangePickerGridRow>
              <ADateRangePickerCell>
                <ADateRangePickerCellTrigger />
              </ADateRangePickerCell>
            </ADateRangePickerGridRow>
          </ADateRangePickerGridBody>
        </ADateRangePickerGrid>
      </ADateRangePickerCalendar>
    </ADateRangePickerContent>
  </ADateRangePickerRoot>
</template>

Pohon

One benefit of using Akar is its flexibility and low-level control over the components. However, this also means that you may need to manually construct more complex UI elements by combining multiple Akar components together.

If you feel there's a lot of elements that needs to be constructed manually using Akar, consider using Pohon UI instead. It provides a higher-level abstraction over Akar components with pre-defined styles and behaviors that can help you build UIs faster.

API Reference

Root

Contains all the parts of a date picker

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

allowNonContiguousRangesfalseboolean

When combined with isDateUnavailable, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.

asChildboolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

closeOnSelectfalseboolean

Whether or not to close the popover on range select

defaultOpenfalseboolean

The open state of the popover when it is initially rendered. Use when you do not need to control its open state.

defaultPlaceholderDateValue

The default placeholder date

defaultValue{ start: undefined, end: undefined }DateRange

The default value for the calendar

dir'ltr' | 'rtl'

The reading direction of the date field when applicable.
If omitted, inherits globally from AConfigProvider or assumes LTR (left-to-right) reading mode.

disabledfalseboolean

Whether or not the date field is disabled

fixedDate'start' | 'end'

Which part of the range should be fixed

fixedWeeksfalseboolean

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

hideTimeZoneboolean

Whether or not to hide the time zone segment of the field

hourCycle12 | 24

The hour cycle used for formatting times. Defaults to the local preference

idstring

Id of the element

isDateDisabledDateMatcher

A function that returns whether or not a date is disabled

isDateHighlightableDateMatcher

A function that returns whether or not a date is hightable

isDateUnavailableDateMatcher

A function that returns whether or not a date is unavailable

locale'en'string

The locale to use for formatting dates

maximumDaysnumber

The maximum number of days that can be selected in a range

maxValueDateValue

The maximum date that can be selected

minValueDateValue

The minimum date that can be selected

modalfalseboolean

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.

modelValueDateRange | null

The controlled checked state of the calendar. Can be bound as v-model.

namestring

The name of the field. Submitted with its owning form as part of a name/value pair.

numberOfMonths1number

The number of months to display at once

openboolean

The controlled open state of the popover.

pagedNavigationfalseboolean

This property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month

placeholderDateValue

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 programmatically control the calendar view

preventDeselectfalseboolean

Whether or not to prevent the user from deselecting a date without selecting another date first

readonlyfalseboolean

Whether or not the date field is readonly

requiredboolean

When true, indicates that the user must set the value before the owning form can be submitted.

stepDateStep

The stepping interval for the time fields. Defaults to 1.

weekdayFormat'narrow''narrow' | 'long' | 'short'

The format to use for the weekday strings provided via the weekdays slot prop

weekStartsOn00 | 1 | 2 | 3 | 4 | 5 | 6

The day of the week to start the calendar on

Emits

Event Type
update:modelValue[date: DateRange]

Event handler called whenever the model value changes

update:open[value: boolean]

Event handler called when the open state of the combobox changes.

update:placeholder[date: DateValue]

Event handler called whenever the placeholder value changes

update:startValue[date: DateValue]

Event handler called whenever the start value changes

Slots

Slot Type
modelValueDateRange
openboolean

Field

Contains the date picker date field segments and trigger

Slots

Slot Type
segments{ start: { part: SegmentPart; value: string; }[]; end: { part: SegmentPart; value: string; }[]; }
modelValueDateRange | null

Data Attributes

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

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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 Attributes

Attribute Value
[data-disabled]Present when disabled
[data-invalid]Present when invalid
[data-placeholder]Present when no value is set

Trigger

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

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

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

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

alignFlipboolean

Flip alignment when colliding with boundary. May only occur when prioritizePosition is true.

alignOffsetnumber

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

arrowPaddingnumber

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.

asChildboolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

avoidCollisionsboolean

When true, overrides the side and align preferences to prevent collisions with boundary edges.

collisionBoundaryElement | (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.

collisionPaddingnumber | Partial<Record<Side, 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 }.

disableOutsidePointerEventsboolean

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.

disableUpdateOnLayoutShiftboolean

Whether to disable the update position for the content when the layout shifted.

forceMountboolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

hideWhenDetachedboolean

Whether to hide the content when the trigger becomes fully occluded.

portalAPopoverPortalProps

Props to control the portal wrapped around the content.

positionStrategy'fixed' | 'absolute'

The type of CSS position property to use.

prioritizePositionboolean

Force content to be position within the viewport.

Might overlap the reference element, which may not be desired.

referenceReferenceElement

The custom element or virtual element that will be set as the reference to position the floating element.

If provided, it will replace the default anchor element.

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.

sideFlipboolean

Flip to the opposite side when colliding with boundary.

sideOffsetnumber

The distance in pixels from the trigger.

sticky'always' | 'partial'

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.

trapFocusboolean

Whether focus should be trapped within the AMenuContent

updatePositionStrategy'always' | 'optimized'

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

Emits

Event Type
closeAutoFocus[event: Event]
escapeKeyDown[event: KeyboardEvent]
focusOutside[event: FocusOutsideEvent]
interactOutside[event: PointerDownOutsideEvent | FocusOutsideEvent]
openAutoFocus[event: Event]
pointerDownOutside[event: PointerDownOutsideEvent]

Arrow

An optional arrow element to render alongside the popover. This can be used to help visually link the anchor with the ADateRangePickerContent. Must be rendered inside ADateRangePickerContent.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

heightnumber

The height of the arrow in pixels.

roundedboolean

When true, render the rounded version of arrow. Do not work with as/asChild

widthnumber

The width of the arrow in pixels.

Close

The button that closes an open date picker.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

referenceReferenceElement

The custom element or virtual element that will be set as the reference to position the floating element.

If provided, it will replace the default anchor element.

Calendar

Contains all the parts of a calendar

Slots

Slot Type
dateDateValue
gridDateGrid<DateValue>
weekDaysstring[]
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6
localestring
fixedWeeksboolean

Data Attributes

Attribute Value
[data-disabled]Present when disabled
[data-readonly]Present when readonly
Present when invalid

Contains the navigation buttons and the heading segments.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

prevPage((placeholder: DateValue) => DateValue)

The function to be used for the prev page. Overwrites the prevPage function set on the range-calendar-root.

Slots

Slot Type
disabledboolean

Whether or not the date field is disabled

Data Attributes

Attribute Value
[data-disabled]Present when disabled

Next Button

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

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

nextPage((placeholder: DateValue) => DateValue)

The function to be used for the next page. Overwrites the nextPage function set on the range-calendar-root.

Slots

Slot Type
disabledboolean

Whether or not the date field is disabled

Data Attributes

Attribute Value
[data-disabled]Present when disabled

Heading

Heading for displaying the current month and year

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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

Slot Type
headingValuestring

Current month and year

Data Attributes

Attribute Value
[data-disabled]Present when disabled

Grid

Container for wrapping the calendar grid.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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 Attributes

Attribute Value
[data-disabled]Present when disabled
[data-readonly]Present when readonly

Grid Head

Container for wrapping the grid head.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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.

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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 Attributes

Attribute Value
[data-disabled]Present when disabled

Cell Trigger

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

Props

Prop Default Type
as'div'APrimitiveAsTag | Component

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

asChildboolean

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

Slots

Slot Type
dayValuestring

Current day

disabledboolean

Whether or not the date field is disabled

selectedboolean

Current selected state

todayboolean

Current today state

outsideViewboolean

Current outside view state

outsideVisibleViewboolean

Current outside visible view state

unavailableboolean

Current unavailable state

highlightedboolean

Current highlighted state

highlightedStartboolean

Current highlighted start state

highlightedEndboolean

Current highlighted end state

selectionStartboolean

Current selection start state

selectionEndboolean

Current selection end state

Data Attributes

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-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 ADatePickerNext or ADatePickerPrev, it navigates the calendar. Otherwise, it selects the date. If the focus is on ADatePickerTrigger, it opens/closes the popover.

Enter

When the focus is on either ADatePickerNext or ADatePickerPrev, it navigates the calendar. Otherwise it selects the date. If the focus is on ADatePickerTrigger, it opens/closes the popover.

ArrowLeftArrowRight

Navigates between the date field segments. If the focus is on the ADatePickerCalendar, it navigates between dates.

ArrowUpArrowDown

Increments/changes the value of the segment. If the focus is on the ADatePickerCalendar, it navigates between the dates.

0-9

When the focus is on a numeric ADatePickerInput, 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.