HextaUI

Date picker

Ein Button, der einen Kalender in einem Popover öffnet, auf Smartphones als Bottom Sheet, für einzelne Daten und Zeiträume.

import { DatePicker } from "@/components/ui/date-picker"

export function DatePickerDemo() {
  return <DatePicker />
}
pnpm dlx shadcn@latest add https://hextaui.com/r/date-picker.json

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

import { DatePicker, DateRangePicker } from "@/components/ui/date-picker"
const [date, setDate] = React.useState<Date | null>(null)

<DatePicker value={date} onValueChange={setDate} />

Auf Bildschirmen unter 640 px öffnet sich der Kalender statt in einem Popover in einem Bottom Sheet, damit die Tage groß genug zum Antippen bleiben.

Kontrolliert

Übergib value und onValueChange. Geleerte Werte sind null. Der Kalender öffnet sich immer im Monat des ausgewählten Datums, und das Wählen eines Tages schließt ihn und gibt den Fokus an den Button zurück.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { DatePicker } from "@/components/ui/date-picker"

function daysFromToday(days: number) {
  const date = new Date()
  date.setHours(0, 0, 0, 0)
  date.setDate(date.getDate() + days)
  return date
}

const isoDay = new Intl.DateTimeFormat("en-CA", {
  year: "numeric",
  month: "2-digit",
  day: "2-digit",
})

export function DatePickerControlled() {
  const [date, setDate] = React.useState<Date | null>(null)

  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <DatePicker value={date} onValueChange={setDate} clearable />
      <Button variant="ghost" onClick={() => setDate(daysFromToday(0))}>
        Today
      </Button>
      <Button variant="ghost" onClick={() => setDate(daysFromToday(7))}>
        In a week
      </Button>
      <output className="text-sm text-muted-foreground tabular-nums">
        {date ? isoDay.format(date) : "null"}
      </output>
    </div>
  )
}

Range

<DateRangePicker /> zeigt auf größeren Bildschirmen zwei Monate. Der erste Klick beginnt immer einen neuen Bereich, auch wenn bereits einer gesetzt ist, und der zweite Klick beendet ihn in beliebiger Reihenfolge und schließt den Picker.

"use client"

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

import { DateRangePicker } from "@/components/ui/date-picker"

const isoDay = new Intl.DateTimeFormat("en-CA", {
  year: "numeric",
  month: "2-digit",
  day: "2-digit",
})

export function DatePickerRange() {
  const [range, setRange] = React.useState<DateRange | null>(null)

  return (
    <div className="flex flex-col items-center gap-2">
      <DateRangePicker value={range} onValueChange={setRange} clearable />
      <output className="text-sm text-muted-foreground tabular-nums">
        {range?.from && range.to
          ? `${isoDay.format(range.from)} → ${isoDay.format(range.to)}`
          : "null"}
      </output>
    </div>
  )
}

Deaktivierte Tage

Übergib einen beliebigen react-day-picker-Matcher als calendarProps.disabled. Hier können nur künftige Werktage gewählt werden.

"use client"

import * as React from "react"

import { DatePicker } from "@/components/ui/date-picker"

function tomorrow() {
  const date = new Date()
  date.setHours(0, 0, 0, 0)
  date.setDate(date.getDate() + 1)
  return date
}

export function DatePickerDisabledDays() {
  const [firstDay] = React.useState(tomorrow)

  return (
    <DatePicker
      placeholder="Delivery date"
      calendarProps={{
        disabled: [{ before: firstDay }, { dayOfWeek: [0, 6] }],
      }}
    />
  )
}

Dropdowns für Monat und Jahr

Setze für weit entfernte Daten wie einen Geburtstag captionLayout: "dropdown" mit startMonth und endMonth. Beschrifte den Trigger mit einem <label />, das auf seine id zeigt.

import { DatePicker } from "@/components/ui/date-picker"

export function DatePickerDropdowns() {
  return (
    <div className="flex flex-col gap-1.5">
      <label htmlFor="birthday" className="text-sm font-medium">
        Date of birth
      </label>
      <DatePicker
        id="birthday"
        placeholder="Select your birthday"
        calendarProps={{
          captionLayout: "dropdown",
          startMonth: new Date(1920, 0),
          endMonth: new Date(),
          disabled: { after: new Date() },
        }}
      />
    </div>
  )
}

In einem Formular

Mit name wird ein einzelnes Datum als 2026-10-14 gesendet und ein Bereich als 2026-10-20/2026-10-24. Leere Picker senden einen leeren String.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { DatePicker, DateRangePicker } from "@/components/ui/date-picker"

export function DatePickerForm() {
  const [submitted, setSubmitted] = React.useState<string>()

  return (
    <form
      className="flex flex-col items-start gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        setSubmitted(JSON.stringify(Object.fromEntries(data)))
      }}
    >
      <DatePicker name="due" defaultValue={new Date(2026, 9, 14)} />
      <DateRangePicker
        name="stay"
        defaultValue={{
          from: new Date(2026, 9, 20),
          to: new Date(2026, 9, 24),
        }}
      />
      <Button type="submit">Submit</Button>
      <output className="text-sm break-all text-muted-foreground">
        {submitted ?? "Not submitted"}
      </output>
    </form>
  )
}

Deaktiviert

Ein deaktivierter Trigger kann trotzdem einen Wert anzeigen.

import { DatePicker } from "@/components/ui/date-picker"

export function DatePickerDisabled() {
  return (
    <div className="flex flex-wrap justify-center gap-2">
      <DatePicker disabled />
      <DatePicker disabled defaultValue={new Date(2026, 0, 1)} />
    </div>
  )
}

Schmaler Container

Der Trigger wird nie breiter als sein Container. Lange Labels werden gekürzt, statt umzubrechen.

import { DateRangePicker } from "@/components/ui/date-picker"

export function DatePickerNarrow() {
  return (
    <div className="w-40">
      <DateRangePicker
        defaultValue={{
          from: new Date(2026, 0, 28),
          to: new Date(2026, 11, 3),
        }}
      />
    </div>
  )
}

In einem Sheet

Der Picker liegt über dem Sheet, und Escape schließt nur den Picker.

import { Button } from "@/components/ui/button"
import { DateRangePicker } from "@/components/ui/date-picker"
import {
  Sheet,
  SheetBody,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"

export function DatePickerInSheet() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="outline" />}>
        Edit booking
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Edit booking</SheetTitle>
          <SheetDescription>
            The picker opens above the sheet and Escape closes only the picker.
          </SheetDescription>
        </SheetHeader>
        <SheetBody>
          <DateRangePicker />
        </SheetBody>
      </SheetContent>
    </Sheet>
  )
}

Rechts nach links

Übergib locale für das Trigger-Label und in calendarProps eine react-day-picker-Locale mit dir: "rtl" für den Kalender. Die Pfeiltasten folgen der Richtung.

"use client"

import { arSA } from "react-day-picker/locale"

import { DatePicker, DateRangePicker } from "@/components/ui/date-picker"

export function DatePickerRtl() {
  return (
    <div dir="rtl" className="flex flex-wrap justify-center gap-2">
      <DatePicker
        locale="ar"
        placeholder="اختر تاريخًا"
        title="اختر تاريخًا"
        clearLabel="مسح"
        clearable
        defaultValue={new Date(2026, 9, 2)}
        calendarProps={{ locale: arSA, dir: "rtl" }}
      />
      <DateRangePicker
        locale="ar"
        placeholder="اختر فترة"
        title="اختر فترة"
        calendarProps={{ locale: arSA, dir: "rtl" }}
      />
    </div>
  )
}
TasteAktion
EnterSpaceAm Trigger: öffnet den Picker und fokussiert den ausgewählten Tag oder heute. Auf einem Tag: wählt ihn aus.
←→Wechselt zum vorherigen oder nächsten Tag.
↑↓Wechselt zum selben Tag der vorherigen oder nächsten Woche.
Page UpPage DownWechselt zum vorherigen oder nächsten Monat.
ShiftPage UpWechselt zum vorherigen Jahr. Shift+Bild ab wechselt zum nächsten.
HomeEndWechselt zum Anfang oder Ende der Woche.
EscSchließt den Picker und gibt den Fokus an den Trigger zurück.
  • Das Popover wird mit title beschriftet, und das Bottom Sheet zeigt ihn als Überschrift. Übersetze ihn zusammen mit placeholder.
  • Der Trigger hat kein eigenes sichtbares Label. Kombiniere ihn mit einem <label /> oder übergib aria-label.
  • Deaktivierte Tage werden von der Tastatur übersprungen und als nicht verfügbar angesagt.

Beide Picker teilen die Props unten und rendern einen <Button />-Trigger mit einem <Calendar /> in einem Popover oder Bottom Sheet.

PropTypStandard
value
Date | null–
defaultValue
Date | nullnull
onValueChange
(value: Date | null) => void–
placeholder
string"Pick a date"
titleZugänglicher Name des Popovers, Überschrift des Sheets.
string"Select a date"
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean) => void–
localeFormatiert das Trigger-Label mit Intl.DateTimeFormat. Übergib in calendarProps eine react-day-picker-Locale, um auch den Kalender zu übersetzen.
string"en-US"
clearableFügt unter dem Kalender einen Löschen-Button hinzu, sobald ein Wert vorhanden ist.
booleanfalse
clearLabel
string"Clear"
nameRendert ein verstecktes Input, damit der Wert mit seinem Formular gesendet wird.
string–
calendarPropsWird an den Kalender weitergegeben, außer mode, selected, onSelect, required, numberOfMonths und autoFocus. Nutze es für deaktivierte Tage, captionLayout, startMonth, endMonth, locale und dir.
CalendarProps–
variantDer Trigger ist ein Button und akzeptiert auch dessen übrige Props.
Button variant"outline"
disabled
booleanfalse
classNameWird auf den Trigger angewendet, der standardmäßig w-60 ist.
string–
PropTypStandard
value
{ from: Date; to?: Date } | null–
defaultValue
{ from: Date; to?: Date } | nullnull
onValueChangeWird aufgerufen, sobald der zweite Tag gewählt ist.
(value: DateRange | null) => void–
placeholder
string"Pick a date range"
title
string"Select dates"
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean) => void–
localeFormatiert das Trigger-Label mit Intl.DateTimeFormat. Übergib in calendarProps eine react-day-picker-Locale, um auch den Kalender zu übersetzen.
string"en-US"
clearableFügt unter dem Kalender einen Löschen-Button hinzu, sobald ein Wert vorhanden ist.
booleanfalse
clearLabel
string"Clear"
nameRendert ein verstecktes Input, damit der Wert mit seinem Formular gesendet wird.
string–
calendarPropsWird an den Kalender weitergegeben, außer mode, selected, onSelect, required, numberOfMonths und autoFocus. Nutze es für deaktivierte Tage, captionLayout, startMonth, endMonth, locale und dir.
CalendarProps–
variantDer Trigger ist ein Button und akzeptiert auch dessen übrige Props.
Button variant"outline"
disabled
booleanfalse
classNameWird auf den Trigger angewendet, der standardmäßig w-60 ist.
string–
AttributBeschreibung
data-slot="date-picker-trigger"Der Trigger-Button.
data-emptyVorhanden am Trigger, solange kein Wert vorhanden ist. Zeigt den Platzhalter in gedämpftem Text.
data-popup-openVorhanden am Trigger, solange der Picker geöffnet ist.
data-slot="date-picker-content"Umschließt den Kalender im Popover oder Sheet.
data-slot="date-picker-clear"Der Löschen-Button.

In Blocks verwendet

Blocks, die auf Date picker aufbauen.