HextaUI

Date picker

Un botón que abre un calendario en un popover, o en una hoja inferior en móviles, para fechas únicas y rangos.

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

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

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

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

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

En pantallas de menos de 640px el calendario se abre en una hoja inferior en lugar de un popover, para que los días sigan siendo lo bastante grandes para tocarlos.

Controlado

Pasa value y onValueChange. Los valores borrados son null. El calendario siempre se abre en el mes de la fecha seleccionada, y elegir un día lo cierra y devuelve el foco al botón.

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

Rango

<DateRangePicker /> muestra dos meses en pantallas más grandes. El primer clic siempre inicia un rango nuevo, aunque ya haya uno definido, y el segundo clic lo termina en cualquier orden y cierra el selector.

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

Días deshabilitados

Pasa cualquier matcher de react-day-picker como calendarProps.disabled. Aquí solo se pueden elegir días laborables futuros.

"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] }],
      }}
    />
  )
}

Desplegables de mes y año

Para fechas lejanas como un cumpleaños, define captionLayout: "dropdown" con un startMonth y un endMonth. Etiqueta el trigger con un <label /> que apunte a su id.

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

En un formulario

Con name, una sola fecha se envía como 2026-10-14 y un rango como 2026-10-20/2026-10-24. Los selectores vacíos envían una cadena vacía.

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

Deshabilitado

Un trigger deshabilitado puede seguir mostrando un valor.

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

Contenedor estrecho

El trigger nunca crece más allá de su contenedor. Las etiquetas largas se truncan en lugar de ajustarse.

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

Dentro de una hoja

El selector se apila sobre el sheet, y Escape cierra solo el selector.

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

De derecha a izquierda

Pasa locale para la etiqueta del trigger y un locale de react-day-picker con dir: "rtl" en calendarProps para el calendario. Las teclas de flecha siguen la dirección.

"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>
  )
}
KeyAcción
EnterSpaceEn el trigger, abre el selector y da foco al día seleccionado, o a hoy. En un día, lo selecciona.
←→Pasa al día anterior o siguiente.
↑↓Pasa al mismo día de la semana anterior o siguiente.
Page UpPage DownPasa al mes anterior o siguiente.
ShiftPage UpPasa al año anterior. Shift Page Down pasa al siguiente.
HomeEndPasa al inicio o al final de la semana.
EscCierra el selector y devuelve el foco al trigger.
  • El popover se etiqueta con title, y la hoja inferior lo muestra como encabezado. Tradúcelo junto con placeholder.
  • El trigger no tiene una etiqueta visible propia. Combínalo con un <label /> o pasa aria-label.
  • El teclado omite los días deshabilitados y se anuncian como no disponibles.

Ambos selectores comparten las props de abajo y renderizan un trigger <Button /> con un <Calendar /> en un popover o una hoja inferior.

PropTipoPredeterminado
value
Date | null–
defaultValue
Date | nullnull
onValueChange
(value: Date | null) => void–
placeholder
string"Pick a date"
titleNombre accesible del popover, encabezado del sheet.
string"Select a date"
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean) => void–
localeDa formato a la etiqueta del trigger con Intl.DateTimeFormat. Pasa un locale de react-day-picker en calendarProps para traducir también el calendario.
string"en-US"
clearableAñade un botón Clear bajo el calendario una vez que hay un valor.
booleanfalse
clearLabel
string"Clear"
nameRenderiza un input oculto para que el valor se envíe con su formulario.
string–
calendarPropsSe pasa al calendario, excepto mode, selected, onSelect, required, numberOfMonths y autoFocus. Úsalo para días deshabilitados, captionLayout, startMonth, endMonth, locale y dir.
CalendarProps–
variantEl trigger es un Button y también acepta sus otras props.
Button variant"outline"
disabled
booleanfalse
classNameSe aplica al trigger, que es w-60 por defecto.
string–
PropTipoPredeterminado
value
{ from: Date; to?: Date } | null–
defaultValue
{ from: Date; to?: Date } | nullnull
onValueChangeSe llama una vez que se elige el segundo día.
(value: DateRange | null) => void–
placeholder
string"Pick a date range"
title
string"Select dates"
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean) => void–
localeDa formato a la etiqueta del trigger con Intl.DateTimeFormat. Pasa un locale de react-day-picker en calendarProps para traducir también el calendario.
string"en-US"
clearableAñade un botón Clear bajo el calendario una vez que hay un valor.
booleanfalse
clearLabel
string"Clear"
nameRenderiza un input oculto para que el valor se envíe con su formulario.
string–
calendarPropsSe pasa al calendario, excepto mode, selected, onSelect, required, numberOfMonths y autoFocus. Úsalo para días deshabilitados, captionLayout, startMonth, endMonth, locale y dir.
CalendarProps–
variantEl trigger es un Button y también acepta sus otras props.
Button variant"outline"
disabled
booleanfalse
classNameSe aplica al trigger, que es w-60 por defecto.
string–
AtributoDescripción
data-slot="date-picker-trigger"El botón trigger.
data-emptyPresente en el trigger mientras no hay valor. Muestra el placeholder en texto atenuado.
data-popup-openPresente en el trigger mientras el selector está abierto.
data-slot="date-picker-content"Envuelve el calendario en el popover o el sheet.
data-slot="date-picker-clear"El botón Clear.

Usado en bloques

Bloques que se construyen sobre Date picker.