HextaUI

BetaHecho sobre el UI SDK 0.1.0-beta.1 de Vercel. Su API puede cambiar.

Date picker

Un selector de fechas sobre el UI SDK de Vercel donde se escribe la fecha en el propio idioma o se elige, con rangos, preajustes y valores ISO.

Depende 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

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

UI SDK añade un poco de CSS propio en una capa de cascada llamada ui-sdk. Declara esa capa al principio de tu hoja de estilos global, encima del import de Tailwind, para que tus clases ganen.

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

Los valores son cadenas de fecha ISO como 2026-11-20, sin hora ni zona horaria, así que no necesitas una librería de fechas. Cada persona escribe en su propio orden, como 11/20/2026 o 20.11.2026, y el campo da formato a la fecha al pulsar Enter o salir. DatePickerContent sin hijos muestra un calendario con estilo.

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

Con mode="range", añade un segundo input con end. El calendario sigue abierto tras la primera fecha y se cierra tras la segunda. months={2} muestra dos meses lado a lado, apilados en pantallas pequeñas.

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 elige su value y cierra el popup. Se muestra pulsado mientras su valor está seleccionado.

"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 y unavailable se aplican al escribir y al elegir. Aquí no se pueden elegir los fines de semana ni dos festivos, y uno escrito se marca como no válido.

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 define el formato escrito, la pista y los nombres del calendario. Sin él, el selector lee el lang de la página. Los botones tienen nombres en inglés, así que pasa aria-label para traducirlos.

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 }

Con un name, la fecha se envía como cadena ISO. required bloquea un envío vacío, un texto que no es fecha también lo bloquea, y reset vuelve a 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 controlador de createDatePickerController() fija la fecha o abre el calendario desde cualquier lugar, y onValueChange informa de cada cambio. Deja value sin definir aquí para que el calendario pueda ir al mes que elige el controlador.

"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 impide escribir y abrir. readOnly sigue abriendo el calendario para mirar, pero nada puede cambiar.

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 }

Con una configuración regional árabe dentro de dir=rtl, el campo, las flechas y el calendario van de derecha a izquierda.

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 }
KeyAcción
EnterEn el input, lee la fecha escrita. En un día, lo elige.
←→↑↓Se mueve entre días, o una semana arriba y abajo.
Page UpPage DownRetrocede o avanza un mes.
HomeEndPasa al inicio o al final de la semana.
EscCancela un rango sin terminar o, si no, cierra el calendario.

El calendario se abre como un diálogo con una cuadrícula de días, cada uno nombrado por completo, como «Friday, November 20, 2026». La pista describe el input, así que los lectores de pantalla oyen el formato a escribir. Un texto que no es una fecha activa aria-invalid y conserva el valor anterior, y los días no disponibles siguen enfocables para que quien usa el teclado los oiga.

El disparador, el popup y las flechas tienen nombres en inglés. Pasa aria-label a cada uno para traducirlos. Los inputs usan una fuente de 16px en pantallas táctiles para que iOS no haga zoom, y el popup solo se escala al entrar cuando se permite el movimiento.

Cada parte envuelve la parte de UI SDK con el mismo nombre y acepta todas sus props, incluidas render y una className que puede ser una función del estado de la parte. DatePickerControl, DatePickerSeparator y DatePickerCalendar son partes de diseño de HextaUI.

PropTipoPredeterminado
mode
"single" | "range""single"
value
string | [string, string] | null–
defaultValue
string | [string, string] | null–
onValueChange
(value, details) => void–
min
string–
max
string–
unavailableFechas ISO, { from, to }, { weekday }, { before }, { after } o una función.
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–
controllerDe createDatePickerController().
DatePickerController–
AtributoDescripción
data-slot="date-picker"Selecciona el selector de fechas en CSS.
data-openPresente mientras está abierto.
data-invalidPresente mientras un input tiene texto que no es una fecha.
data-disabledPresente cuando está deshabilitado.
data-readonlyPresente cuando es de solo lectura.
PropTipoPredeterminado
endLa convierte en el final de un rango.
booleanfalse
PropTipoPredeterminado
childrenPor defecto, un icono de calendario.
ReactNode–
aria-label
string"Choose date"
PropTipoPredeterminado
childrenPor defecto, un icono X llamado Clear. Se oculta mientras no hay nada que borrar.
ReactNode–
PropTipoPredeterminado
childrenPor defecto, un DatePickerCalendar.
ReactNode–
align
Align"start"
sideOffset
number6
collisionPadding
number8
AtributoDescripción
data-slot="date-picker-content"Para apuntar al popup en CSS.
--cell-sizeEl tamaño de cada día, mayor en pantallas táctiles.
PropTipoPredeterminado
monthsCuántas cuadrículas mostrar, una junto a otra.
number1
PropTipoPredeterminado
value
string | [string, string]–
render
ReactElement | (props, state) => ReactElement–
AtributoDescripción
data-slot="calendar-day"Selecciona un día en CSS.
data-todayPresente en el día de hoy.
data-range-startPresente donde empieza un rango.
data-range-endPresente donde termina un rango.
data-unavailablePresente en un día que no se puede elegir.
data-outsidePresente en un día del mes siguiente o anterior.