HextaUI

BetaGebaut auf Vercels UI SDK 0.1.0-beta.1. Die API kann sich ändern.

Date picker

Ein Datepicker auf Vercels UI SDK, in dem man ein Datum in der eigenen Sprache tippt oder auswählt, mit Bereichen, Presets und ISO-Werten.

Abhängig von
  • @tabler/icons-react
  • @ui-sdk/core
  • @ui-sdk/react
MM/DD/YYYY
import {
  DatePicker,
  DatePickerClear,
  DatePickerContent,
  DatePickerControl,
  DatePickerHint,
  DatePickerInput,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"

function UiSdkDatePickerDemo() {
  return (
    <DatePicker defaultValue="2026-11-20" className="max-w-56">
      <DatePickerControl>
        <DatePickerInput aria-label="Due date" />
        <DatePickerClear />
        <DatePickerTrigger />
      </DatePickerControl>
      <DatePickerHint />
      <DatePickerContent />
    </DatePicker>
  )
}

export { UiSdkDatePickerDemo }
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-date-picker.json

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

UI SDK bringt etwas eigenes CSS in einem Cascade Layer namens ui-sdk mit. Deklariere diesen Layer ganz oben im globalen Stylesheet, über dem Tailwind-Import, damit deine Klassen gewinnen.

@layer ui-sdk;

@import "tailwindcss";
import {
  DatePicker,
  DatePickerContent,
  DatePickerControl,
  DatePickerHint,
  DatePickerInput,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"
<DatePicker defaultValue="2026-11-20">
  <DatePickerControl>
    <DatePickerInput aria-label="Due date" />
    <DatePickerTrigger />
  </DatePickerControl>
  <DatePickerHint />
  <DatePickerContent />
</DatePicker>

Werte sind ISO-Datumsstrings wie 2026-11-20, ohne Uhrzeit oder Zeitzone, also brauchst du keine Datumsbibliothek. Man tippt in der eigenen Reihenfolge, etwa 11/20/2026 oder 20.11.2026, und das Feld formatiert das Datum bei Enter oder beim Verlassen. DatePickerContent ohne Kinder rendert einen gestalteten Kalender.

DatePicker
├── DatePickerControl
│   ├── DatePickerInput
│   ├── DatePickerSeparator
│   ├── DatePickerClear
│   └── DatePickerTrigger
├── DatePickerHint
└── DatePickerContent
    ├── DatePickerPreset
    └── DatePickerCalendar
        ├── CalendarPrevious
        ├── CalendarHeading
        ├── CalendarNext
        └── CalendarGrid
            ├── CalendarWeekdays
            │   └── CalendarWeekday
            └── CalendarWeeks
                └── CalendarDay

Mit mode="range" fügst du ein zweites Input mit end hinzu. Der Kalender bleibt nach dem ersten Datum offen und schließt nach dem zweiten. months={2} zeigt zwei Monate nebeneinander, auf kleinen Bildschirmen untereinander.

import {
  DatePicker,
  DatePickerCalendar,
  DatePickerContent,
  DatePickerControl,
  DatePickerInput,
  DatePickerSeparator,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"

function UiSdkDatePickerRange() {
  return (
    <DatePicker
      mode="range"
      months={2}
      pageBy="page"
      defaultValue={["2026-11-18", "2026-11-24"]}
      className="max-w-72"
    >
      <DatePickerControl>
        <DatePickerInput aria-label="Check-in" />
        <DatePickerSeparator />
        <DatePickerInput end aria-label="Check-out" />
        <DatePickerTrigger />
      </DatePickerControl>
      <DatePickerContent>
        <DatePickerCalendar months={2} />
      </DatePickerContent>
    </DatePicker>
  )
}

export { UiSdkDatePickerRange }

Ein DatePickerPreset wählt seinen value und schließt das Popup. Solange sein Wert ausgewählt ist, wirkt er gedrückt.

"use client"

import * as React from "react"

import {
  DatePicker,
  DatePickerCalendar,
  DatePickerContent,
  DatePickerControl,
  DatePickerInput,
  DatePickerPreset,
  DatePickerSeparator,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"

function isoDate(date: Date) {
  const month = String(date.getMonth() + 1).padStart(2, "0")
  const day = String(date.getDate()).padStart(2, "0")
  return `${date.getFullYear()}-${month}-${day}`
}

function daysAgo(days: number) {
  const date = new Date()
  date.setDate(date.getDate() - days)
  return isoDate(date)
}

function UiSdkDatePickerPresets() {
  const [today, setToday] = React.useState<string | null>(null)

  React.useEffect(() => {
    setToday(isoDate(new Date()))
  }, [])

  const presets = today
    ? [
        { label: "Today", value: [today, today] as [string, string] },
        {
          label: "Last 7 days",
          value: [daysAgo(6), today] as [string, string],
        },
        {
          label: "Last 30 days",
          value: [daysAgo(29), today] as [string, string],
        },
        {
          label: "Last 90 days",
          value: [daysAgo(89), today] as [string, string],
        },
      ]
    : []

  return (
    <DatePicker mode="range" className="max-w-72">
      <DatePickerControl>
        <DatePickerInput aria-label="From" />
        <DatePickerSeparator />
        <DatePickerInput end aria-label="To" />
        <DatePickerTrigger />
      </DatePickerControl>
      <DatePickerContent>
        <div className="flex flex-wrap gap-0.5 border-b pb-3 sm:w-32 sm:flex-col sm:flex-nowrap sm:border-e sm:border-b-0 sm:pe-3 sm:pb-0">
          {presets.map((preset) => (
            <DatePickerPreset key={preset.label} value={preset.value}>
              {preset.label}
            </DatePickerPreset>
          ))}
        </div>
        <DatePickerCalendar />
      </DatePickerContent>
    </DatePicker>
  )
}

export { UiSdkDatePickerPresets }

min, max und unavailable gelten beim Tippen und Auswählen. Hier sind Wochenenden und zwei Feiertage nicht wählbar, und ein getippter wird als ungültig markiert.

import {
  DatePicker,
  DatePickerContent,
  DatePickerControl,
  DatePickerHint,
  DatePickerInput,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"

function UiSdkDatePickerLimits() {
  return (
    <DatePicker
      min="2026-11-02"
      max="2026-12-18"
      unavailable={[{ weekday: [0, 6] }, "2026-11-26", "2026-11-27"]}
      defaultMonth="2026-11"
      className="max-w-56"
    >
      <DatePickerControl>
        <DatePickerInput aria-label="Delivery date" />
        <DatePickerTrigger />
      </DatePickerControl>
      <DatePickerHint />
      <DatePickerContent />
    </DatePicker>
  )
}

export { UiSdkDatePickerLimits }

locale legt das Eingabeformat, den Hinweis und die Namen im Kalender fest. Ohne es liest der Picker das lang der Seite. Die Buttons haben englische Namen, also gib aria-label an, um sie zu übersetzen.

import {
  DatePicker,
  DatePickerContent,
  DatePickerControl,
  DatePickerHint,
  DatePickerInput,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"

function UiSdkDatePickerLocale() {
  return (
    <DatePicker locale="de" defaultValue="2026-12-03" className="max-w-56">
      <DatePickerControl>
        <DatePickerInput aria-label="Lieferdatum" />
        <DatePickerTrigger aria-label="Datum wählen" />
      </DatePickerControl>
      <DatePickerHint />
      <DatePickerContent aria-label="Datum wählen" />
    </DatePicker>
  )
}

export { UiSdkDatePickerLocale }

Mit einem name wird das Datum als ISO-String gesendet. required blockiert ein leeres Absenden, Text, der kein Datum ist, ebenfalls, und Reset kehrt zu defaultValue zurück.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  DatePicker,
  DatePickerContent,
  DatePickerControl,
  DatePickerHint,
  DatePickerInput,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"

function UiSdkDatePickerForm() {
  const [submitted, setSubmitted] = React.useState<string | null>(null)

  return (
    <form
      className="flex w-full max-w-56 flex-col gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        setSubmitted(String(data.get("start")))
      }}
    >
      <DatePicker name="start" required>
        <DatePickerControl>
          <DatePickerInput aria-label="Start date" />
          <DatePickerTrigger />
        </DatePickerControl>
        <DatePickerHint />
        <DatePickerContent />
      </DatePicker>
      <div className="flex items-center gap-3">
        <Button type="submit" size="sm">
          Submit
        </Button>
        <Button type="reset" size="sm" variant="ghost">
          Reset
        </Button>
      </div>
      <p className="text-sm text-muted-foreground tabular-nums">
        {submitted ? `Submitted ${submitted}` : "Nothing submitted yet."}
      </p>
    </form>
  )
}

export { UiSdkDatePickerForm }

Ein Controller aus createDatePickerController() setzt das Datum oder öffnet den Kalender von überall, und onValueChange meldet jede Änderung. Lass value hier weg, damit der Kalender zum Monat springen kann, den der Controller wählt.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  createDatePickerController,
  DatePicker,
  DatePickerContent,
  DatePickerControl,
  DatePickerInput,
  DatePickerTrigger,
  type DatePickerValue,
} from "@/components/ui/ui-sdk-date-picker"

function UiSdkDatePickerController() {
  const [picker] = React.useState(createDatePickerController)
  const [value, setValue] = React.useState<DatePickerValue>(null)

  return (
    <div className="flex w-full max-w-56 flex-col gap-3">
      <DatePicker controller={picker} onValueChange={setValue}>
        <DatePickerControl>
          <DatePickerInput aria-label="Party date" />
          <DatePickerTrigger />
        </DatePickerControl>
        <DatePickerContent />
      </DatePicker>
      <div className="flex gap-2">
        <Button
          variant="outline"
          size="sm"
          onClick={() => picker.setValue("2026-12-25")}
        >
          Choose Christmas
        </Button>
        <Button variant="ghost" size="sm" onClick={() => picker.open()}>
          Open
        </Button>
      </div>
      <p className="text-sm text-muted-foreground tabular-nums">
        {typeof value === "string" ? value : "No date"}
      </p>
    </div>
  )
}

export { UiSdkDatePickerController }

disabled verhindert Tippen und Öffnen. readOnly öffnet den Kalender weiterhin zum Ansehen, aber nichts lässt sich ändern.

import {
  DatePicker,
  DatePickerContent,
  DatePickerControl,
  DatePickerInput,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"

function UiSdkDatePickerDisabled() {
  return (
    <div className="flex w-full max-w-56 flex-col gap-3">
      <DatePicker defaultValue="2026-11-20" disabled>
        <DatePickerControl>
          <DatePickerInput aria-label="Disabled date" />
          <DatePickerTrigger />
        </DatePickerControl>
        <DatePickerContent />
      </DatePicker>
      <DatePicker defaultValue="2026-11-20" readOnly>
        <DatePickerControl>
          <DatePickerInput aria-label="Read-only date" />
          <DatePickerTrigger />
        </DatePickerControl>
        <DatePickerContent />
      </DatePicker>
    </div>
  )
}

export { UiSdkDatePickerDisabled }

Mit arabischer Locale in dir=rtl laufen Feld, Pfeile und Kalender von rechts.

import {
  DatePicker,
  DatePickerContent,
  DatePickerControl,
  DatePickerInput,
  DatePickerTrigger,
} from "@/components/ui/ui-sdk-date-picker"

function UiSdkDatePickerRtl() {
  return (
    <div dir="rtl" className="w-full max-w-56">
      <DatePicker locale="ar" defaultValue="2026-11-20">
        <DatePickerControl>
          <DatePickerInput aria-label="تاريخ الاستحقاق" />
          <DatePickerTrigger aria-label="اختر التاريخ" />
        </DatePickerControl>
        <DatePickerContent aria-label="اختر التاريخ" />
      </DatePicker>
    </div>
  )
}

export { UiSdkDatePickerRtl }
TasteAktion
EnterIm Input liest es das getippte Datum. Auf einem Tag wählt es ihn.
←→↑↓Wechselt zwischen Tagen, nach oben und unten um eine Woche.
Page UpPage DownGeht einen Monat zurück oder vor.
HomeEndWechselt zum Anfang oder Ende der Woche.
EscBricht einen unfertigen Bereich ab, sonst schließt es den Kalender.

Der Kalender öffnet sich als Dialog mit einem Raster aus Tagen, jeder voll benannt, etwa „Friday, November 20, 2026“. Der Hinweis beschreibt das Input, sodass Screenreader das einzugebende Format hören. Text, der kein Datum ist, setzt aria-invalid und behält den alten Wert, und nicht verfügbare Tage bleiben fokussierbar, damit Tastaturnutzer sie trotzdem hören.

Trigger, Popup und Pfeile haben englische Namen. Gib jedem ein aria-label, um sie zu übersetzen. Inputs nutzen auf Touchscreens 16px Schrift, damit iOS nicht zoomt, und das Popup skaliert nur herein, wenn Bewegung erlaubt ist.

Jeder Teil umschließt den gleichnamigen UI-SDK-Teil und nimmt alle seine Props an, darunter render und einen className, der eine Funktion des Teilzustands sein kann. DatePickerControl, DatePickerSeparator und DatePickerCalendar sind Layout-Teile von HextaUI.

PropTypStandard
mode
"single" | "range""single"
value
string | [string, string] | null–
defaultValue
string | [string, string] | null–
onValueChange
(value, details) => void–
min
string–
max
string–
unavailableISO-Daten, { from, to }, { weekday }, { before }, { after } oder eine Funktion.
DateMatchers–
months
number1
pageBy
"month" | "page""month"
locale
string–
weekStartsOn
number–
minDays
number–
maxDays
number–
closeOnSelect
booleantrue
disabled
booleanfalse
readOnly
booleanfalse
name
string–
startName
string–
endName
string–
required
booleanfalse
open
boolean–
onOpenChange
(open, details) => void–
controllerAus createDatePickerController().
DatePickerController–
AttributBeschreibung
data-slot="date-picker"Spricht den Datepicker in CSS an.
data-openVorhanden, solange geöffnet.
data-invalidVorhanden, solange ein Input Text enthält, der kein Datum ist.
data-disabledVorhanden, wenn deaktiviert.
data-readonlyVorhanden, wenn schreibgeschützt.
PropTypStandard
endMacht es zum Ende eines Bereichs.
booleanfalse
PropTypStandard
childrenStandard ist ein Kalendersymbol.
ReactNode–
aria-label
string"Choose date"
PropTypStandard
childrenStandard ist ein X-Symbol namens Clear. Es verschwindet, solange nichts zu leeren ist.
ReactNode–
PropTypStandard
childrenStandard ist ein DatePickerCalendar.
ReactNode–
align
Align"start"
sideOffset
number6
collisionPadding
number8
AttributBeschreibung
data-slot="date-picker-content"Spricht das Popup in CSS an.
--cell-sizeDie Größe jedes Tages, auf Touchscreens größer.
PropTypStandard
monthsWie viele Raster nebeneinander erscheinen.
number1
PropTypStandard
value
string | [string, string]–
render
ReactElement | (props, state) => ReactElement–
AttributBeschreibung
data-slot="calendar-day"Spricht einen Tag in CSS an.
data-todayVorhanden am heutigen Tag.
data-range-startVorhanden, wo ein Bereich beginnt.
data-range-endVorhanden, wo ein Bereich endet.
data-unavailableVorhanden an einem Tag, der nicht wählbar ist.
data-outsideVorhanden an einem Tag des nächsten oder vorigen Monats.