HextaUI

Date picker

押すとポップオーバーでカレンダーを開くボタンです。スマートフォンではボトムシートになり、単一の日付も範囲も選べます。

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

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

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

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

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

640px より狭い画面では、カレンダーはポップオーバーではなくボトムシートで開くため、日付はタップしやすい大きさに保たれます。

制御

value と onValueChange を渡します。クリアされた値は null です。カレンダーは常に選択された日付の月で開き、日付を選ぶと閉じてフォーカスがボタンに戻ります。

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

範囲

<DateRangePicker /> は、大きな画面では 2 か月分を表示します。最初のクリックは、すでに範囲が設定されていても常に新しい範囲を開始し、2 回目のクリックは、どちらの順序でも範囲を確定してピッカーを閉じます。

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

無効な日

calendarProps.disabled に、react-day-picker の任意のマッチャーを渡します。この例では、未来の平日だけを選べます。

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

月と年のドロップダウン

誕生日のように遠い日付には、startMonth と endMonth を指定した captionLayout: "dropdown" を設定します。トリガーには、その id を指す <label /> でラベルを付けます。

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

フォーム内

name を指定すると、単一の日付は 2026-10-14、範囲は 2026-10-20/2026-10-24 の形式で送信されます。空のピッカーは空文字列を送信します。

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

無効

無効なトリガーでも、値を表示できます。

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

狭いコンテナ

トリガーがコンテナを超えて広がることはありません。長いラベルは折り返されず、省略されます。

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

シートの中

ピッカーはシートの上に重なり、Escape はピッカーだけを閉じます。

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

右から左

トリガーのラベル用に locale を、カレンダー用に calendarProps で dir: "rtl" を持つ react-day-picker のロケールを渡します。矢印キーは方向に従います。

"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>
  )
}
キーアクション
EnterSpaceトリガーでは、ピッカーを開き、選択された日または今日にフォーカスします。日付では、その日を選択します。
←→前日または翌日に移動します。
↑↓前週または翌週の同じ曜日に移動します。
Page UpPage Down前月または翌月に移動します。
ShiftPage Up前年に移動します。Shift Page Down で翌年に移動します。
HomeEnd週の最初または最後に移動します。
Escピッカーを閉じ、フォーカスをトリガーに戻します。
  • ポップオーバーには title でラベルが付き、ボトムシートではそれが見出しとして表示されます。placeholder とあわせて翻訳してください。
  • トリガー自体には、表示されるラベルがありません。<label /> と組み合わせるか、aria-label を渡してください。
  • 無効な日はキーボードではスキップされ、利用不可として通知されます。

どちらのピッカーも以下の props を共有し、ポップオーバーまたはボトムシート内の <Calendar /> を開く <Button /> のトリガーをレンダリングします。

プロパティ型デフォルト
value
Date | null–
defaultValue
Date | nullnull
onValueChange
(value: Date | null) => void–
placeholder
string"Pick a date"
titleポップオーバーのアクセシブルな名前、およびシートの見出し。
string"Select a date"
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean) => void–
localeトリガーのラベルを Intl.DateTimeFormat で整形します。カレンダーも翻訳するには、calendarProps に react-day-picker のロケールを渡します。
string"en-US"
clearable値が設定されると、カレンダーの下に Clear ボタンを追加します。
booleanfalse
clearLabel
string"Clear"
name非表示の input をレンダリングし、値をフォームと一緒に送信します。
string–
calendarPropsmode、selected、onSelect、required、numberOfMonths、autoFocus を除き、カレンダーに渡されます。無効な日、captionLayout、startMonth、endMonth、locale、dir に使います。
CalendarProps–
variantトリガーは Button で、その他の props も受け付けます。
Button variant"outline"
disabled
booleanfalse
classNameデフォルトで w-60 のトリガーに適用されます。
string–
プロパティ型デフォルト
value
{ from: Date; to?: Date } | null–
defaultValue
{ from: Date; to?: Date } | nullnull
onValueChange2 日目が選ばれたときに呼ばれます。
(value: DateRange | null) => void–
placeholder
string"Pick a date range"
title
string"Select dates"
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean) => void–
localeトリガーのラベルを Intl.DateTimeFormat で整形します。カレンダーも翻訳するには、calendarProps に react-day-picker のロケールを渡します。
string"en-US"
clearable値が設定されると、カレンダーの下に Clear ボタンを追加します。
booleanfalse
clearLabel
string"Clear"
name非表示の input をレンダリングし、値をフォームと一緒に送信します。
string–
calendarPropsmode、selected、onSelect、required、numberOfMonths、autoFocus を除き、カレンダーに渡されます。無効な日、captionLayout、startMonth、endMonth、locale、dir に使います。
CalendarProps–
variantトリガーは Button で、その他の props も受け付けます。
Button variant"outline"
disabled
booleanfalse
classNameデフォルトで w-60 のトリガーに適用されます。
string–
属性説明
data-slot="date-picker-trigger"トリガーボタン。
data-empty値がない間、トリガーに付与されます。プレースホルダーを控えめな色のテキストで表示します。
data-popup-openピッカーが開いている間、トリガーに付与されます。
data-slot="date-picker-content"カレンダーをポップオーバーまたはシートで包みます。
data-slot="date-picker-clear"Clear ボタン。

使用しているブロック

Date picker の上に構築されるブロック。