HextaUI

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

Date input

Un campo de fecha sobre el UI SDK de Vercel que se escribe parte por parte en el orden del idioma, con hora opcional, mínimo y máximo, días no disponibles y un valor ISO.

Depende de
  • @ui-sdk/core
  • @ui-sdk/react
import { IconCalendar } from "@tabler/icons-react"

import { Label } from "@/components/ui/label"
import { DateInput, DateInputSegments } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputDemo() {
  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="birthday">Birthday</Label>
      <DateInput aria-labelledby="birthday" name="birthday">
        <IconCalendar aria-hidden="true" />
        <DateInputSegments />
      </DateInput>
    </div>
  )
}

export { UiSdkDateInputDemo }
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-date-input.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 { DateInput } from "@/components/ui/ui-sdk-date-input"
<Label id="start-date">Start date</Label>
<DateInput aria-labelledby="start-date" defaultValue="2026-11-02" />

Usa un campo de fecha para fechas que se saben de memoria, como un cumpleaños. El valor es una fecha ISO como "2026-11-02", y sigue siendo null hasta que todas las partes tienen algo. Cada parte se llena al escribir y avanza, el día se mantiene dentro de su mes y se puede pegar una fecha completa en cualquier parte. Para elegir en un calendario, usa el Date Picker.

El campo es un grupo, así que nómbralo con aria-labelledby o aria-label. Al hacer clic en cualquier punto de la caja se enfoca la parte más cercana.

DateInput
└── DateInputSegments
    └── DateInputSegment

DateInput muestra sus partes por sí solo. Añade DateInputSegments tú mismo para poner un icono u otro contenido a su lado.

granularity con "hour", "minute" o "second" añade la hora, y el valor pasa a ser una fecha y hora locales como "2026-11-02T14:30".

import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputDateTime() {
  return (
    <div className="flex w-full max-w-60 flex-col gap-2">
      <Label id="departure">Departure</Label>
      <DateInput
        aria-labelledby="departure"
        granularity="minute"
        defaultValue="2026-11-02T14:30"
        locale="en-US"
      />
    </div>
  )
}

export { UiSdkDateInputDateTime }

Una fecha antes de min o después de max marca el campo como no válido, y su formulario no se envía.

"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

const min = "2026-01-01"
const max = "2026-12-31"

function UiSdkDateInputMinMax() {
  const [date, setDate] = React.useState<string | null>("2026-06-15")
  const outside = date !== null && (date < min || date > max)

  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="check-in">Check-in</Label>
      <DateInput
        aria-labelledby="check-in"
        aria-describedby="check-in-message"
        min={min}
        max={max}
        defaultValue="2026-06-15"
        onValueChange={(value) => setDate(value)}
      />
      <p
        id="check-in-message"
        className={
          outside
            ? "m-0 text-xs text-destructive"
            : "m-0 text-xs text-muted-foreground"
        }
      >
        {outside ? "Pick a day in 2026." : "Any day in 2026."}
      </p>
    </div>
  )
}

export { UiSdkDateInputMinMax }

unavailable acepta fechas ISO, rangos { from, to }, días de la semana o una función, y una fecha que coincida no es válida.

"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

function isWeekend(date: string) {
  const day = new Date(`${date}T12:00`).getDay()
  return day === 0 || day === 6
}

function UiSdkDateInputUnavailable() {
  const [date, setDate] = React.useState<string | null>("2026-11-02")
  const weekend = date !== null && isWeekend(date)

  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="delivery-date">Delivery date</Label>
      <DateInput
        aria-labelledby="delivery-date"
        aria-describedby="delivery-date-message"
        defaultValue="2026-11-02"
        unavailable={{ weekday: [0, 6] }}
        onValueChange={(value) => setDate(value)}
      />
      <p
        id="delivery-date-message"
        className={
          weekend
            ? "m-0 text-xs text-destructive"
            : "m-0 text-xs text-muted-foreground"
        }
      >
        {weekend
          ? "We don’t deliver on weekends."
          : "We deliver Monday to Friday."}
      </p>
    </div>
  )
}

export { UiSdkDateInputUnavailable }

locale fija el orden y los separadores. Los dígitos son siempre del 0 al 9 y el calendario siempre gregoriano, así que el valor significa la misma fecha en todas partes.

import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

const locales = [
  { locale: "en-GB", label: "Date (en-GB)" },
  { locale: "de-DE", label: "Datum (de-DE)" },
  { locale: "ja-JP", label: "日付 (ja-JP)" },
]

function UiSdkDateInputLocale() {
  return (
    <div className="flex w-full max-w-44 flex-col gap-4">
      {locales.map(({ locale, label }) => (
        <div key={locale} lang={locale} className="flex flex-col gap-2">
          <Label id={`date-${locale}`}>{label}</Label>
          <DateInput
            aria-labelledby={`date-${locale}`}
            locale={locale}
            defaultValue="2026-11-02"
          />
        </div>
      ))}
    </div>
  )
}

export { UiSdkDateInputLocale }

Un input oculto envía la fecha ISO con name, aquí junto a un Time Input. required impide una fecha vacía o a medio escribir, y restablecer vuelve al valor por defecto.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

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

  return (
    <form
      className="flex w-full max-w-72 flex-col gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        setSubmitted(`${data.get("date")} at ${data.get("time")}`)
      }}
    >
      <div className="grid grid-cols-[1fr_auto] gap-2">
        <div className="flex min-w-0 flex-col gap-2">
          <Label id="booking-date">Date</Label>
          <DateInput
            aria-labelledby="booking-date"
            name="date"
            defaultValue="2026-11-02"
            required
          />
        </div>
        <div className="flex flex-col gap-2">
          <Label id="booking-time">Time</Label>
          <TimeInput
            aria-labelledby="booking-time"
            name="time"
            defaultValue="09:30"
            required
          />
        </div>
      </div>
      <div className="flex items-center gap-3">
        <Button type="submit" size="sm">
          Book
        </Button>
        <Button type="reset" size="sm" variant="ghost">
          Reset
        </Button>
      </div>
      <p className="m-0 text-sm text-muted-foreground tabular-nums">
        {submitted ? `Submitted ${submitted}` : "Nothing submitted yet."}
      </p>
    </form>
  )
}

export { UiSdkDateInputForm }

createDateInputController() fija, vacía o enfoca el campo desde cualquier sitio. Enfoca una parte por su unidad, como "year".

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import {
  DateInput,
  createDateInputController,
} from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputController() {
  const [controller] = React.useState(createDateInputController)

  return (
    <div className="flex w-full max-w-60 flex-col gap-2">
      <Label id="publish-on">Publish on</Label>
      <DateInput aria-labelledby="publish-on" controller={controller} />
      <div className="flex items-center gap-2 pt-1">
        <Button
          size="sm"
          variant="outline"
          onClick={() => controller.setValue("2026-12-25")}
        >
          Christmas
        </Button>
        <Button
          size="sm"
          variant="ghost"
          onClick={() => controller.focus("year")}
        >
          Edit year
        </Button>
        <Button size="sm" variant="ghost" onClick={() => controller.clear()}>
          Clear
        </Button>
      </div>
    </div>
  )
}

export { UiSdkDateInputController }

Un campo desactivado se salta al enfocar y no se envía.

import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputDisabled() {
  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="start-date-disabled">Start date</Label>
      <DateInput
        aria-labelledby="start-date-disabled"
        defaultValue="2026-11-02"
        disabled
      />
    </div>
  )
}

export { UiSdkDateInputDisabled }

Un campo de solo lectura recibe el foco y se envía, pero las teclas y la escritura no lo cambian.

import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputReadOnly() {
  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="signed-on">Signed on</Label>
      <DateInput
        aria-labelledby="signed-on"
        defaultValue="2026-11-02"
        readOnly
      />
    </div>
  )
}

export { UiSdkDateInputReadOnly }

Las partes mantienen el orden del idioma, y las flechas las siguen en pantalla.

import { IconCalendar } from "@tabler/icons-react"

import { Label } from "@/components/ui/label"
import { DateInput, DateInputSegments } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputRtl() {
  return (
    <div dir="rtl" lang="ar" className="flex w-full max-w-44 flex-col gap-2">
      <Label id="rtl-date">تاريخ البدء</Label>
      <DateInput
        aria-labelledby="rtl-date"
        locale="ar-EG"
        defaultValue="2026-11-02"
      >
        <IconCalendar aria-hidden="true" />
        <DateInputSegments />
      </DateInput>
    </div>
  )
}

export { UiSdkDateInputRtl }
KeyAcción
0–9Rellena la parte y pasa a la siguiente.
/.Pasa a la siguiente parte.
↑↓Cambia la parte en uno, dando la vuelta. Una parte vacía empieza en hoy.
PageUpPageDownCambia más: un año en 5, un mes en 3, un día en 7.
HomeEndVa al primer o último valor de la parte.
←→Pasa a la parte anterior o siguiente.
BackspaceBorra el último dígito, luego vacía la parte y después retrocede.
EnterEnvía el formulario con lo escrito.

La raíz es un grupo. Cada parte es un spinbutton nombrado por su unidad, como “month”, con su valor y su rango, y una parte vacía se lee como “Empty”. El texto entre partes queda oculto para los lectores de pantalla.

Las partes son inputs reales, así que los móviles muestran un teclado numérico y se puede escribir antes de que se ejecute el script de la página. Crecen a 16px en pantallas táctiles para que los móviles no hagan zoom, y la parte enfocada se resalta en lugar de mostrar un cursor.

Cada parte envuelve la parte de UI SDK del mismo nombre y acepta todas sus props, incluidas render y un className que puede ser una función del estado de la parte.

PropTipoPredeterminado
value
string | null–
defaultValue
string | null–
onValueChangeSe ejecuta cuando todas las partes tienen algo, y con null cuando una se vacía.
(value: string | null, details) => void–
granularity
"day" | "hour" | "minute" | "second""day"
hourCyclePor defecto, el reloj del idioma.
12 | 24–
min
string–
max
string–
unavailableFechas ISO, rangos { from, to }, { weekday } o una función.
DateMatchers–
localePor defecto, el idioma de la página.
string–
invalid
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
required
booleanfalse
name
string–
form
string–
controllerDe createDateInputController().
DateInputController–
childrenPor defecto, DateInputSegments.
ReactNode | (state) => ReactNode–
render
ReactElement | (props, state) => ReactElement–
AtributoDescripción
data-slot="date-input"Para apuntar al campo de fecha en CSS.
data-invalidPresente cuando la fecha está fuera de min y max, no está disponible, o invalid está activo.
data-emptyPresente mientras todas las partes están vacías.
data-disabledPresente cuando está deshabilitado.
data-readonlyPresente cuando es de solo lectura.
data-requiredPresente cuando es obligatorio.

Muestra un DateInputSegment por cada parte, en el orden del idioma, con el texto entre ellas.

PropTipoPredeterminado
segmentUno de los segments del estado de la raíz.
DateInputSegmentData–
render
ReactElement | (props, state) => ReactElement–
AtributoDescripción
data-slot="date-input-segment"Para apuntar a una parte en CSS.
data-typeyear, month, day, hour, minute, second, dayPeriod, o literal para el texto entre partes.
data-emptyPresente mientras la parte está vacía.