Skip to content
Dethink Components

ComponentsCalendar

Calendar

Let users choose a date or date range from a month view.

On this page

Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.

View registry files
  • Internationalized Date (@internationalized/date)

    Date values, calendar arithmetic, and time-zone-aware date handling.

  • React Aria Components (react-aria-components)

    Accessible interaction primitives, including keyboard behavior, focus handling, and ARIA semantics.

Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.

Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.

Import the component into your page or component file.

Usage
import { Calendar, RangeCalendar } from "@dethink/components";
import { CalendarDate } from "@internationalized/date";

Try the examples, then open the code to use them in your app. Move focus into a grid and navigate with the arrow keys, PageUp/PageDown for months, and Enter or Space to select.

Single date

A controlled Calendar — value and onValueChange pair with React state holding a CalendarDate.

Launch date, July 2026

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

Selected: 2026-07-14

Show sourceexamples/calendar/basic.tsx
examples/calendar/basic.tsx
"use client";

import { useState } from "react";
import { Calendar } from "@dethink/components";
import { CalendarDate, type DateValue } from "@internationalized/date";

export function CalendarBasic() {
  const [value, setValue] = useState<DateValue | null>(
    new CalendarDate(2026, 7, 14),
  );

  return (
    <div className="flex flex-col items-center gap-3">
      <Calendar
        aria-label="Launch date"
        value={value}
        onValueChange={setValue}
      />
      <p className="text-muted-foreground text-sm">
        Selected: {value ? value.toString() : "none"}
      </p>
    </div>
  );
}

Range

RangeCalendar selects a start and end date in one grid; the value is a { start, end } pair.

Sprint dates, July 2026

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
Show sourceexamples/calendar/range.tsx
examples/calendar/range.tsx
"use client";

import { RangeCalendar } from "@dethink/components";
import { CalendarDate } from "@internationalized/date";

export function CalendarRange() {
  return (
    <div className="flex justify-center">
      <RangeCalendar
        aria-label="Sprint dates"
        defaultValue={{
          start: new CalendarDate(2026, 7, 6),
          end: new CalendarDate(2026, 7, 17),
        }}
      />
    </div>
  );
}

Bounds and unavailable dates

minValue and maxValue clamp selection to a window, while isDateUnavailable strikes out individual dates — here, weekends.

Delivery slot, July 2026

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
Show sourceexamples/calendar/bounds.tsx
examples/calendar/bounds.tsx
"use client";

import { Calendar } from "@dethink/components";
import { CalendarDate, getDayOfWeek } from "@internationalized/date";

const minValue = new CalendarDate(2026, 7, 6);
const maxValue = new CalendarDate(2026, 7, 31);

export function CalendarBounds() {
  return (
    <div className="flex justify-center">
      <Calendar
        aria-label="Delivery slot"
        defaultFocusedValue={minValue}
        minValue={minValue}
        maxValue={maxValue}
        // Weekends are unavailable for delivery.
        isDateUnavailable={(date) => {
          const dayOfWeek = getDayOfWeek(date, "en-US");
          return dayOfWeek === 0 || dayOfWeek === 6;
        }}
      />
    </div>
  );
}

Locale and week start

locale drives month and weekday formatting, weekStartsOn overrides the first day of the week, and weekdayStyle switches the header format.

Date (German), Juli 2026

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

Date (Japanese), 2026年7月

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
Show sourceexamples/calendar/locale.tsx
examples/calendar/locale.tsx
"use client";

import { Calendar } from "@dethink/components";
import { CalendarDate } from "@internationalized/date";

export function CalendarLocale() {
  return (
    <div className="flex flex-wrap items-start justify-center gap-6">
      <Calendar
        aria-label="Date (German)"
        locale="de-DE"
        weekStartsOn="mon"
        defaultValue={new CalendarDate(2026, 7, 14)}
      />
      <Calendar
        aria-label="Date (Japanese)"
        locale="ja-JP"
        weekdayStyle="narrow"
        defaultValue={new CalendarDate(2026, 7, 14)}
      />
    </div>
  );
}

Calendar and RangeCalendar share the same API; RangeCalendar's value shape is { start, end }. Always give each calendar an aria-label (or aria-labelledby) describing what is being picked.

Calendar and RangeCalendar props
PropWhat it doesDefault
valueDateValue | nullControlled selected date, built with @internationalized/date (for RangeCalendar: { start, end }).Not set
defaultValueDateValue | nullThe starting selection when the component manages its own state.Not set
onValueChange(value) => voidFires with the new date (or range) whenever the selection changes.Not set
minValue / maxValueDateValueInclusive bounds; dates outside them cannot be selected.Not set
isDateUnavailable(date: DateValue) => booleanMarks individual dates unavailable — rendered struck through and unselectable.Not set
disabledbooleanDisables the whole calendar.false
invalidbooleanMarks the current selection invalid for form validation.false
isReadOnlybooleanSelection is visible and focusable but cannot change (react-aria passthrough).false
localestringBCP 47 tag (for example "de-DE") controlling month/weekday formatting.runtime locale
weekStartsOn"sun" | "mon" | … | "sat"First day of the week, overriding the locale.locale default
weekdayStyle"narrow" | "short" | "long"Weekday header format."short"