HextaUI

BêtaConstruit sur l’UI SDK 0.1.0-beta.1 de Vercel. Son API peut changer.

Date picker

Un sélecteur de date sur l’UI SDK de Vercel où l’on tape une date dans sa langue ou la choisit, avec plages, préréglages et valeurs ISO.

Dépend de
  • @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

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

UI SDK ajoute un peu de CSS dans une couche de cascade nommée ui-sdk. Déclarez cette couche en haut de votre feuille de style globale, au-dessus de l’import Tailwind, pour que vos classes l’emportent.

@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>

Les valeurs sont des chaînes de date ISO comme 2026-11-20, sans heure ni fuseau horaire, donc pas besoin de bibliothèque de dates. Chacun tape dans son propre ordre, comme 11/20/2026 ou 20.11.2026, et le champ formate la date à l’appui sur Entrée ou en le quittant. DatePickerContent sans enfants affiche un calendrier stylé.

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

Avec mode="range", ajoutez un second input avec end. Le calendrier reste ouvert après la première date et se ferme après la seconde. months={2} affiche deux mois côte à côte, empilés sur petit écran.

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 }

Un DatePickerPreset choisit sa value et ferme le popup. Il s’affiche enfoncé tant que sa valeur est sélectionnée.

"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 et unavailable s’appliquent à la saisie et au choix. Ici, les week-ends et deux jours fériés ne peuvent pas être choisis, et un jour tapé est marqué invalide.

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 définit le format de saisie, l’indice et les noms du calendrier. Sans lui, le sélecteur lit le lang de la page. Les boutons ont des noms anglais, donc passez aria-label pour les traduire.

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 }

Avec un name, la date est envoyée en chaîne ISO. required bloque un envoi vide, un texte qui n’est pas une date aussi, et reset revient à defaultValue.

"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 }

Un contrôleur issu de createDatePickerController() définit la date ou ouvre le calendrier depuis n’importe où, et onValueChange signale chaque changement. Laissez value non défini ici pour que le calendrier puisse aller au mois choisi par le contrôleur.

"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 empêche la saisie et l’ouverture. readOnly ouvre toujours le calendrier pour regarder, mais rien ne peut changer.

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 }

Avec une locale arabe dans dir=rtl, le champ, les flèches et le calendrier partent tous de la droite.

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 }
ToucheAction
EnterDans l’input, lit la date tapée. Sur un jour, le choisit.
←→↑↓Passe d’un jour à l’autre, ou d’une semaine vers le haut et le bas.
Page UpPage DownRecule ou avance d’un mois.
HomeEndPasse au début ou à la fin de la semaine.
EscAnnule une plage inachevée, sinon ferme le calendrier.

Le calendrier s’ouvre comme une boîte de dialogue avec une grille de jours, chacun nommé en entier, comme « Friday, November 20, 2026 ». L’indice décrit l’input, donc les lecteurs d’écran entendent le format à taper. Un texte qui n’est pas une date active aria-invalid et garde l’ancienne valeur, et les jours indisponibles restent focalisables pour que les personnes au clavier les entendent quand même.

Le déclencheur, le popup et les flèches ont des noms anglais. Passez aria-label à chacun pour les traduire. Les inputs utilisent une police de 16px sur écran tactile pour qu’iOS ne zoome pas, et le popup n’apparaît en zoom que si le mouvement est autorisé.

Chaque partie enveloppe la partie UI SDK du même nom et accepte toutes ses props, y compris render et un className qui peut être une fonction de l’état de la partie. DatePickerControl, DatePickerSeparator et DatePickerCalendar sont des parties de mise en page HextaUI.

PropTypePar défaut
mode
"single" | "range""single"
value
string | [string, string] | null–
defaultValue
string | [string, string] | null–
onValueChange
(value, details) => void–
min
string–
max
string–
unavailableDates ISO, { from, to }, { weekday }, { before }, { after } ou une fonction.
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–
controllerIssu de createDatePickerController().
DatePickerController–
AttributDescription
data-slot="date-picker"Cible le sélecteur de date en CSS.
data-openPrésent tant que l’élément est ouvert.
data-invalidPrésent tant qu’un input contient un texte qui n’est pas une date.
data-disabledPrésent lorsque l’élément est désactivé.
data-readonlyPrésent en lecture seule.
PropTypePar défaut
endEn fait la fin d’une plage.
booleanfalse
PropTypePar défaut
childrenPar défaut, une icône de calendrier.
ReactNode–
aria-label
string"Choose date"
PropTypePar défaut
childrenPar défaut, une icône X nommée Clear. Elle se masque tant qu’il n’y a rien à effacer.
ReactNode–
PropTypePar défaut
childrenPar défaut, un DatePickerCalendar.
ReactNode–
align
Align"start"
sideOffset
number6
collisionPadding
number8
AttributDescription
data-slot="date-picker-content"Pour cibler le popup en CSS.
--cell-sizeLa taille de chaque jour, plus grande sur écran tactile.
PropTypePar défaut
monthsCombien de grilles afficher côte à côte.
number1
PropTypePar défaut
value
string | [string, string]–
render
ReactElement | (props, state) => ReactElement–
AttributDescription
data-slot="calendar-day"Cible un jour en CSS.
data-todayPrésent sur aujourd’hui.
data-range-startPrésent là où une plage commence.
data-range-endPrésent là où une plage se termine.
data-unavailablePrésent sur un jour qu’on ne peut pas choisir.
data-outsidePrésent sur un jour du mois suivant ou précédent.