Calendar

PohonGitHub
Displays dates and days of the week, facilitating date-related interactions.
December 2025
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
Event Date, December 2025
<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>

Features

  • Automatic and manual control over when the image renders.
  • Fallback part accepts any children.
  • Optionally delay fallback rendering to avoid content flashing.
  • Full keyboard navigation
  • Can be controlled or uncontrolled
  • Focus is fully managed
  • Localization support
  • Highly composable

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.

Anatomy

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>

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 calendar

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.

calendarLabelstring

The accessible label for the calendar

defaultPlaceholderDateValue

The default placeholder date

defaultValueDateValue

The default value for the calendar

dir'ltr' | 'rtl'

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

disabledfalseboolean

Whether the calendar is disabled

disableDaysOutsideCurrentViewfalseboolean

Whether or not to disable days outside the current view.

fixedWeeksfalseboolean
initialFocusfalseboolean

If 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

isDateDisabledDateMatcher

A function that returns whether or not a date is disabled

isDateUnavailableDateMatcher

A function that returns whether or not a date is unavailable

localestring
maxValueDateValue

The maximum date that can be selected

minValueDateValue

The minimum date that can be selected

modelValueDateValue | DateValue[]
multiplefalseboolean

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

numberOfMonths1number

The number of months to display at once

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

preventDeselectfalseboolean

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

readonlyfalseboolean

Whether the calendar is readonly

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

Emits

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

Slots

Slot Type
dateDateValue

The current date of the placeholder

gridDateGrid<DateValue>

The grid of dates

weekDaysstring[]

The days of the week

weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6

The day of the week to start the calendar on

localestring

The locale to use for formatting dates

fixedWeeksboolean

Whether or not to always display 6 weeks in the calendar

modelValueDateValue | DateValue[]

The controlled checked state of the calendar

Data Attributes

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.

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'button'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)

A function that returns the previous page of the calendar. It receives the current placeholder as an argument inside the component.

Slots

Slot Type
disabledboolean

Whether the calendar 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'button'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)

A function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component.

Slots

Slot Type
disabledboolean

Whether the calendar 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'table'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-readonly]Present when readonly
[data-disabled]Present when disabled

Grid Head

Container for wrapping the grid head.

Props

Prop Default Type
as'thead'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'tbody'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'tr'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'th'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'td'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

The current date of the placeholder

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

The date value provided to the cell trigger

month*DateValue

The month in which the cell is rendered

Slots

Slot Type
dayValuestring

Current day

disabledboolean

Whether the calendar 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

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

Examples

Calendar with Year Incrementation

This example showcases a calendar which allows incrementing the year.

December 2025
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
Event Date, December 2025
<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>

Calendar with Locale and Calendar System Selection

This example showcases some of the available locales and how the calendar systems are displayed.

December 2025
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
Event Date, December 2025
<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>

Calendar swipe gesture navigation

This component demonstrates intuitive calendar navigation using touch-based swipe gestures, user-friendly way to browse through months.

January 2023
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
Event Date, January 2023
<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>

Calendar week numbers

This example showcases usage of the CalendarWeek component used to display the number of the week.

December 2025
48
49
50
51
52
1
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
Event Date, December 2025
<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>

Accessibility

Keyboard Interactions

Key Description
Tab

When focus moves onto the calendar, focuses the first navigation button.

Space

When the focus is on either CalendarNext or CalendarPrev, it navigates the calendar. Otherwise, it selects the date.

Enter

When the focus is on either CalendarNext or CalendarPrev, it navigates the calendar. Otherwise, it selects the date.

ArrowLeftArrowRightArrowUpArrowDown

When the focus is on CalendarCellTrigger, it navigates the dates, changing the month/year/decade if necessary.