| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 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 |
<script setup lang="ts">
import type { CalendarRootProps } from 'akar';
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNext, CalendarPrev, CalendarRoot } from 'akar';
import './styles.css';
const isDateUnavailable: CalendarRootProps['isDateUnavailable'] = (date) => {
return date.day === 17 || date.day === 18;
};
</script>
<template>
<div class="akar-demo akar-demo-Calendar">
<CalendarRoot
v-slot="{ weekDays, grid }"
:is-date-unavailable="isDateUnavailable"
class="Calendar"
fixed-weeks
>
<CalendarHeader class="CalendarHeader">
<CalendarPrev
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-left"
class="Icon"
/>
</CalendarPrev>
<CalendarHeading class="CalendarHeading" />
<CalendarNext
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-right"
class="Icon"
/>
</CalendarNext>
</CalendarHeader>
<div
class="CalendarWrapper"
>
<CalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="CalendarGrid"
>
<CalendarGridHead>
<CalendarGridRow class="CalendarGridRow">
<CalendarHeadCell
v-for="day in weekDays"
:key="day"
class="CalendarHeadCell"
>
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody class="CalendarGridWrapper">
<CalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="CalendarGridRow"
>
<CalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="CalendarCell"
>
<CalendarCellTrigger
:day="weekDate"
:month="month.value"
class="CalendarCellTrigger"
/>
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</div>
</CalendarRoot>
</div>
</template>@import '../_colors/black-alpha.css';
@import '../_colors/grass.css';
@scope (.akar-demo-Calendar) {
.Icon {
width: 1.5rem;
height: 1.5rem;
}
.Calendar {
margin-top: 1.5rem;
border-width: 1px;
border-color: #000000;
background-color: #ffffff;
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 22px;
}
.CalendarHeader {
display: flex;
justify-content: space-between;
align-items: center;
}
.CalendarNavButton {
display: inline-flex;
justify-content: center;
align-items: center;
width: 2.5rem;
height: 2.5rem;
color: #000000;
background-color: transparent;
cursor: pointer;
}
.CalendarNavButton:hover {
color: #ffffff;
background-color: #000000;
}
.CalendarHeading {
font-weight: 500;
color: #000000;
}
.CalendarWrapper {
display: flex;
padding-top: 1rem;
margin-top: 1rem;
flex-direction: column;
}
@media (min-width: 640px) {
.CalendarWrapper {
margin-left: 1rem;
margin-top: 0;
flex-direction: row;
}
}
.CalendarGrid {
margin-top: 0.25rem;
width: 100%;
user-select: none;
border-collapse: collapse;
}
.CalendarGridRow {
display: grid;
margin-bottom: 0.25rem;
grid-template-columns: repeat(4, minmax(0, 1fr));
width: 100%;
}
.CalendarGridRow[data-reka-calendar-month-view] {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.CalendarHeadCell {
border-radius: 0.375rem;
font-size: 0.75rem;
line-height: 1rem;
color: #000000;
font-weight: 400;
}
.CalendarCell {
position: relative;
font-size: 0.875rem;
line-height: 1.25rem;
text-align: center;
}
.CalendarCellTrigger {
display: flex;
position: relative;
padding: 0.5rem;
justify-content: center;
align-items: center;
border-width: 1px;
border-color: transparent;
outline-style: none;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 400;
color: #000000;
white-space: nowrap;
background-color: transparent;
}
.CalendarCellTrigger:hover {
border-color: #000000;
}
.CalendarCellTrigger:focus {
box-shadow: 0 0 0 2px #000000;
}
.CalendarCellTrigger[data-disabled] {
cursor: none;
color: rgba(0, 0, 0, 0.3);
}
.CalendarCellTrigger[data-selected] {
background-color: #000000;
color: #ffffff;
font-weight: 500;
}
.CalendarCellTrigger[data-selected]::before {
background-color: #ffffff;
}
.CalendarCellTrigger[data-unavailable] {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
.CalendarCellTrigger::before {
content: '';
position: absolute;
top: 5px;
left: 0;
width: 0.25rem;
height: 0.25rem;
border-radius: 9999px;
background-color: #ffffff;
}
.CalendarCellTrigger[data-today]::before {
display: block;
background-color: var(--grass-9);
}
}Features
- 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.
Installation
Install the date package.
pnpm add @internationalized/date
yarn add @internationalized/date
npm install @internationalized/date
bun add @internationalized/date
Install the component from your command line.
pnpm add akar
yarn add akar
npm install akar
bun add akar
Anatomy
Import all parts and piece them together.
<script setup>
import {
CalendarCell,
CalendarCellTrigger,
CalendarGrid,
CalendarGridBody,
CalendarGridHead,
CalendarGridRow,
CalendarHeadCell,
CalendarHeader,
CalendarHeading,
CalendarNext,
CalendarPrev,
CalendarRoot,
} from 'akar';
</script>
<template>
<CalendarRoot>
<CalendarHeader>
<CalendarPrev />
<CalendarHeading />
<CalendarNext />
</CalendarHeader>
<CalendarGrid>
<CalendarGridHead>
<CalendarGridRow>
<CalendarHeadCell />
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody>
<CalendarGridRow>
<CalendarCell>
<CalendarCellTrigger />
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</CalendarRoot>
</template>
API Reference
Root
Contains all the parts of a calendar
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | 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. If omitted, inherits globally from | |
disabled | false | booleanWhether the calendar is disabled |
disableDaysOutsideCurrentView | false | booleanWhether or not to disable days outside the current view. |
fixedWeeks | false | booleanWhether or not to always display 6 weeks in the calendar |
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 | MatcherA function that returns whether or not a date is disabled | |
isDateUnavailable | MatcherA function that returns whether or not a date is unavailable | |
locale | stringThe locale to use for formatting dates | |
maxValue | DateValueThe maximum date that can be selected | |
minValue | DateValueThe minimum date that can be selected | |
modelValue | DateValue | DateValue[] | nullThe controlled selected date value of the calendar. Can be bound as | |
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" | "short" | "long"The format to use for the weekday strings provided via the weekdays slot prop |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6The day of the week to start the calendar on |
| Event | Payload |
|---|---|
update:modelValue | date: DateValueEvent handler called whenever the model value changes |
update:placeholder | date: DateValueEvent handler called whenever the placeholder value changes |
| Slot (default) | Payload |
|---|---|
date | DateValueThe current date of the placeholder |
grid | Grid<DateValue>[]The grid of dates |
weekDays | string[]The days of the week |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6The start of the week |
locale | stringThe calendar locale |
fixedWeeks | booleanWhether or not to always display 6 weeks in the calendar |
modelValue | DateValue | DateValue[]The current date of the calendar |
| Data attribute | Value |
|---|---|
[data-readonly] | Present when readonly |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
Header
Contains the navigation buttons and the heading segments.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | 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. |
Prev Button
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" | AsTag | 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 (default) | Payload |
|---|---|
disabled | booleanCurrent disable state |
| Data 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.
| Prop | Default | Type |
|---|---|---|
as | "button" | AsTag | 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 (default) | Payload |
|---|---|
disabled | booleanCurrent disable state |
| Data attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
Heading
Heading for displaying the current month and year
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | 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 (default) | Payload |
|---|---|
headingValue | stringCurrent month and year |
| Data attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
Grid
Container for wrapping the calendar grid.
| Prop | Default | Type |
|---|---|---|
as | "table" | AsTag | 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. |
| Data attribute | Value |
|---|---|
[data-readonly] | Present when readonly |
[data-disabled] | Present when disabled |
Grid Head
Container for wrapping the grid head.
| Prop | Default | Type |
|---|---|---|
as | "thead" | AsTag | 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. |
Grid Body
Container for wrapping the grid body.
| Prop | Default | Type |
|---|---|---|
as | "tbody" | AsTag | 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. |
Grid Row
Container for wrapping the grid row.
| Prop | Default | Type |
|---|---|---|
as | "tr" | AsTag | 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. |
Head Cell
Container for wrapping the head cell. Used for displaying the week days.
| Prop | Default | Type |
|---|---|---|
as | "th" | AsTag | 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. |
Cell
Container for wrapping the calendar cells.
| Prop | Default | Type |
|---|---|---|
as | "td" | AsTag | 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 date value for the cell |
| Data attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
Cell Trigger
Interactable container for displaying the cell dates. Clicking it selects the date.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | 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 (default) | Payload |
|---|---|
dayValue | stringCurrent day |
disabled | booleanCurrent disable state |
selected | booleanCurrent selected state |
today | booleanCurrent today state |
outsideView | booleanCurrent outside view state |
outsideVisibleView | booleanCurrent outside visible view state |
unavailable | booleanCurrent unavailable state |
| Data 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.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 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 |
<script setup lang="ts">
import type { DateValue } from '@internationalized/date';
import type { CalendarRootProps } from 'akar';
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNext, CalendarPrev, CalendarRoot } from 'akar';
import './styles.css';
const isDateUnavailable: CalendarRootProps['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>
<div class="akar-demo akar-demo-CalendarYearIncrement">
<CalendarRoot
v-slot="{ weekDays, grid }"
:is-date-unavailable="isDateUnavailable"
class="Calendar"
fixed-weeks
>
<CalendarHeader class="CalendarHeader">
<CalendarPrev
class="CalendarNavButton"
:prev-page="(date: DateValue) => pagingFunc(date, -1)"
>
<PIcon
name="i-lucide-chevrons-left"
class="Icon"
/>
</CalendarPrev>
<CalendarPrev
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-left"
class="Icon"
/>
</CalendarPrev>
<CalendarHeading class="CalendarHeading" />
<CalendarNext
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-right"
class="Icon"
/>
</CalendarNext>
<CalendarNext
class="CalendarNavButton"
:next-page="(date: DateValue) => pagingFunc(date, 1)"
>
<PIcon
name="i-lucide-chevrons-right"
class="Icon"
/>
</CalendarNext>
</CalendarHeader>
<div
class="CalendarWrapper"
>
<CalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="CalendarGrid"
>
<CalendarGridHead>
<CalendarGridRow class="CalendarGridRow">
<CalendarHeadCell
v-for="day in weekDays"
:key="day"
class="CalendarHeadCell"
>
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody class="CalendarGridWrapper">
<CalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="CalendarGridRow"
>
<CalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="CalendarCell"
>
<CalendarCellTrigger
:day="weekDate"
:month="month.value"
class="CalendarCellTrigger"
/>
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</div>
</CalendarRoot>
</div>
</template>@import '../_colors/black-alpha.css';
@import '../_colors/grass.css';
@scope (.akar-demo-CalendarYearIncrement) {
.Icon {
width: 1.5rem;
height: 1.5rem;
}
.Calendar {
margin-top: 1.5rem;
border-width: 1px;
border-color: #000000;
background-color: #ffffff;
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 22px;
}
.CalendarHeader {
display: flex;
justify-content: space-between;
align-items: center;
}
.CalendarNavButton {
display: inline-flex;
justify-content: center;
align-items: center;
width: 2.5rem;
height: 2.5rem;
color: #000000;
background-color: transparent;
cursor: pointer;
}
.CalendarNavButton:hover {
color: #ffffff;
background-color: #000000;
}
.CalendarHeading {
font-weight: 500;
color: #000000;
color: 15px;
}
.CalendarWrapper {
display: flex;
padding-top: 1rem;
margin-top: 1rem;
flex-direction: column;
}
@media (min-width: 640px) {
.CalendarWrapper {
margin-left: 1rem;
margin-top: 0;
flex-direction: row;
}
}
.CalendarGrid {
margin-top: 0.25rem;
width: 100%;
user-select: none;
border-collapse: collapse;
}
.CalendarGridRow {
display: grid;
margin-bottom: 0.25rem;
grid-template-columns: repeat(4, minmax(0, 1fr));
width: 100%;
}
.CalendarGridRow[data-reka-calendar-month-view] {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.CalendarHeadCell {
border-radius: 0.375rem;
font-size: 0.75rem;
line-height: 1rem;
color: #000000;
font-weight: 400;
}
.CalendarCell {
position: relative;
font-size: 0.875rem;
line-height: 1.25rem;
text-align: center;
}
.CalendarCellTrigger {
display: flex;
position: relative;
padding: 0.5rem;
justify-content: center;
align-items: center;
border-width: 1px;
border-color: transparent;
outline-style: none;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 400;
color: #000000;
white-space: nowrap;
background-color: transparent;
}
.CalendarCellTrigger:hover {
border-color: #000000;
}
.CalendarCellTrigger:focus {
box-shadow: 0 0 0 2px #000000;
}
.CalendarCellTrigger[data-disabled] {
cursor: none;
color: rgba(0, 0, 0, 0.3);
}
.CalendarCellTrigger[data-selected] {
background-color: #000000;
color: #ffffff;
font-weight: 500;
}
.CalendarCellTrigger[data-selected]::before {
background-color: #ffffff;
}
.CalendarCellTrigger[data-unavailable] {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
.CalendarCellTrigger::before {
content: '';
position: absolute;
top: 5px;
left: 0;
width: 0.25rem;
height: 0.25rem;
border-radius: 9999px;
background-color: #ffffff;
}
.CalendarCellTrigger[data-today]::before {
display: block;
background-color: var(--grass-9);
}
/* reset */
button {
all: unset;
}
.Wrapper {
display: flex;
flex-direction: column;
gap: 1rem;
}
.Label {
color: #fff;
}
.SelectTrigger {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
padding: 0 15px;
font-size: 13px;
line-height: 1;
height: 35px;
gap: 5px;
background-color: white;
color: var(--grass-11);
box-shadow: 0 2px 10px var(--black-a7);
}
.SelectTrigger:hover {
background-color: var(--mauve-3);
}
.SelectTrigger:focus {
box-shadow: 0 0 0 2px black;
}
.SelectTrigger[data-placeholder] {
color: var(--grass-9);
}
.SelectIcon {
color: Var(--grass-11);
}
.SelectContent {
overflow: hidden;
background-color: white;
border-radius: 6px;
box-shadow:
0px 10px 38px -10px rgba(22, 23, 24, 0.35),
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
}
.SelectViewport {
padding: 5px;
}
.SelectItem {
font-size: 13px;
line-height: 1;
color: var(--grass-11);
border-radius: 3px;
display: flex;
align-items: center;
height: 25px;
padding: 0 35px 0 25px;
position: relative;
user-select: none;
}
.SelectItem[data-disabled] {
color: var(--mauve-8);
pointer-events: none;
}
.SelectItem[data-highlighted] {
outline: none;
background-color: var(--grass-9);
color: var(--grass-1);
}
.SelectLabel {
padding: 0 25px;
font-size: 12px;
line-height: 25px;
color: var(--mauve-11);
}
.SelectSeparator {
height: 1px;
background-color: var(--grass-6);
margin: 5px;
}
.SelectItemIndicator {
position: absolute;
left: 0;
width: 25px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.SelectScrollButton {
display: flex;
align-items: center;
justify-content: center;
height: 25px;
background-color: white;
color: var(--grass-11);
cursor: default;
}
}Calendar with Locale and Calendar System Selection
This example showcases some of the available locales and how the calendar systems are displayed.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 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 |
<script setup lang="ts">
import { createCalendar, getLocalTimeZone, toCalendar, today } from '@internationalized/date';
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNext, CalendarPrev, CalendarRoot, Label, SelectContent, SelectGroup, SelectItem, SelectItemIndicator, SelectItemText, SelectLabel, SelectPortal, SelectRoot, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SelectViewport } 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="akar-demo akar-demo-CalendarSelect">
<div class="Wrapper">
<Label class="Label">Locale</Label>
<SelectRoot
v-model="locale"
@update:model-value="updateLocale"
>
<SelectTrigger
class="SelectTrigger"
aria-label="Select a locale"
>
<SelectValue placeholder="Please select a locale">
{{ pref!.label }}
</SelectValue>
<PIcon
name="i-lucide-chevron-down"
class="Icon"
/>
</SelectTrigger>
<SelectPortal>
<SelectContent
class="SelectContent"
:side-offset="5"
>
<SelectScrollUpButton class="SelectScrollButton">
<PIcon name="i-lucide-chevron-up" />
</SelectScrollUpButton>
<SelectViewport class="SelectViewport">
<SelectItem
v-for="(option, index) in preferences"
:key="index"
class="SelectItem"
:value="option.locale"
>
<SelectItemIndicator class="SelectItemIndicator">
<PIcon name="i-lucide-check" />
</SelectItemIndicator>
<SelectItemText>
{{ option.label }}
</SelectItemText>
</SelectItem>
</SelectViewport>
<SelectScrollDownButton class="SelectScrollButton">
<PIcon name="i-lucide-chevron-down" />
</SelectScrollDownButton>
</SelectContent>
</SelectPortal>
</SelectRoot>
<Label class="Label">Calendar</Label>
<SelectRoot v-model="calendar">
<SelectTrigger
class="SelectTrigger"
aria-label="Select a calendar"
>
<SelectValue placeholder="Please select a calendar">
{{ calendars.find(c => c.key === calendar)?.name }}
</SelectValue>
<PIcon
name="i-lucide-chevron-down"
class="Icon"
/>
</SelectTrigger>
<SelectPortal>
<SelectContent
class="SelectContent"
:side-offset="5"
>
<SelectScrollUpButton class="SelectScrollButton">
<PIcon name="i-lucide-chevron-up" />
</SelectScrollUpButton>
<SelectViewport class="SelectViewport">
<SelectLabel class="SelectLabel">
Preferred
</SelectLabel>
<SelectGroup>
<SelectItem
v-for="(option, index) in preferredCalendars"
:key="index"
class="SelectItem"
:value="option!.key"
>
<SelectItemIndicator class="SelectItemIndicator">
<PIcon name="i-lucide-check" />
</SelectItemIndicator>
<SelectItemText>
{{ option!.name }}
</SelectItemText>
</SelectItem>
</SelectGroup>
<SelectSeparator class="SelectSeparator" />
<SelectLabel class="SelectLabel">
Other
</SelectLabel>
<SelectGroup>
<SelectItem
v-for="(option, index) in otherCalendars"
:key="index"
class="SelectItem"
:value="option.key"
>
<SelectItemIndicator class="SelectItemIndicator">
<PIcon name="i-lucide-check" />
</SelectItemIndicator>
<SelectItemText>
{{ option.name }}
</SelectItemText>
</SelectItem>
</SelectGroup>
</SelectViewport>
<SelectScrollDownButton class="SelectScrollButton">
<PIcon name="i-lucide-chevron-down" />
</SelectScrollDownButton>
</SelectContent>
</SelectPortal>
</SelectRoot>
<CalendarRoot
v-slot="{ weekDays, grid }"
:model-value="value"
:locale="locale"
class="Calendar"
fixed-weeks
>
<CalendarHeader class="CalendarHeader">
<CalendarPrev
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-left"
class="Icon"
/>
</CalendarPrev>
<CalendarHeading class="CalendarHeading" />
<CalendarNext
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-right"
class="Icon"
/>
</CalendarNext>
</CalendarHeader>
<div
class="CalendarWrapper"
>
<CalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="CalendarGrid"
>
<CalendarGridHead>
<CalendarGridRow class="CalendarGridRow">
<CalendarHeadCell
v-for="day in weekDays"
:key="day"
class="CalendarHeadCell"
>
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody class="CalendarGridWrapper">
<CalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="CalendarGridRow"
>
<CalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="CalendarCell"
>
<CalendarCellTrigger
:day="weekDate"
:month="month.value"
class="CalendarCellTrigger"
/>
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</div>
</CalendarRoot>
</div>
</div>
</template>@import '../_colors/black-alpha.css';
@import '../_colors/grass.css';
@scope (.akar-demo-CalendarSelect) {
.Icon {
width: 1.5rem;
height: 1.5rem;
}
.Calendar {
margin-top: 1.5rem;
border-width: 1px;
border-color: #000000;
background-color: #ffffff;
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 22px;
}
.CalendarHeader {
display: flex;
justify-content: space-between;
align-items: center;
}
.CalendarNavButton {
display: inline-flex;
justify-content: center;
align-items: center;
width: 2.5rem;
height: 2.5rem;
color: #000000;
background-color: transparent;
cursor: pointer;
}
.CalendarNavButton:hover {
color: #ffffff;
background-color: #000000;
}
.CalendarHeading {
font-weight: 500;
color: #000000;
color: 15px;
}
.CalendarWrapper {
display: flex;
padding-top: 1rem;
margin-top: 1rem;
flex-direction: column;
}
@media (min-width: 640px) {
.CalendarWrapper {
margin-left: 1rem;
margin-top: 0;
flex-direction: row;
}
}
.CalendarGrid {
margin-top: 0.25rem;
width: 100%;
user-select: none;
border-collapse: collapse;
}
.CalendarGridRow {
display: grid;
margin-bottom: 0.25rem;
grid-template-columns: repeat(4, minmax(0, 1fr));
width: 100%;
}
.CalendarGridRow[data-reka-calendar-month-view] {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.CalendarHeadCell {
border-radius: 0.375rem;
font-size: 0.75rem;
line-height: 1rem;
color: #000000;
font-weight: 400;
}
.CalendarCell {
position: relative;
font-size: 0.875rem;
line-height: 1.25rem;
text-align: center;
}
.CalendarCellTrigger {
display: flex;
position: relative;
padding: 0.5rem;
justify-content: center;
align-items: center;
border-width: 1px;
border-color: transparent;
outline-style: none;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 400;
color: #000000;
white-space: nowrap;
background-color: transparent;
}
.CalendarCellTrigger:hover {
border-color: #000000;
}
.CalendarCellTrigger:focus {
box-shadow: 0 0 0 2px #000000;
}
.CalendarCellTrigger[data-disabled] {
cursor: none;
color: rgba(0, 0, 0, 0.3);
}
.CalendarCellTrigger[data-selected] {
background-color: #000000;
color: #ffffff;
font-weight: 500;
}
.CalendarCellTrigger[data-selected]::before {
background-color: #ffffff;
}
.CalendarCellTrigger[data-unavailable] {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
.CalendarCellTrigger::before {
content: '';
position: absolute;
top: 5px;
left: 0;
width: 0.25rem;
height: 0.25rem;
border-radius: 9999px;
background-color: #ffffff;
}
.CalendarCellTrigger[data-today]::before {
display: block;
background-color: var(--grass-9);
}
/* reset */
button {
all: unset;
}
.Wrapper {
display: flex;
flex-direction: column;
gap: 1rem;
}
.Label {
color: #fff;
}
.SelectTrigger {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
padding: 0 15px;
font-size: 13px;
line-height: 1;
height: 35px;
gap: 5px;
background-color: white;
color: var(--grass-11);
box-shadow: 0 2px 10px var(--black-a7);
}
.SelectTrigger:hover {
background-color: var(--mauve-3);
}
.SelectTrigger:focus {
box-shadow: 0 0 0 2px black;
}
.SelectTrigger[data-placeholder] {
color: var(--grass-9);
}
.SelectIcon {
color: Var(--grass-11);
}
.SelectContent {
overflow: hidden;
background-color: white;
border-radius: 6px;
box-shadow:
0px 10px 38px -10px rgba(22, 23, 24, 0.35),
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
}
.SelectViewport {
padding: 5px;
}
.SelectItem {
font-size: 13px;
line-height: 1;
color: var(--grass-11);
border-radius: 3px;
display: flex;
align-items: center;
height: 25px;
padding: 0 35px 0 25px;
position: relative;
user-select: none;
}
.SelectItem[data-disabled] {
color: var(--mauve-8);
pointer-events: none;
}
.SelectItem[data-highlighted] {
outline: none;
background-color: var(--grass-9);
color: var(--grass-1);
}
.SelectLabel {
padding: 0 25px;
font-size: 12px;
line-height: 25px;
color: var(--mauve-11);
}
.SelectSeparator {
height: 1px;
background-color: var(--grass-6);
margin: 5px;
}
.SelectItemIndicator {
position: absolute;
left: 0;
width: 25px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.SelectScrollButton {
display: flex;
align-items: center;
justify-content: center;
height: 25px;
background-color: white;
color: var(--grass-11);
cursor: default;
}
}Calendar swipe gesture navigation
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 { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNext, CalendarPrev, CalendarRoot } from 'akar';
import { onMounted, ref, useTemplateRef } from 'vue';
import './styles.css';
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>
<div class="akar-demo akar-demo-CalendarSwipe">
<CalendarRoot
ref="calendarRef"
v-slot="{ weekDays, grid }"
v-model:placeholder="date"
class="Calendar"
fixed-weeks
>
<CalendarHeader class="CalendarHeader">
<CalendarPrev
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-left"
class="Icon"
/>
</CalendarPrev>
<CalendarHeading class="CalendarHeading" />
<CalendarNext
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-right"
class="Icon"
/>
</CalendarNext>
</CalendarHeader>
<div
class="CalendarWrapper"
>
<CalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="CalendarGrid"
>
<CalendarGridHead>
<CalendarGridRow class="CalendarGridRow">
<CalendarHeadCell
v-for="day in weekDays"
:key="day"
class="CalendarHeadCell"
>
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody class="CalendarGridWrapper">
<CalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="CalendarGridRow"
>
<CalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="CalendarCell"
>
<CalendarCellTrigger
:day="weekDate"
:month="month.value"
class="CalendarCellTrigger"
/>
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</div>
</CalendarRoot>
</div>
</template>@import '../_colors/black-alpha.css';
@import '../_colors/grass.css';
@scope (.akar-demo-CalendarSwipe) {
.Icon {
width: 1.5rem;
height: 1.5rem;
}
.Calendar {
margin-top: 1.5rem;
border-width: 1px;
border-color: #000000;
background-color: #ffffff;
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 22px;
}
.CalendarHeader {
display: flex;
justify-content: space-between;
align-items: center;
}
.CalendarNavButton {
display: inline-flex;
justify-content: center;
align-items: center;
width: 2.5rem;
height: 2.5rem;
color: #000000;
background-color: transparent;
cursor: pointer;
}
.CalendarNavButton:hover {
color: #ffffff;
background-color: #000000;
}
.CalendarHeading {
font-weight: 500;
color: #000000;
}
.CalendarWrapper {
display: flex;
padding-top: 1rem;
margin-top: 1rem;
flex-direction: column;
}
@media (min-width: 640px) {
.CalendarWrapper {
margin-left: 1rem;
margin-top: 0;
flex-direction: row;
}
}
.CalendarGrid {
margin-top: 0.25rem;
width: 100%;
user-select: none;
border-collapse: collapse;
}
.CalendarGridRow {
display: grid;
margin-bottom: 0.25rem;
grid-template-columns: repeat(4, minmax(0, 1fr));
width: 100%;
}
.CalendarGridRow[data-reka-calendar-month-view] {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.CalendarHeadCell {
border-radius: 0.375rem;
font-size: 0.75rem;
line-height: 1rem;
color: #000000;
font-weight: 400;
}
.CalendarCell {
position: relative;
font-size: 0.875rem;
line-height: 1.25rem;
text-align: center;
}
.CalendarCellTrigger {
display: flex;
position: relative;
padding: 0.5rem;
justify-content: center;
align-items: center;
border-width: 1px;
border-color: transparent;
outline-style: none;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 400;
color: #000000;
white-space: nowrap;
background-color: transparent;
}
.CalendarCellTrigger:hover {
border-color: #000000;
}
.CalendarCellTrigger:focus {
box-shadow: 0 0 0 2px #000000;
}
.CalendarCellTrigger[data-disabled] {
cursor: none;
color: rgba(0, 0, 0, 0.3);
}
.CalendarCellTrigger[data-selected] {
background-color: #000000;
color: #ffffff;
font-weight: 500;
}
.CalendarCellTrigger[data-selected]::before {
background-color: #ffffff;
}
.CalendarCellTrigger[data-unavailable] {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
.CalendarCellTrigger::before {
content: '';
position: absolute;
top: 5px;
left: 0;
width: 0.25rem;
height: 0.25rem;
border-radius: 9999px;
background-color: #ffffff;
}
.CalendarCellTrigger[data-today]::before {
display: block;
background-color: var(--grass-9);
}
}Calendar week numbers
This example showcases usage of the CalendarWeek component used to display the number of the week.
| Wk | S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|---|
27 | 28 | 29 | 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 |
<script setup lang="ts">
import { CalendarCell, CalendarCellTrigger, CalendarGrid, CalendarGridBody, CalendarGridHead, CalendarGridRow, CalendarHeadCell, CalendarHeader, CalendarHeading, CalendarNext, CalendarPrev, CalendarRoot } from 'akar';
import { getWeekNumber } from 'akar/date';
</script>
<template>
<div class="akar-demo akar-demo-CalendarWeeks">
<CalendarRoot
v-slot="{ weekDays, grid }"
class="Calendar"
fixed-weeks
>
<CalendarHeader class="CalendarHeader">
<CalendarPrev
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-left"
class="Icon"
/>
</CalendarPrev>
<CalendarHeading class="CalendarHeading" />
<CalendarNext
class="CalendarNavButton"
>
<PIcon
name="i-lucide-chevron-right"
class="Icon"
/>
</CalendarNext>
</CalendarHeader>
<div
class="CalendarWrapper"
>
<CalendarGrid
v-for="month in grid"
:key="month.value.toString()"
class="CalendarGrid"
>
<CalendarGridHead>
<CalendarGridRow class="CalendarGridRow">
<CalendarHeadCell
class="CalendarHeadCell"
>
Wk
</CalendarHeadCell>
<CalendarHeadCell
v-for="day in weekDays"
:key="day"
class="CalendarHeadCell"
>
{{ day }}
</CalendarHeadCell>
</CalendarGridRow>
</CalendarGridHead>
<CalendarGridBody class="CalendarGridWrapper">
<CalendarGridRow
v-for="(weekDates, index) in month.rows"
:key="`weekDate-${index}`"
class="CalendarGridRow"
>
<div
class="CalendarWeek"
:row-index="index"
>
{{ getWeekNumber(weekDates[0]) }}
</div>
<CalendarCell
v-for="weekDate in weekDates"
:key="weekDate.toString()"
:date="weekDate"
class="CalendarCell"
>
<CalendarCellTrigger
:day="weekDate"
:month="month.value"
class="CalendarCellTrigger"
/>
</CalendarCell>
</CalendarGridRow>
</CalendarGridBody>
</CalendarGrid>
</div>
</CalendarRoot>
</div>
</template>@import '../_colors/black-alpha.css';
@import '../_colors/grass.css';
@scope (.akar-demo-CalendarWeeks) {
.Icon {
width: 1.5rem;
height: 1.5rem;
}
.Calendar {
margin-top: 1.5rem;
border-width: 1px;
border-color: #000000;
background-color: #ffffff;
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 22px;
}
.CalendarHeader {
display: flex;
justify-content: space-between;
align-items: center;
}
.CalendarNavButton {
display: inline-flex;
justify-content: center;
align-items: center;
width: 2.5rem;
height: 2.5rem;
color: #000000;
background-color: transparent;
cursor: pointer;
}
.CalendarNavButton:hover {
color: #ffffff;
background-color: #000000;
}
.CalendarHeading {
font-weight: 500;
color: #000000;
color: 15px;
}
.CalendarWrapper {
display: flex;
padding-top: 1rem;
margin-top: 1rem;
flex-direction: column;
}
@media (min-width: 640px) {
.CalendarWrapper {
margin-left: 1rem;
margin-top: 0;
flex-direction: row;
}
}
.CalendarGrid {
margin-top: 0.25rem;
width: 100%;
user-select: none;
border-collapse: collapse;
}
.CalendarGridRow {
display: grid;
margin-bottom: 0.25rem;
grid-template-columns: repeat(4, minmax(0, 1fr));
width: 100%;
}
.CalendarGridRow[data-reka-calendar-month-view] {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.CalendarHeadCell {
border-radius: 0.375rem;
font-size: 0.75rem;
line-height: 1rem;
color: #000000;
font-weight: 400;
}
.CalendarWeek {
display: flex;
justify-content: center;
align-items: center;
font-size: 0.875rem;
line-height: 1.25rem;
}
.CalendarCell {
position: relative;
font-size: 0.875rem;
line-height: 1.25rem;
text-align: center;
}
.CalendarCellTrigger {
display: flex;
position: relative;
padding: 0.5rem;
justify-content: center;
align-items: center;
border-width: 1px;
border-color: transparent;
outline-style: none;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 400;
color: #000000;
white-space: nowrap;
background-color: transparent;
}
.CalendarCellTrigger:hover {
border-color: #000000;
}
.CalendarCellTrigger:focus {
box-shadow: 0 0 0 2px #000000;
}
.CalendarCellTrigger[data-disabled] {
cursor: none;
color: rgba(0, 0, 0, 0.3);
}
.CalendarCellTrigger[data-selected] {
background-color: #000000;
color: #ffffff;
font-weight: 500;
}
.CalendarCellTrigger[data-selected]::before {
background-color: #ffffff;
}
.CalendarCellTrigger[data-unavailable] {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
.CalendarCellTrigger::before {
content: '';
position: absolute;
top: 5px;
left: 0;
width: 0.25rem;
height: 0.25rem;
border-radius: 9999px;
background-color: #ffffff;
}
.CalendarCellTrigger[data-today]::before {
display: block;
background-color: var(--grass-9);
}
/* reset */
button {
all: unset;
}
}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. |
| ArrowLeft/ArrowRight/ArrowUp/ArrowDown | When the focus is on CalendarCellTrigger, it navigates the dates, changing the month/year/decade if necessary. |
| Home | When the focus is on CalendarCellTrigger, it moves focus to the first day of the current week. |
| End | When the focus is on CalendarCellTrigger, it moves focus to the last day of the current week. |
| PageUp | When the focus is on CalendarCellTrigger, it moves to the previous month while preserving the day of the month when possible, or clamps to the last day of the month. |
| PageDown | When the focus is on CalendarCellTrigger, it moves to the next month while preserving the day of the month when possible, or clamps to the last day of the month. |
| Shift+PageUp | When the focus is on CalendarCellTrigger, it moves to the previous year while preserving the month and day when possible, or clamps to the last day of the month. |
| Shift+PageDown | When the focus is on CalendarCellTrigger, it moves to the next year while preserving the month and day when possible, or clamps to the last day of the month. |