| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
30 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
<script setup lang="ts">
import type { ACalendarRootProps } from 'akar';
import { getLocalTimeZone, today } from '@internationalized/date';
import { ACalendarCell, ACalendarCellTrigger, ACalendarGrid, ACalendarGridBody, ACalendarGridHead, ACalendarGridRow, ACalendarHeadCell, ACalendarHeader, ACalendarHeading, ACalendarNext, ACalendarPrev, ACalendarRoot } from 'akar';
const date = today(getLocalTimeZone());
const isDateUnavailable: ACalendarRootProps['isDateUnavailable'] = (date) => {
return date.day === 17 || date.day === 18;
};
</script>
<template>
<ACalendarRoot
v-slot="{ weekDays, grid }"
:is-date-unavailable="isDateUnavailable"
:default-value="date"
fixed-weeks
>
<ACalendarHeader class="flex items-center justify-between">
<ACalendarPrev
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 h-4 w-4"
/>
</ACalendarPrev>
<ACalendarHeading class="text-sm font-medium mx-auto text-center truncate" />
<ACalendarNext
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 h-4 w-4"
/>
</ACalendarNext>
</ACalendarHeader>
<div
class="pt-4 flex flex-col space-y-4 sm:(flex-row space-x-4 space-y-0)"
>
<ACalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="w-full select-none border-collapse space-y-1 focus:outline-none"
>
<ACalendarGridHead>
<ACalendarGridRow class="mb-1 grid grid-cols-7 w-full">
<ACalendarHeadCell
v-for="day in weekDays"
:key="day"
class="text-xs color-primary rounded-md"
>
{{ day }}
</ACalendarHeadCell>
</ACalendarGridRow>
</ACalendarGridHead>
<ACalendarGridBody class="grid">
<ACalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="grid grid-cols-7 place-items-center"
>
<ACalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="text-sm text-center relative"
>
<ACalendarCellTrigger
: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"
/>
</ACalendarCell>
</ACalendarGridRow>
</ACalendarGridBody>
</ACalendarGrid>
</div>
</ACalendarRoot>
</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.
Import all parts and piece them together.
<script setup>
import {
ACalendarCell,
ACalendarCellTrigger,
ACalendarGrid,
ACalendarGridBody,
ACalendarGridHead,
ACalendarGridRow,
ACalendarHeadCell,
ACalendarHeader,
ACalendarHeading,
ACalendarNext,
ACalendarPrev,
ACalendarRoot,
} from 'akar';
</script>
<template>
<ACalendarRoot>
<ACalendarHeader>
<ACalendarPrev />
<ACalendarHeading />
<ACalendarNext />
</ACalendarHeader>
<ACalendarGrid>
<ACalendarGridHead>
<ACalendarGridRow>
<ACalendarHeadCell />
</ACalendarGridRow>
</ACalendarGridHead>
<ACalendarGridBody>
<ACalendarGridRow>
<ACalendarCell>
<ACalendarCellTrigger />
</ACalendarCell>
</ACalendarGridRow>
</ACalendarGridBody>
</ACalendarGrid>
</ACalendarRoot>
</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 calendar
| 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. | |
calendarLabel | stringThe accessible label for the calendar | |
defaultPlaceholder | DateValueThe default placeholder date | |
defaultValue | DateValueThe default value for the calendar | |
dir | 'ltr' | 'rtl'The reading direction of the calendar when applicable. | |
disabled | false | booleanWhether the calendar is disabled |
disableDaysOutsideCurrentView | false | booleanWhether or not to disable days outside the current view. |
fixedWeeks | false | boolean |
initialFocus | false | booleanIf true, the calendar will focus the selected day, today, or the first day of the month depending on what is visible when the calendar is mounted |
isDateDisabled | DateMatcherA function that returns whether or not a date is disabled | |
isDateUnavailable | DateMatcherA function that returns whether or not a date is unavailable | |
locale | string | |
maxValue | DateValueThe maximum date that can be selected | |
minValue | DateValueThe minimum date that can be selected | |
modelValue | DateValue | DateValue[] | |
multiple | false | booleanWhether multiple dates can be selected |
nextPage | ((placeholder: DateValue) => DateValue)A function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component. | |
numberOfMonths | 1 | numberThe number of months to display at once |
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 | |
preventDeselect | false | booleanWhether or not to prevent the user from deselecting a date without selecting another date first |
prevPage | ((placeholder: DateValue) => DateValue)A function that returns the previous page of the calendar. It receives the current placeholder as an argument inside the component. | |
readonly | false | booleanWhether the calendar is readonly |
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 | 6 |
| Event | Type |
|---|---|
update:modelValue | [date: DateValue]Event handler called whenever the model value changes |
update:placeholder | [date: DateValue]Event handler called whenever the placeholder value changes |
| Slot | Type |
|---|---|
date | DateValueThe current date of the placeholder |
grid | DateGrid<DateValue>The grid of dates |
weekDays | string[]The days of the week |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6The day of the week to start the calendar on |
locale | stringThe locale to use for formatting dates |
fixedWeeks | booleanWhether or not to always display 6 weeks in the calendar |
modelValue | DateValue | DateValue[] The controlled checked state of the calendar |
| Attribute | Value |
|---|---|
[data-readonly] | Present when readonly |
[data-disabled] | Present when disabled |
[data-invalid] | 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 | 'button' | 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)A function that returns the previous page of the calendar. It receives the current placeholder as an argument inside the component. |
| Slot | Type |
|---|---|
disabled | booleanWhether the calendar 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 | 'button' | 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)A function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component. |
| Slot | Type |
|---|---|
disabled | booleanWhether the calendar 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 | 'table' | 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-readonly] | Present when readonly |
[data-disabled] | Present when disabled |
Container for wrapping the grid head.
| Prop | Default | Type |
|---|---|---|
as | 'thead' | 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 | 'tbody' | 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 | 'tr' | 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 | 'th' | 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 | 'td' | 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* | DateValueThe current date of the placeholder |
| 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* | DateValueThe date value provided to the cell trigger | |
month* | DateValueThe month in which the cell is rendered |
| Slot | Type |
|---|---|
dayValue | stringCurrent day |
disabled | booleanWhether the calendar 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 |
| 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 |
This example showcases a calendar which allows incrementing the year.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
30 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
<script setup lang="ts">
import type { ACalendarRootProps } from 'akar';
import { today } from '@internationalized/date';
import { ACalendarCell, ACalendarCellTrigger, ACalendarGrid, ACalendarGridBody, ACalendarGridHead, ACalendarGridRow, ACalendarHeadCell, ACalendarHeader, ACalendarHeading, ACalendarNext, ACalendarPrev, ACalendarRoot } from 'akar';
const date = today('Asia/Makassar');
const isDateUnavailable: ACalendarRootProps['isDateUnavailable'] = (date) => {
return date.day === 17 || date.day === 18;
};
function pagingFunc(date: DateValue, sign: -1 | 1) {
if (sign === -1) {
return date.subtract({ years: 1 });
}
return date.add({ years: 1 });
}
</script>
<template>
<ACalendarRoot
v-slot="{ weekDays, grid }"
:is-date-unavailable="isDateUnavailable"
:default-value="date"
fixed-weeks
>
<ACalendarHeader class="flex items-center justify-between">
<ACalendarPrev
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"
:prev-page="(date: DateValue) => pagingFunc(date, -1)"
>
<i
class="i-lucide:chevrons-left h-4 w-4"
/>
</ACalendarPrev>
<ACalendarPrev
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 h-4 w-4"
/>
</ACalendarPrev>
<ACalendarHeading class="text-sm font-medium mx-auto text-center truncate" />
<ACalendarNext
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 h-4 w-4"
/>
</ACalendarNext>
<ACalendarNext
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"
:next-page="(date: DateValue) => pagingFunc(date, 1)"
>
<i
class="i-lucide:chevrons-right h-4 w-4"
/>
</ACalendarNext>
</ACalendarHeader>
<div
class="pt-4 flex flex-col space-y-4 sm:(flex-row space-x-4 space-y-0)"
>
<ACalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="w-full select-none border-collapse space-y-1 focus:outline-none"
>
<ACalendarGridHead>
<ACalendarGridRow class="mb-1 grid grid-cols-7 w-full">
<ACalendarHeadCell
v-for="day in weekDays"
:key="day"
class="text-xs color-primary rounded-md"
>
{{ day }}
</ACalendarHeadCell>
</ACalendarGridRow>
</ACalendarGridHead>
<ACalendarGridBody class="grid">
<ACalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="grid grid-cols-7 place-items-center"
>
<ACalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="text-sm text-center relative"
>
<ACalendarCellTrigger
: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"
/>
</ACalendarCell>
</ACalendarGridRow>
</ACalendarGridBody>
</ACalendarGrid>
</div>
</ACalendarRoot>
</template>
This example showcases some of the available locales and how the calendar systems are displayed.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
30 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
<script setup lang="ts">
import { createCalendar, getLocalTimeZone, toCalendar, today } from '@internationalized/date';
import { ACalendarCell, ACalendarCellTrigger, ACalendarGrid, ACalendarGridBody, ACalendarGridHead, ACalendarGridRow, ACalendarHeadCell, ACalendarHeader, ACalendarHeading, ACalendarNext, ACalendarPrev, ACalendarRoot, ALabel, ASelectContent, ASelectGroup, ASelectItem, ASelectItemIndicator, ASelectItemText, ASelectLabel, ASelectPortal, ASelectRoot, ASelectScrollDownButton, ASelectScrollUpButton, ASelectSeparator, ASelectTrigger, ASelectValue, ASelectViewport } from 'akar';
import { computed, ref } from 'vue';
const preferences = [
{ locale: 'en-US', label: 'Default', ordering: 'gregory' },
{ label: 'Arabic (Algeria)', locale: 'ar-DZ', territories: 'DJ DZ EH ER IQ JO KM LB LY MA MR OM PS SD SY TD TN YE', ordering: 'gregory islamic islamic-civil islamic-tbla' },
{ label: 'Arabic (United Arab Emirates)', locale: 'ar-AE', territories: 'AE BH KW QA', ordering: 'gregory islamic-umalqura islamic islamic-civil islamic-tbla' },
{ label: 'Arabic (Egypt)', locale: 'AR-EG', territories: 'EG', ordering: 'gregory coptic islamic islamic-civil islamic-tbla' },
{ label: 'Arabic (Saudi Arabia)', locale: 'ar-SA', territories: 'SA', ordering: 'islamic-umalqura gregory islamic islamic-rgsa' },
{ label: 'Farsi (Iran)', locale: 'fa-IR', territories: 'IR', ordering: 'persian gregory islamic islamic-civil islamic-tbla' },
{ label: 'Farsi (Afghanistan)', locale: 'fa-AF', territories: 'AF IR', ordering: 'persian gregory islamic islamic-civil islamic-tbla' },
{ label: 'Amharic (Ethiopia)', locale: 'am-ET', territories: 'ET', ordering: 'gregory ethiopic ethioaa' },
{ label: 'Hebrew (Israel)', locale: 'he-IL', territories: 'IL', ordering: 'gregory hebrew islamic islamic-civil islamic-tbla' },
{ label: 'Hindi (India)', locale: 'hi-IN', territories: 'IN', ordering: 'gregory indian' },
{ label: 'Japanese (Japan)', locale: 'ja-JP', territories: 'JP', ordering: 'gregory japanese' },
{ label: 'Thai (Thailand)', locale: 'th-TH', territories: 'TH', ordering: 'buddhist gregory' },
{ label: 'Chinese (Taiwan)', locale: 'zh-TW', territories: 'TW', ordering: 'gregory roc chinese' },
];
const calendars = [
{ key: 'gregory', name: 'Gregorian' },
{ key: 'japanese', name: 'Japanese' },
{ key: 'buddhist', name: 'Buddhist' },
{ key: 'roc', name: 'Taiwan' },
{ key: 'persian', name: 'Persian' },
{ key: 'indian', name: 'Indian' },
{ key: 'islamic-umalqura', name: 'Islamic (Umm al-Qura)' },
{ key: 'islamic-civil', name: 'Islamic Civil' },
{ key: 'islamic-tbla', name: 'Islamic Tabular' },
{ key: 'hebrew', name: 'Hebrew' },
{ key: 'coptic', name: 'Coptic' },
{ key: 'ethiopic', name: 'Ethiopic' },
{ key: 'ethioaa', name: 'Ethiopic (Amete Alem)' },
];
const locale = ref(preferences[0].locale);
const calendar = ref(calendars[0].key);
const pref = computed(() => preferences.find((p) => p.locale === locale.value));
const preferredCalendars = computed(() => pref.value ? pref.value.ordering.split(' ').map((p) => calendars.find((c) => c.key === p)).filter(Boolean) : [calendars[0]]);
const otherCalendars = computed(() => calendars.filter((c) => !preferredCalendars.value.some((p) => p!.key === c.key)));
function updateLocale(newLocale: string) {
locale.value = newLocale;
calendar.value = pref.value!.ordering.split(' ')[0];
}
const value = computed(() => toCalendar(today(getLocalTimeZone()), createCalendar(calendar.value)));
</script>
<template>
<div class="flex flex-col gap-4">
<ALabel class="text-white">
Locale
</ALabel>
<ASelectRoot
v-model="locale"
@update:model-value="updateLocale"
>
<ASelectTrigger
class="text-grass11 hover:bg-mauve3 data-[placeholder]:text-green9 text-xs leading-none px-[15px] outline-none rounded-md bg-white inline-flex gap-[5px] h-[35px] min-w-[160px] shadow-[0_2px_10px] shadow-black/10 items-center justify-between focus:shadow-[0_0_0_2px] focus:shadow-black"
aria-label="Select a locale"
>
<ASelectValue placeholder="Please select a locale">
{{ pref!.label }}
</ASelectValue>
<i
class="i-lucide:chevron-down h-3.5 w-3.5"
/>
</ASelectTrigger>
<ASelectPortal>
<ASelectContent
class="data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade will-change-[opacity,transform] rounded-md bg-white min-w-[160px] shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] z-[100]"
:side-offset="5"
>
<ASelectScrollUpButton class="text-violet11 bg-white flex h-[25px] cursor-default items-center justify-center">
<i class="i-lucide:chevron-up" />
</ASelectScrollUpButton>
<ASelectViewport class="p-[5px]">
<ASelectItem
v-for="(option, index) in preferences"
:key="index"
class="text-grass11 data-[disabled]:text-mauve8 data-[highlighted]:bg-green9 data-[highlighted]:text-green1 text-xs leading-none pl-[25px] pr-[35px] rounded-[3px] flex h-[25px] select-none items-center relative data-[highlighted]:outline-none data-[disabled]:pointer-events-none"
:value="option.locale"
>
<ASelectItemIndicator class="inline-flex w-[25px] items-center left-0 justify-center absolute">
<i class="i-lucide:check" />
</ASelectItemIndicator>
<ASelectItemText>
{{ option.label }}
</ASelectItemText>
</ASelectItem>
</ASelectViewport>
<ASelectScrollDownButton class="text-violet11 bg-white flex h-[25px] cursor-default items-center justify-center">
<i class="i-lucide:chevron-down" />
</ASelectScrollDownButton>
</ASelectContent>
</ASelectPortal>
</ASelectRoot>
<ALabel class="text-white">
Calendar
</ALabel>
<ASelectRoot v-model="calendar">
<ASelectTrigger
class="text-grass11 hover:bg-mauve3 data-[placeholder]:text-green9 text-xs leading-none px-[15px] outline-none rounded-md bg-white inline-flex gap-[5px] h-[35px] min-w-[160px] shadow-[0_2px_10px] shadow-black/10 items-center justify-between focus:shadow-[0_0_0_2px] focus:shadow-black"
aria-label="Select a calendar"
>
<ASelectValue placeholder="Please select a calendar">
{{ calendars.find(c => c.key === calendar)?.name }}
</ASelectValue>
<i
class="i-lucide:chevron-down h-3.5 w-3.5"
/>
</ASelectTrigger>
<ASelectPortal>
<ASelectContent
class="data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade will-change-[opacity,transform] rounded-md bg-white min-w-[160px] shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] z-[100]"
:side-offset="5"
>
<ASelectScrollUpButton class="text-violet11 bg-white flex h-[25px] cursor-default items-center justify-center">
<i class="i-lucide:chevron-up" />
</ASelectScrollUpButton>
<ASelectViewport class="p-[5px]">
<ASelectLabel class="text-mauve11 text-xs leading-[25px] px-[25px]">
Preferred
</ASelectLabel>
<ASelectGroup>
<ASelectItem
v-for="(option, index) in preferredCalendars"
:key="index"
class="text-grass11 data-[disabled]:text-mauve8 data-[highlighted]:bg-green9 data-[highlighted]:text-green1 text-xs leading-none pl-[25px] pr-[35px] rounded-[3px] flex h-[25px] select-none items-center relative data-[highlighted]:outline-none data-[disabled]:pointer-events-none"
:value="option!.key"
>
<ASelectItemIndicator class="inline-flex w-[25px] items-center left-0 justify-center absolute">
<i class="i-lucide:check" />
</ASelectItemIndicator>
<ASelectItemText>
{{ option!.name }}
</ASelectItemText>
</ASelectItem>
</ASelectGroup>
<ASelectSeparator class="bg-green6 m-[5px] h-[1px]" />
<ASelectLabel class="text-mauve11 text-xs leading-[25px] px-[25px]">
Other
</ASelectLabel>
<ASelectGroup>
<ASelectItem
v-for="(option, index) in otherCalendars"
:key="index"
class="text-grass11 data-[disabled]:text-mauve8 data-[highlighted]:bg-green9 data-[highlighted]:text-green1 text-xs leading-none pl-[25px] pr-[35px] rounded-[3px] flex h-[25px] select-none items-center relative data-[highlighted]:outline-none data-[disabled]:pointer-events-none"
:value="option.key"
>
<ASelectItemIndicator class="inline-flex w-[25px] items-center left-0 justify-center absolute">
<i class="i-lucide:check" />
</ASelectItemIndicator>
<ASelectItemText>
{{ option.name }}
</ASelectItemText>
</ASelectItem>
</ASelectGroup>
</ASelectViewport>
<ASelectScrollDownButton class="text-violet11 bg-white flex h-[25px] cursor-default items-center justify-center">
<i class="i-lucide:chevron-down" />
</ASelectScrollDownButton>
</ASelectContent>
</ASelectPortal>
</ASelectRoot>
<ACalendarRoot
v-slot="{ weekDays, grid }"
:model-value="value"
:locale="locale"
fixed-weeks
>
<ACalendarHeader class="flex items-center justify-between">
<ACalendarPrev
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 h-4 w-4"
/>
</ACalendarPrev>
<ACalendarHeading class="text-sm font-medium mx-auto text-center truncate" />
<ACalendarNext
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 h-4 w-4"
/>
</ACalendarNext>
</ACalendarHeader>
<div
class="pt-4 flex flex-col space-y-4 sm:(flex-row space-x-4 space-y-0)"
>
<ACalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="w-full select-none border-collapse space-y-1 focus:outline-none"
>
<ACalendarGridHead>
<ACalendarGridRow class="mb-1 grid grid-cols-7 w-full">
<ACalendarHeadCell
v-for="day in weekDays"
:key="day"
class="text-xs color-primary rounded-md"
>
{{ day }}
</ACalendarHeadCell>
</ACalendarGridRow>
</ACalendarGridHead>
<ACalendarGridBody class="grid">
<ACalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="grid grid-cols-7 place-items-center"
>
<ACalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="text-sm text-center relative"
>
<ACalendarCellTrigger
: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"
/>
</ACalendarCell>
</ACalendarGridRow>
</ACalendarGridBody>
</ACalendarGrid>
</div>
</ACalendarRoot>
</div>
</template>
This component demonstrates intuitive calendar navigation using touch-based swipe gestures, user-friendly way to browse through months.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
<script setup lang="ts">
import { CalendarDate } from '@internationalized/date';
import { usePointerSwipe } from '@vueuse/core';
import { ACalendarCell, ACalendarCellTrigger, ACalendarGrid, ACalendarGridBody, ACalendarGridHead, ACalendarGridRow, ACalendarHeadCell, ACalendarHeader, ACalendarHeading, ACalendarNext, ACalendarPrev, ACalendarRoot } from 'akar';
import { onMounted, ref, useTemplateRef } from 'vue';
const calendarRef = useTemplateRef('calendarRef');
const date = ref(new CalendarDate(2023, 1, 1));
function nextPage() {
date.value = date.value.add({ months: 1 }).copy();
}
function prevPage() {
date.value = date.value.subtract({ months: 1 }).copy();
}
onMounted(() => {
if (calendarRef.value) {
usePointerSwipe(calendarRef.value.$el, {
onSwipeEnd(_e, direction) {
if (direction === 'none') {
// eslint-disable-next-line no-useless-return
return;
} else if (['down', 'right'].includes(direction)) {
prevPage();
} else {
nextPage();
}
},
});
}
});
</script>
<template>
<ACalendarRoot
ref="calendarRef"
v-slot="{ weekDays, grid }"
v-model:placeholder="date"
fixed-weeks
>
<ACalendarHeader class="flex items-center justify-between">
<ACalendarPrev
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 h-4 w-4"
/>
</ACalendarPrev>
<ACalendarHeading class="text-sm font-medium mx-auto text-center truncate" />
<ACalendarNext
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 h-4 w-4"
/>
</ACalendarNext>
</ACalendarHeader>
<div
class="pt-4 flex flex-col space-y-4 sm:(flex-row space-x-4 space-y-0)"
>
<ACalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="w-full select-none border-collapse space-y-1 focus:outline-none"
>
<ACalendarGridHead>
<ACalendarGridRow class="mb-1 grid grid-cols-7 w-full">
<ACalendarHeadCell
v-for="day in weekDays"
:key="day"
class="text-xs color-primary rounded-md"
>
{{ day }}
</ACalendarHeadCell>
</ACalendarGridRow>
</ACalendarGridHead>
<ACalendarGridBody class="grid">
<ACalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="grid grid-cols-7 place-items-center"
>
<ACalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="text-sm text-center relative"
>
<ACalendarCellTrigger
: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"
/>
</ACalendarCell>
</ACalendarGridRow>
</ACalendarGridBody>
</ACalendarGrid>
</div>
</ACalendarRoot>
</template>
This example showcases usage of the CalendarWeek component used to display the number of the week.
| Wk | S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|---|
30 | 1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 | |
14 | 15 | 16 | 17 | 18 | 19 | 20 | |
21 | 22 | 23 | 24 | 25 | 26 | 27 | |
28 | 29 | 30 | 31 | 1 | 2 | 3 | |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
<script setup lang="ts">
import { ACalendarCell, ACalendarCellTrigger, ACalendarGrid, ACalendarGridBody, ACalendarGridHead, ACalendarGridRow, ACalendarHeadCell, ACalendarHeader, ACalendarHeading, ACalendarNext, ACalendarPrev, ACalendarRoot } from 'akar';
import { getWeekNumber } from 'akar/date';
</script>
<template>
<ACalendarRoot
v-slot="{ weekDays, grid }"
fixed-weeks
>
<ACalendarHeader class="flex items-center justify-between">
<ACalendarPrev
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 h-4 w-4"
/>
</ACalendarPrev>
<ACalendarHeading class="text-sm font-medium mx-auto text-center truncate" />
<ACalendarNext
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 h-4 w-4"
/>
</ACalendarNext>
</ACalendarHeader>
<div
class="pt-4 flex flex-col space-y-4 sm:(flex-row space-x-4 space-y-0)"
>
<ACalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="w-full select-none border-collapse space-y-1 focus:outline-none"
>
<ACalendarGridHead>
<ACalendarGridRow class="mb-1 grid grid-cols-7 w-full">
<ACalendarHeadCell
class="text-xs color-primary rounded-md"
>
Wk
</ACalendarHeadCell>
<ACalendarHeadCell
v-for="day in weekDays"
:key="day"
class="text-xs color-primary rounded-md"
>
{{ day }}
</ACalendarHeadCell>
</ACalendarGridRow>
</ACalendarGridHead>
<ACalendarGridBody class="grid">
<ACalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="grid grid-cols-8 place-items-center"
>
<div
class="text-sm flex items-center justify-center"
>
{{ getWeekNumber(weekDates[0]) }}
</div>
<ACalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="text-sm text-center relative"
>
<ACalendarCellTrigger
: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"
/>
</ACalendarCell>
</ACalendarGridRow>
</ACalendarGridBody>
</ACalendarGrid>
</div>
</ACalendarRoot>
</template>
| Key | Description |
|---|---|
Tab | When focus moves onto the calendar, focuses the first navigation button. |
Space | When the focus is on either |
Enter | When the focus is on either |
ArrowLeftArrowRightArrowUpArrowDown | When the focus is on |