<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>
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.
Install the date package.
pnpm add @internationalized/date
npm install @internationalized/date
bun add @internationalized/date
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>
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.
Contains all the parts of a date picker
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
allowNonContiguousRanges | false | booleanWhen combined with |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
closeOnSelect | false | booleanWhether or not to close the popover on range select |
defaultOpen | false | booleanThe open state of the popover when it is initially rendered. Use when you do not need to control its open state. |
defaultPlaceholder | DateValueThe default placeholder date | |
defaultValue | { start: undefined, end: undefined } | DateRangeThe default value for the calendar |
dir | 'ltr' | 'rtl'The reading direction of the date field when applicable. | |
disabled | false | booleanWhether or not the date field is disabled |
fixedDate | 'start' | 'end'Which part of the range should be fixed | |
fixedWeeks | false | booleanWhether 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 | booleanWhether or not to hide the time zone segment of the field | |
hourCycle | 12 | 24The hour cycle used for formatting times. Defaults to the local preference | |
id | stringId of the element | |
isDateDisabled | DateMatcherA function that returns whether or not a date is disabled | |
isDateHighlightable | DateMatcherA function that returns whether or not a date is hightable | |
isDateUnavailable | DateMatcherA function that returns whether or not a date is unavailable | |
locale | 'en' | stringThe locale to use for formatting dates |
maximumDays | numberThe maximum number of days that can be selected in a range | |
maxValue | DateValueThe maximum date that can be selected | |
minValue | DateValueThe minimum date that can be selected | |
modal | false | booleanThe 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 | DateRange | nullThe controlled checked state of the calendar. Can be bound as | |
name | stringThe name of the field. Submitted with its owning form as part of a name/value pair. | |
numberOfMonths | 1 | numberThe number of months to display at once |
open | booleanThe controlled open state of the popover. | |
pagedNavigation | false | booleanThis property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month |
placeholder | DateValueThe 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 | |
preventDeselect | false | booleanWhether or not to prevent the user from deselecting a date without selecting another date first |
readonly | false | booleanWhether or not the date field is readonly |
required | booleanWhen | |
step | DateStepThe stepping interval for the time fields. Defaults to | |
weekdayFormat | 'narrow' | 'narrow' | 'long' | 'short'The format to use for the weekday strings provided via the weekdays slot prop |
weekStartsOn | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6The day of the week to start the calendar on |
| 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 |
| Slot | Type |
|---|---|
modelValue | DateRange |
open | boolean |
Contains the date picker date field segments and trigger
| Slot | Type |
|---|---|
segments | { start: { part: SegmentPart; value: string; }[]; end: { part: SegmentPart; value: string; }[]; } |
modelValue | DateRange | null |
| Attribute | Value |
|---|---|
[data-readonly] | Present when readonly |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
Contains the date picker date field segments
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange 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) |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-placeholder] | Present when no value is set |
The button that toggles the popover. By default, the ADateRangePickerContent will position itself against the trigger.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
The component that pops out when the popover is open.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
align | 'start' | 'center' | 'end'The preferred alignment against the trigger. May change when collisions occur. | |
alignFlip | booleanFlip alignment when colliding with boundary.
May only occur when | |
alignOffset | numberAn offset in pixels from the | |
arrowPadding | numberThe padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. | |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
avoidCollisions | booleanWhen | |
collisionBoundary | Element | (Element | null)[] | nullThe 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<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 }. | |
disableOutsidePointerEvents | booleanWhen | |
disableUpdateOnLayoutShift | booleanWhether to disable the update position for the content when the layout shifted. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
hideWhenDetached | booleanWhether to hide the content when the trigger becomes fully occluded. | |
portal | APopoverPortalPropsProps to control the portal wrapped around the content. | |
positionStrategy | 'fixed' | 'absolute'The type of CSS position property to use. | |
prioritizePosition | booleanForce content to be position within the viewport. Might overlap the reference element, which may not be desired. | |
reference | ReferenceElementThe 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. | |
sideFlip | booleanFlip to the opposite side when colliding with boundary. | |
sideOffset | numberThe distance in pixels from the trigger. | |
sticky | 'always' | 'partial'The sticky behavior on the align axis. | |
trapFocus | booleanWhether focus should be trapped within the | |
updatePositionStrategy | 'always' | 'optimized'Strategy to update the position of the floating element on every animation frame. |
| Event | Type |
|---|---|
closeAutoFocus | [event: Event] |
escapeKeyDown | [event: KeyboardEvent] |
focusOutside | [event: FocusOutsideEvent] |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent] |
openAutoFocus | [event: Event] |
pointerDownOutside | [event: PointerDownOutsideEvent] |
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.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
height | numberThe height of the arrow in pixels. | |
rounded | booleanWhen | |
width | numberThe width of the arrow in pixels. |
The button that closes an open date picker.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
An optional element to position the ADateRangePickerContent against. If this part is not used, the content will position alongside the ADateRangePickerTrigger.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
reference | ReferenceElementThe 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. |
Contains all the parts of a calendar
| Slot | Type |
|---|---|
date | DateValue |
grid | DateGrid<DateValue> |
weekDays | string[] |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
locale | string |
fixedWeeks | boolean |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
[data-readonly] | Present when readonly |
| Present when invalid |
Contains the navigation buttons and the heading segments.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
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' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange 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 |
| Slot | Type |
|---|---|
disabled | booleanWhether or not the date field is disabled |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
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' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange 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 |
| Slot | Type |
|---|---|
disabled | booleanWhether or not the date field is disabled |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
Heading for displaying the current month and year
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Slot | Type |
|---|---|
headingValue | stringCurrent month and year |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
Container for wrapping the calendar grid.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
[data-readonly] | Present when readonly |
Container for wrapping the grid head.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Container for wrapping the grid body.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Container for wrapping the grid row.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Container for wrapping the head cell. Used for displaying the week days.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Container for wrapping the calendar cells.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange 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 |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
Interactable container for displaying the cell dates. Clicking it selects the date.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange 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 |
| Slot | Type |
|---|---|
dayValue | stringCurrent day |
disabled | booleanWhether or not the date field is disabled |
selected | booleanCurrent selected state |
today | booleanCurrent today state |
outsideView | booleanCurrent outside view state |
outsideVisibleView | booleanCurrent outside visible view state |
unavailable | booleanCurrent unavailable state |
highlighted | booleanCurrent highlighted state |
highlightedStart | booleanCurrent highlighted start state |
highlightedEnd | booleanCurrent highlighted end state |
selectionStart | booleanCurrent selection start state |
selectionEnd | booleanCurrent selection end state |
| 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 |
| Key | Description |
|---|---|
Tab | When focus moves onto the date field, focuses the first segment. |
Space | When the focus is on either |
Enter | When the focus is on either |
ArrowLeftArrowRight | Navigates between the date field segments. If the focus is on the |
ArrowUpArrowDown | Increments/changes the value of the segment. If the focus is on the |
0-9 | When the focus is on a numeric |
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. |