Przejdź do treści

Calendar

Siatka miesiąca do wyboru jednej daty, kilku dat lub zakresu — podstawa każdego selektora daty.

wrzesień 2026
"use client"

import * as React from "react"

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

// A fixed "today" keeps statically rendered docs deterministic.
const today = new Date(2026, 8, 23)

export default function CalendarDemo() {
  const [date, setDate] = React.useState<Date | undefined>(
    new Date(2026, 8, 26)
  )

  return (
    <Calendar
      mode="single"
      today={today}
      defaultMonth={today}
      selected={date}
      onSelect={setDate}
      className="rounded-xl border shadow-raised"
    />
  )
}

Anatomia

wrzesień 2026
  1. 1NagłówekWyświetlany miesiąc. W nagłówku z listami rozwijanymi zamienia się w listy wyboru miesiąca i roku.
  2. 2DzisiajOznaczony subtelnym wypełnieniem, więc łatwo go znaleźć, a nie wygląda na zaznaczony.
  3. 3NawigacjaPoprzedni i następny miesiąc. Na krańcach dozwolonego zakresu przyciski się wyłączają.
  4. 4Zaznaczony dzieńWypełniony kolorem marki. W zakresach zabarwione są też dni pomiędzy.
  5. 5Dzień tygodniaSkrócone nagłówki kolumn, od pierwszego dnia tygodnia według ustawień regionalnych.

Instalacja

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/calendar.json

Użycie

import { Calendar } from "@/components/ui/calendar"
const [date, setDate] = React.useState<Date | undefined>(new Date())

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

Calendar opakowuje React DayPicker w style prfct. Zaznaczenie korzysta ze skali marki: zaznaczone dni wypełnia brand-9, dni wewnątrz zakresu — brand-3, a dzisiejszy dzień jest pogrubiony na spokojnym tle.

Daty w przykładach
Przykłady ustawiają „dzisiaj” na stałą datę propem today, żeby strony renderowane statycznie zgadzały się z tym, co wyrenderuje przeglądarka. W swojej aplikacji go pomiń.

Przykłady

Zakres

mode="range" wybiera początek i koniec; numberOfMonths={2} pokazuje oba krańce dłuższych zakresów naraz. Przeszłe dni wyłącz przez disabled={{ before: today }}, zamiast sprawdzać wybór po fakcie.

wrzesień 2026
październik 2026
"use client"

import * as React from "react"
import type { DateRange } from "react-day-picker"

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

const today = new Date(2026, 8, 23)

export default function CalendarRange() {
  const [range, setRange] = React.useState<DateRange | undefined>({
    from: new Date(2026, 8, 28),
    to: new Date(2026, 9, 9),
  })

  return (
    <Calendar
      mode="range"
      today={today}
      defaultMonth={today}
      numberOfMonths={2}
      selected={range}
      onSelect={setRange}
      disabled={{ before: today }}
      className="rounded-xl border shadow-raised"
    />
  )
}

Wiele dat

mode="multiple" przełącza pojedyncze dni. Ogranicz wybór propem max i pokaż licznik, żeby użytkownik wiedział, kiedy dojdzie do limitu.

wrzesień 2026

Wybrano: 3 z 5 dni w biurze

"use client"

import * as React from "react"

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

const today = new Date(2026, 8, 23)

export default function CalendarMultiple() {
  const [days, setDays] = React.useState<Date[] | undefined>([
    new Date(2026, 8, 24),
    new Date(2026, 8, 29),
    new Date(2026, 9, 1),
  ])

  return (
    <div className="flex flex-col items-center gap-3">
      <Calendar
        mode="multiple"
        max={5}
        today={today}
        defaultMonth={today}
        selected={days}
        onSelect={setDays}
        className="rounded-xl border shadow-raised"
      />
      <p className="text-sm text-muted-foreground" aria-live="polite">
        {`Wybrano: ${days?.length ?? 0} z 5 dni w biurze`}
      </p>
    </div>
  )
}

Listy rozwijane miesiąca i roku

Przy datach odległych od dzisiejszej — takich jak data urodzenia czy rok ważności — captionLayout="dropdown" zastępuje nagłówek listami wyboru miesiąca i roku. Ogranicz ich zakres propami startMonth i endMonth.

maj 1990
"use client"

import * as React from "react"

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

export default function CalendarDropdown() {
  const [date, setDate] = React.useState<Date | undefined>(
    new Date(1990, 4, 12)
  )

  return (
    <Calendar
      mode="single"
      captionLayout="dropdown"
      startMonth={new Date(1920, 0)}
      endMonth={new Date(2026, 11)}
      defaultMonth={date}
      selected={date}
      onSelect={setDate}
      className="rounded-xl border shadow-raised"
    />
  )
}

Niedostępne daty

disabled przyjmuje daty, zakresy, dni tygodnia i funkcje. Połącz go z modifiers, żeby pokazać, dlaczego dzień jest niedostępny — tutaj zarezerwowane dni są przekreślone, a weekendy tylko przygaszone.

wrzesień 2026
"use client"

import * as React from "react"

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

const today = new Date(2026, 8, 23)
const booked = [
  new Date(2026, 8, 24),
  new Date(2026, 8, 25),
  new Date(2026, 9, 2),
]

export default function CalendarDisabledDates() {
  const [date, setDate] = React.useState<Date | undefined>()

  return (
    <Calendar
      mode="single"
      today={today}
      defaultMonth={today}
      selected={date}
      onSelect={setDate}
      // Past days, weekends and fully booked days can't be chosen.
      disabled={[{ before: today }, { dayOfWeek: [0, 6] }, ...booked]}
      modifiers={{ booked }}
      modifiersClassNames={{ booked: "[&>button]:line-through" }}
      className="rounded-xl border shadow-raised"
    />
  )
}

Selektor daty

Codzienne pole daty: Button, który pokazuje wybraną datę i otwiera kalendarz w komponencie Popover. Zamykaj popover po wyborze i formatuj datę dla ludzi, nie dla maszyn.

"use client"

import * as React from "react"
import { CalendarIcon } from "lucide-react"

import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover"

const today = new Date(2026, 8, 23)
const formatter = new Intl.DateTimeFormat("pl-PL", { dateStyle: "long" })

export default function CalendarDatePicker() {
  const [open, setOpen] = React.useState(false)
  const [date, setDate] = React.useState<Date | undefined>()

  return (
    <Field className="w-64">
      <FieldLabel htmlFor="due-date">Termin</FieldLabel>
      <Popover open={open} onOpenChange={setOpen}>
        <PopoverTrigger
          render={
            <Button
              id="due-date"
              variant="outline"
              className="w-full justify-start font-normal data-[empty=true]:text-muted-foreground"
              data-empty={!date}
            />
          }
        >
          <CalendarIcon data-icon="inline-start" />
          {date ? formatter.format(date) : "Wybierz datę"}
        </PopoverTrigger>
        <PopoverContent align="start" className="w-auto p-0">
          <Calendar
            mode="single"
            today={today}
            defaultMonth={date ?? today}
            selected={date}
            onSelect={(next) => {
              setDate(next)
              setOpen(false)
            }}
            disabled={{ before: today }}
          />
        </PopoverContent>
      </Popover>
      <FieldDescription>
        Po tym dniu zadanie stanie się zaległe.
      </FieldDescription>
    </Field>
  )
}

Lokalizacja

Przekaż locale z react-day-picker/locale, żeby przetłumaczyć nazwy miesięcy i dni oraz zaczynać tydzień od właściwego dnia. Tutaj po polsku — tydzień zaczyna się w poniedziałek — z numerami tygodni ISO.

wrzesień 2026
36
37
38
39
40
"use client"

import * as React from "react"
import { pl } from "react-day-picker/locale"

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

const today = new Date(2026, 8, 23)

export default function CalendarLocalized() {
  const [date, setDate] = React.useState<Date | undefined>(
    new Date(2026, 8, 30)
  )

  return (
    <Calendar
      mode="single"
      locale={pl}
      showWeekNumber
      today={today}
      defaultMonth={today}
      selected={date}
      onSelect={setDate}
      className="rounded-xl border shadow-raised"
    />
  )
}

Wytyczne

Kiedy używać

  • Do wyboru dat bliskich dzisiejszej, gdy pomaga kontekst tygodnia i miesiąca: rezerwacje, terminy, dostępność.
  • Do wyboru zakresów, gdy widok obu krańców naraz zapobiega pomyłkom.

Kiedy nie używać

  • Do dat, które użytkownik zna na pamięć i które są odległe od dzisiejszej, np. daty urodzenia — trzy pola albo pole tekstowe z czytelnym formatem są szybsze niż cofanie się o dziesięciolecia.
  • Do przybliżonych terminów, takich jak w przyszłym tygodniu — zaproponuj gotowe opcje w komponencie Select albo przyciskami.

Pokazuj ograniczenia

Wyłącz dni, których nie można wybrać, zamiast odrzucać je po wyborze, i wyjaśnij powód w tekście obok (Weekendy są niedostępne). Nigdy nie pozwól zbudować wyboru, który zostanie odrzucony dopiero przy wysyłaniu formularza.

Wybierz dzień dostawyTylko dni robocze · najbliższy wolny termin: czw. 24
DobrzeNiedostępne dni są wyłączone od początku, a reguła jest podana obok.
Wybierz dzień dostawyNiedziele są niedostępne. Spróbuj ponownie.
ŹleNiedzielę da się wybrać, a błąd pojawia się dopiero po wysłaniu formularza.

Formaty

W wyzwalaczu pokazuj datę słownie (26 września 2026) za pomocą Intl.DateTimeFormat z ustawieniami regionalnymi użytkownika, a zapisuj ją w ISO 8601. Niejednoznaczne formaty liczbowe, takie jak 09/10, w różnych krajach czyta się różnie.

Dostępność

Kalendarz renderuje grid przycisków z wędrującym fokusem (roving focus). Czytnik ekranu odczytuje każdy dzień z pełną datą oraz stanem zaznaczenia i wyłączenia; nagłówek jest regionem na żywo, więc podczas nawigacji ogłaszany jest bieżący miesiąc.

KlawiszDziałanie
←→
Przenosi fokus na poprzedni lub następny dzień.
↑↓
Przenosi fokus na ten sam dzień w poprzednim lub następnym tygodniu.
PageUpPageDown
Przechodzi do tego samego dnia w poprzednim lub następnym miesiącu.
ShiftPageUpShiftPageDown
Przechodzi do tego samego dnia w poprzednim lub następnym roku.
HomeEnd
Przechodzi do pierwszego lub ostatniego dnia tygodnia.
EnterSpacja
Zaznacza dzień, na którym jest fokus.
  • Fokus. Dzień z fokusem ma standardowy pierścień fokusu o grubości 2 px; zaznaczony dzień ma dodatkowo aria-selected — nigdy nie jest oznaczony samym kolorem.
  • W popoverze. Tekst przycisku wyzwalacza powinien podawać bieżącą wartość (Wybierz datę albo sformatowaną datę), a po zamknięciu popovera fokus wraca na ten przycisk.
  • Kontrast. Zaznaczone dni używają brand-9 z kolorem kontrastowym marki (≥ 4,5:1); dni spoza miesiąca i dni wyłączone są przygaszone, ale pozostają czytelne.

Dokumentacja API

Calendar

Przyjmuje wszystkie propsy DayPicker. Najczęściej używane:

PropTypDomyślnie
mode

Co można zaznaczyć.

"single" | "multiple" | "range"Brak wartości domyślnej
selected

Zaznaczenie. Używaj razem z onSelect.

Date | Date[] | DateRangeBrak wartości domyślnej
onSelect

Wywoływana, gdy zmienia się zaznaczenie.

(selected, day, modifiers, event) => voidBrak wartości domyślnej
defaultMonth

Miesiąc wyświetlany na początku.

DateBrak wartości domyślnej
month / onMonthChange

Steruje wyświetlanym miesiącem.

Date / (month) => voidBrak wartości domyślnej
numberOfMonths

Ile miesięcy pokazać obok siebie.

number1
captionLayout

Nagłówek tekstowy albo listy rozwijane miesięcy i/lub lat.

"label" | "dropdown" | "dropdown-months" | "dropdown-years""label"
startMonth / endMonth

Granice nawigacji i list rozwijanych.

DateBrak wartości domyślnej
disabled

Dni, których nie można zaznaczyć: daty, zakresy, { before }, { dayOfWeek }, funkcje.

Matcher | Matcher[]Brak wartości domyślnej
modifiers / modifiersClassNames

Własne stany dni i ich klasy.

Record<string, Matcher> / Record<string, string>Brak wartości domyślnej
min / max

Limity zaznaczenia w trybach multiple i range.

numberBrak wartości domyślnej
locale

Tłumaczenia i początek tygodnia, np. pl z react-day-picker/locale. Domyślnie język kalendarza z LocaleProvider, a bez niego angielski.

LocaleBrak wartości domyślnej
weekStartsOn

Nadpisuje pierwszy dzień tygodnia z ustawień regionalnych.

0 – 6Brak wartości domyślnej
showWeekNumber

Dodaje kolumnę z numerami tygodni, liczonymi według reguł danego języka. Dodaj ISOWeek, żeby w każdym języku numerować tygodnie zgodnie z ISO 8601.

booleanfalse
showOutsideDays

Pokazuje przygaszone dni sąsiednich miesięcy. Gdy numberOfMonths jest większe niż 1, prfct domyślnie to wyłącza, żeby dni i zaznaczenia się nie powtarzały.

booleannumberOfMonths <= 1
today

Nadpisuje dzisiejszą datę.

DateBrak wartości domyślnej
buttonVariant

Tylko w prfct: wariant Button dla strzałek nawigacji.

ButtonProps["variant"]"ghost"
classNames / formatters / labels

Scalane z własnymi ustawieniami prfct, więc można zastąpić jedną pozycję bez utraty pozostałych.

propsy DayPickerBrak wartości domyślnej

CalendarDayButton

Przycisk renderowany dla każdego dnia. Podmień go przez components={{ DayButton }}, żeby dodać treść, np. ceny albo kropki dostępności.