HextaUI

BetaFeito sobre o UI SDK 0.1.0-beta.2 da Vercel. A API pode mudar.

Date input

Um campo de data no UI SDK da Vercel digitado parte por parte na ordem do idioma, com hora opcional, mínimo e máximo, dias indisponíveis e um 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

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

O UI SDK adiciona um pouco de CSS próprio em uma camada de cascata chamada ui-sdk. Declare essa camada no topo da sua folha de estilos global, acima do import do Tailwind, para que suas classes vençam.

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

Use um campo de data para datas que se sabem de cor, como um aniversário. O valor é uma data ISO como "2026-11-02", e continua null até que todas as partes tenham algo. Cada parte se preenche ao digitar e avança, o dia fica dentro do mês, e uma data inteira pode ser colada em qualquer parte. Para escolher num calendário, use o Date Picker.

O campo é um grupo, então nomeie-o com aria-labelledby ou aria-label. Clicar em qualquer ponto da caixa foca a parte mais próxima.

DateInput
└── DateInputSegments
    └── DateInputSegment

DateInput renderiza suas partes sozinho. Adicione DateInputSegments você mesmo para colocar um ícone ou outro conteúdo ao lado.

granularity com "hour", "minute" ou "second" adiciona a hora, e o valor vira data e hora locais 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 }

Uma data antes de min ou depois de max marca o campo como inválido, e o formulário não é enviado.

"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 recebe datas ISO, intervalos { from, to }, dias da semana ou uma função, e uma data que coincida é invá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 define a ordem e os separadores. Os dígitos são sempre de 0 a 9 e o calendário é sempre gregoriano, então o valor significa a mesma data em todo lugar.

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 }

Um input oculto envia a data ISO sob name, aqui ao lado de um Time Input. required impede uma data vazia ou digitada pela metade, e redefinir traz de volta o padrão.

"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() define, limpa ou foca o campo de qualquer lugar. Foque uma parte pela unidade, 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 }

Um campo desativado é pulado pelo foco e não é enviado.

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 }

Um campo somente leitura recebe foco e é enviado, mas teclas e digitação não o alteram.

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 }

As partes mantêm a ordem do idioma, e as setas as seguem na tela.

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 }
TeclaAção
0–9Preenche a parte e passa para a próxima.
/.Vai para a próxima parte.
↑↓Muda a parte em um, dando a volta. Uma parte vazia começa em hoje.
PageUpPageDownMuda mais: ano em 5, mês em 3, dia em 7.
HomeEndVai ao primeiro ou último valor da parte.
←→Vai para a parte anterior ou seguinte.
BackspaceApaga o último dígito, depois esvazia a parte e então volta.
EnterEnvia o formulário com o que foi digitado.

A raiz é um grupo. Cada parte é um spinbutton nomeado pela unidade, como “month”, com seu valor e intervalo, e uma parte vazia é lida como “Empty”. O texto entre as partes fica oculto para leitores de tela.

As partes são inputs reais, então os celulares mostram um teclado numérico e a digitação funciona antes do script da página rodar. Elas crescem para 16px em telas de toque para os celulares não darem zoom, e a parte focada fica destacada em vez de mostrar um cursor.

Cada parte envolve a parte do UI SDK de mesmo nome e aceita todas as suas props, incluindo render e um className que pode ser uma função do estado da parte.

PropTipoPadrão
value
string | null–
defaultValue
string | null–
onValueChangeExecuta quando todas as partes têm algo, e com null quando uma é esvaziada.
(value: string | null, details) => void–
granularity
"day" | "hour" | "minute" | "second""day"
hourCyclePor padrão, o relógio do idioma.
12 | 24–
min
string–
max
string–
unavailableDatas ISO, intervalos { from, to }, { weekday } ou uma função.
DateMatchers–
localePor padrão, o idioma da página.
string–
invalid
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
required
booleanfalse
name
string–
form
string–
controllerDe createDateInputController().
DateInputController–
childrenPor padrão, DateInputSegments.
ReactNode | (state) => ReactNode–
render
ReactElement | (props, state) => ReactElement–
AtributoDescrição
data-slot="date-input"Para mirar o campo de data no CSS.
data-invalidPresente quando a data está fora de min e max, indisponível, ou invalid está definido.
data-emptyPresente enquanto todas as partes estão vazias.
data-disabledPresente quando desabilitado.
data-readonlyPresente quando somente leitura.
data-requiredPresente quando obrigatório.

Renderiza um DateInputSegment para cada parte, na ordem do idioma, com o texto entre elas.

PropTipoPadrão
segmentUm dos segments do estado da raiz.
DateInputSegmentData–
render
ReactElement | (props, state) => ReactElement–
AtributoDescrição
data-slot="date-input-segment"Para mirar uma parte no CSS.
data-typeyear, month, day, hour, minute, second, dayPeriod, ou literal para o texto entre as partes.
data-emptyPresente enquanto a parte está vazia.