# Calendar

An accessible React calendar for Next.js and Tailwind CSS: pick a day or a range, with full keyboard support, disabled days and Intl names. No date library.

Source: https://ui.fewtone.com/components/calendar

## Installation

```bash
npx shadcn@latest add https://ui.fewtone.com/r/calendar.json
```

## Usage

```tsx
"use client"

import { useState } from "react"
import { Calendar } from "@/components/ui/calendar"

export function DueDate() {
  const [date, setDate] = useState<Date | undefined>()

  return <Calendar mode="single" selected={date} onSelect={setDate} />
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | `"single" \| "range"` | `"single"` | Pick one day, or a range from a start to an end. |
| `selected` | `Date \| DateRange` | – | The picked day (single) or range ({ from, to }), when you control it. |
| `defaultSelected` | `Date \| DateRange` | – | What's picked at first. |
| `onSelect` | `(value: Date \| DateRange \| undefined, day: Date) => void` | – | Called with the new day or range, and the day that was clicked. In range mode the first click starts a range, the second ends it (either way round) and a third starts again. |
| `month` | `Date` | – | The month shown first, when you control it. |
| `defaultMonth` | `Date` | – | The month shown at first. Defaults to the picked day's month, else today's. Pass it to server-render the right month. |
| `onMonthChange` | `(month: Date) => void` | – | Called with the first day of the month when the page turns. |
| `numberOfMonths` | `number` | `1` | Months shown side by side. |
| `disabled` | `Matcher \| Matcher[]` | – | Days that can't be picked: a Date, an array of Dates, { from, to }, { before }, { after }, { dayOfWeek: [0, 6] } or a function that gets each day. |
| `min` | `Date` | – | The earliest day that can be picked. The calendar doesn't page before its month. |
| `max` | `Date` | – | The latest day that can be picked. The calendar doesn't page past its month. |
| `weekStartsOn` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | `0` | The first day of the week: 0 is Sunday, 1 is Monday. |
| `locale` | `string` | `"en-US"` | A language tag, such as fr-FR. Intl writes the month, weekday and day names. |
| `showOutsideDays` | `boolean` | `true` | Shows the days of the months either side, muted. They can be picked. |
| `fixedWeeks` | `boolean` | `true` | Always six weeks, so the calendar keeps its height from month to month. Without it, the height glides to fit. |
| `today` | `Date` | – | The day marked as today. Defaults to the browser's today, known once the page has loaded, so server HTML never disagrees with it. |
| `required` | `boolean` | `false` | Single mode: clicking the picked day again keeps it instead of clearing it. |
| `labels` | `{ previousMonth?: string; nextMonth?: string; selected?: string }` | – | The words the calendar says: "Previous month", "Next month" and ", selected" after a picked day. |

**When to use it:** Use Calendar to show a month and pick a day or a range in place, for example a booking page or a filter panel. Use Date Picker for a form field that opens a calendar from a trigger, and Input with type=date when a typed date is enough.
**Built on:** Intl.DateTimeFormat.
**Accessibility pattern:** https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/
**npm packages (installed by the CLI):** lucide-react@^1.52.0, motion@^14.0.0.
**Installs these FewTone UI components with it:** `button`.

## Examples

### Single and range

Pick another day on the left and its circle travels there. On the right, pick a start and an end: the band grows from the first day to the second, row by row. Turning the page slides the new month in from the side you're going to.

```tsx
"use client";

import { Calendar, type Weekday } from "@/components/ui/calendar";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId, useState } from "react";

// Fixed days, so the server and the browser draw the same month
const october = new Date(2026, 9, 1);
const picked = new Date(2026, 9, 16);
const stay = { from: new Date(2026, 9, 12), to: new Date(2026, 9, 18) };

/*
 * Both modes side by side, named underneath, with the first day of the week in the top-right corner. Pick a
 * day on the left and the circle travels to it; pick two days on the right and the band grows between them.
 * An empty row at the bottom, as tall as the setting's row, keeps them centred.
 */
export default function CalendarModes() {
  const id = useId();
  const [weekStartsOn, setWeekStartsOn] = useState<Weekday>(0);

  return (
    <div className="flex w-full flex-col gap-6 self-stretch">
      <div className="-mt-6 -mr-6 flex items-center justify-end gap-2">
        <label htmlFor={`${id}-week`} className="text-sm text-muted-foreground">
          Week starts
        </label>
        <Select
          value={String(weekStartsOn)}
          onValueChange={(value) => setWeekStartsOn(Number(value) as Weekday)}
        >
          <SelectTrigger id={`${id}-week`} size="sm" className="w-28">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="0">Sunday</SelectItem>
            <SelectItem value="1">Monday</SelectItem>
          </SelectContent>
        </Select>
      </div>

      <ul className="flex flex-1 flex-wrap items-center justify-center gap-x-10 gap-y-6">
        <li className="flex flex-col items-center gap-2">
          <Calendar defaultMonth={october} defaultSelected={picked} weekStartsOn={weekStartsOn} />
          <span className="text-xs text-muted-foreground">Single</span>
        </li>
        <li className="flex flex-col items-center gap-2">
          <Calendar mode="range" defaultMonth={october} defaultSelected={stay} weekStartsOn={weekStartsOn} />
          <span className="text-xs text-muted-foreground">Range</span>
        </li>
      </ul>

      <div aria-hidden className="-mb-6 h-(--size-control-sm)" />
    </div>
  );
}
```

### Two months

numberOfMonths={2} shows two months side by side, for ranges that cross a month's end.

```tsx
"use client";

// A client module: Dates passed from a server component arrive as instants and can land on another day in
// the visitor's time zone, so the days are made here, in the browser's own
import { Calendar } from "@/components/ui/calendar";

const october = new Date(2026, 9, 1);
const trip = { from: new Date(2026, 9, 26), to: new Date(2026, 10, 6) };

/** Two months side by side, for a range that crosses the end of a month. The arrows turn both pages. */
export default function CalendarTwoMonths() {
  return <Calendar mode="range" numberOfMonths={2} defaultMonth={october} defaultSelected={trip} />;
}
```

### Disabled days and limits

Weekends are disabled with { dayOfWeek: [0, 6] }, and min and max keep the pick between 5 October and 20 November. The arrow keys skip what can't be picked, and the month buttons stop at the limits.

```tsx
"use client";

// A client module: Dates passed from a server component arrive as instants and can land on another day in
// the visitor's time zone, so the days are made here, in the browser's own
import { Calendar } from "@/components/ui/calendar";

const october = new Date(2026, 9, 1);

/**
 * Weekends can't be picked, and only 5 October to 20 November can. The arrow keys skip the disabled days,
 * and the month buttons stop at the limits.
 */
export default function CalendarDisabled() {
  return (
    <Calendar
      defaultMonth={october}
      disabled={{ dayOfWeek: [0, 6] }}
      min={new Date(2026, 9, 5)}
      max={new Date(2026, 10, 20)}
    />
  );
}
```

### Other languages

locale names the months, weekdays and days through Intl, weekStartsOn sets the first column, and labels translates the few words the calendar says itself.

```tsx
"use client";

// A client module: Dates passed from a server component arrive as instants and can land on another day in
// the visitor's time zone, so the days are made here, in the browser's own
import { Calendar } from "@/components/ui/calendar";

const october = new Date(2026, 9, 1);

/**
 * In French, with weeks starting on Monday. Intl names the months, weekdays and days, so any locale works;
 * the few words the calendar says itself come from `labels`.
 */
export default function CalendarLocale() {
  return (
    <Calendar
      locale="fr-FR"
      weekStartsOn={1}
      defaultMonth={october}
      defaultSelected={new Date(2026, 9, 16)}
      labels={{ previousMonth: "Mois précédent", nextMonth: "Mois suivant", selected: "sélectionné" }}
    />
  );
}
```

## Related

- [Date Picker](https://ui.fewtone.com/components/date-picker.md): An accessible React date picker for Next.js and Tailwind CSS: a calendar in a popover for a date or a date range, with presets and Intl formatting.
- [Popover](https://ui.fewtone.com/components/popover.md): An accessible React popover for Next.js and Tailwind CSS: forms, settings and other rich content anchored to a button, built on Radix Popover.
- [Select](https://ui.fewtone.com/components/select.md): An accessible React select with a gliding highlight, groups, labels and a scrollable list, built on Radix Select.
