HextaUI

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

Date picker

Um seletor de datas sobre o UI SDK da Vercel em que as pessoas digitam a data no próprio idioma ou a escolhem, com intervalos, predefinições e 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

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

Os valores são strings de data ISO como 2026-11-20, sem hora nem fuso horário, então você não precisa de uma biblioteca de datas. As pessoas digitam na própria ordem de data, como 11/20/2026 ou 20.11.2026, e o campo formata a data ao pressionar Enter ou sair dele. DatePickerContent sem filhos renderiza um calendário estilizado.

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

Com mode="range", adicione um segundo input com end. O calendário fica aberto após a primeira data e fecha após a segunda. months={2} mostra dois meses lado a lado, empilhados em telas pequenas.

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 }

Um DatePickerPreset escolhe seu value e fecha o popup. Ele aparece pressionado enquanto seu valor está selecionado.

"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 e unavailable valem para digitar e escolher. Aqui fins de semana e dois feriados não podem ser escolhidos, e um digitado é marcado como invá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 o formato digitado, a dica e os nomes do calendário. Sem ele, o seletor lê o lang da página. Os botões têm nomes em inglês, então passe aria-label para traduzi-los.

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 }

Com um name, a data é enviada como string ISO. required bloqueia um envio vazio, texto que não é data também bloqueia, e reset volta para 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 }

Um controlador de createDatePickerController() define a data ou abre o calendário de qualquer lugar, e onValueChange informa cada mudança. Deixe value sem definir aqui, para que o calendário possa ir ao mês que o controlador escolhe.

"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 impede digitar e abrir. readOnly ainda abre o calendário para consulta, mas nada pode mudar.

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 }

Com uma localidade árabe dentro de dir=rtl, o campo, as setas e o calendário vão da direita.

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 }
TeclaAção
EnterNo input, lê a data digitada. Em um dia, escolhe-o.
←→↑↓Move entre dias, ou uma semana para cima e para baixo.
Page UpPage DownVolta ou avança um mês.
HomeEndMove para o início ou o fim da semana.
EscCancela um intervalo inacabado ou, senão, fecha o calendário.

O calendário abre como um diálogo com uma grade de dias, cada um nomeado por completo, como “Friday, November 20, 2026”. A dica descreve o input, então leitores de tela ouvem o formato a digitar. Texto que não é data define aria-invalid e mantém o valor anterior, e dias indisponíveis continuam focáveis para que quem usa teclado ainda os ouça.

O gatilho, o popup e as setas têm nomes em inglês. Passe aria-label a cada um para traduzi-los. Os inputs usam fonte de 16px em telas sensíveis ao toque para o iOS não dar zoom, e o popup só entra com escala quando o movimento é permitido.

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. DatePickerControl, DatePickerSeparator e DatePickerCalendar são partes de layout do HextaUI.

PropTipoPadrão
mode
"single" | "range""single"
value
string | [string, string] | null–
defaultValue
string | [string, string] | null–
onValueChange
(value, details) => void–
min
string–
max
string–
unavailableDatas ISO, { from, to }, { weekday }, { before }, { after } ou uma função.
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–
AtributoDescrição
data-slot="date-picker"Seleciona o seletor de datas no CSS.
data-openPresente enquanto aberto.
data-invalidPresente enquanto um input tem texto que não é uma data.
data-disabledPresente quando desabilitado.
data-readonlyPresente quando somente leitura.
PropTipoPadrão
endTorna-o o fim de um intervalo.
booleanfalse
PropTipoPadrão
childrenPor padrão, um ícone de calendário.
ReactNode–
aria-label
string"Choose date"
PropTipoPadrão
childrenPor padrão, um ícone X chamado Clear. Ele some enquanto não há nada para limpar.
ReactNode–
PropTipoPadrão
childrenPor padrão, um DatePickerCalendar.
ReactNode–
align
Align"start"
sideOffset
number6
collisionPadding
number8
AtributoDescrição
data-slot="date-picker-content"Para selecionar o popup no CSS.
--cell-sizeO tamanho de cada dia, maior em telas sensíveis ao toque.
PropTipoPadrão
monthsQuantas grades mostrar, lado a lado.
number1
PropTipoPadrão
value
string | [string, string]–
render
ReactElement | (props, state) => ReactElement–
AtributoDescrição
data-slot="calendar-day"Seleciona um dia no CSS.
data-todayPresente no dia de hoje.
data-range-startPresente onde um intervalo começa.
data-range-endPresente onde um intervalo termina.
data-unavailablePresente em um dia que não pode ser escolhido.
data-outsidePresente em um dia do mês seguinte ou anterior.