HextaUI

ベータVercel の UI SDK 0.1.0-beta.1 上に構築。API は変わる可能性があります。

Date picker

Vercel の UI SDK 上の日付ピッカー。自分の言語で日付を入力するか選べて、範囲、プリセット、ISO 値に対応します。

依存関係
  • @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

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

UI SDK は ui-sdk というカスケードレイヤーに少しだけ独自の CSS を追加します。自分のクラスが優先されるよう、グローバルスタイルシートの先頭、Tailwind の import より上でこのレイヤーを宣言してください。

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

値は 2026-11-20 のような時刻もタイムゾーンもない ISO 日付文字列なので、日付ライブラリは不要です。11/20/2026 や 20.11.2026 のように各自の並びで入力でき、Enter を押すか入力欄を離れると日付が整形されます。子要素のない DatePickerContent はスタイル付きのカレンダーを表示します。

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

mode="range" では end を付けた 2 つ目の入力欄を追加します。カレンダーは 1 つ目の日付の後も開いたままで、2 つ目で閉じます。months={2} は 2 か月を横に並べ、小さな画面では縦に重ねます。

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 }

DatePickerPreset は自分の value を選んでポップアップを閉じます。その値が選択されている間は押された状態で表示されます。

"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、unavailable は入力と選択の両方に適用されます。この例では週末と 2 つの祝日を選べず、入力するとエラーになります。

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 は入力形式、ヒント、カレンダーの名前を決めます。指定しないとページの lang を読みます。ボタンの名前は英語なので、翻訳するには aria-label を渡します。

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 }

name を付けると日付は ISO 文字列として送信されます。required は空の送信を止め、日付でない文字も送信を止めます。リセットすると 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 }

createDatePickerController() のコントローラーで、どこからでも日付を設定したりカレンダーを開いたりでき、onValueChange が変更を毎回知らせます。カレンダーがコントローラーの選んだ月へ移れるよう、ここでは value を指定しません。

"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 は入力も開くことも止めます。readOnly はカレンダーを開いて見られますが、何も変更できません。

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 }

dir=rtl 内でアラビア語ロケールを使うと、入力欄、矢印、カレンダーがすべて右から並びます。

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 }
キーアクション
Enter入力欄では入力した日付を読み取り、日付の上ではその日を選びます。
←→↑↓日ごとに移動し、上下では 1 週間ずつ移動します。
Page UpPage Down1 か月前後に移動します。
HomeEnd週の最初または最後に移動します。
Esc未完成の範囲を取り消し、なければカレンダーを閉じます。

カレンダーは日のグリッドを持つダイアログとして開き、各日は「Friday, November 20, 2026」のように省略せずに読み上げられます。ヒントが入力欄を説明するので、スクリーンリーダーで入力形式が聞こえます。日付でない文字は aria-invalid を設定して以前の値を保ち、選べない日もフォーカスできるのでキーボード利用者にも読み上げられます。

トリガー、ポップアップ、矢印の名前は英語です。翻訳するにはそれぞれに aria-label を渡します。iOS が拡大しないよう、タッチ画面では入力欄に 16px のフォントを使い、ポップアップはモーションが許可されているときだけ拡大して表示されます。

各パーツは同名の UI SDK パーツをラップし、render や、パーツの状態を受け取る関数にもできる className を含むすべての props を受け付けます。DatePickerControl、DatePickerSeparator、DatePickerCalendar は HextaUI のレイアウト用パーツです。

プロパティ型デフォルト
mode
"single" | "range""single"
value
string | [string, string] | null–
defaultValue
string | [string, string] | null–
onValueChange
(value, details) => void–
min
string–
max
string–
unavailableISO 日付、{ from, to }、{ weekday }、{ before }、{ after }、または関数。
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–
controllercreateDatePickerController() で作成します。
DatePickerController–
属性説明
data-slot="date-picker"CSS で日付ピッカーを対象にします。
data-open開いている間存在します。
data-invalid入力欄に日付ではない文字があるあいだ付きます。
data-disabled無効のときに存在します。
data-readonly読み取り専用のときに付与されます。
プロパティ型デフォルト
end範囲の終わりにします。
booleanfalse
プロパティ型デフォルト
children既定はカレンダーアイコンです。
ReactNode–
aria-label
string"Choose date"
プロパティ型デフォルト
children既定は Clear という名前の X アイコンです。消すものがない間は隠れます。
ReactNode–
プロパティ型デフォルト
children既定は DatePickerCalendar です。
ReactNode–
align
Align"start"
sideOffset
number6
collisionPadding
number8
属性説明
data-slot="date-picker-content"CSS でポップアップを指定するときに使います。
--cell-size各日のサイズ。タッチ画面では大きくなります。
プロパティ型デフォルト
months横に並べて表示するグリッドの数。
number1
プロパティ型デフォルト
value
string | [string, string]–
render
ReactElement | (props, state) => ReactElement–
属性説明
data-slot="calendar-day"CSS で日を対象にします。
data-today今日の日付に付きます。
data-range-start範囲の始まりに付きます。
data-range-end範囲の終わりに付きます。
data-unavailable選べない日に付きます。
data-outside前月または翌月の日に付きます。