コンテンツにスキップ

カレンダー

hc-calendar は、日付を選ぶためのスタイル済みインライン月グリッド — shadcn の Calendar に相当します。installCalendar.hc-calendar コンテナへグリッドを描画し、WAI-ARIA デートピッカーの キーボードモデルを配線します。作者が書くのは data-* 設定つきの コンテナだけです。ビヘイビアがヘッダー、ローカライズされた曜日行、 ロービングタブインデックスで管理される <td role="gridcell"> の 6 週グリッドを描画します。

import { installCalendar } from '@hypermedia-components/core';
installCalendar(); // idempotent; returns an uninstaller

ゼロ設定の @hypermedia-components/core/behaviors エントリは自動で インストールし、htmx でスワップされた内容も自動で拾います。

設定はすべてコンテナの data-* 属性です:

属性用途
data-value選択された日付、ISO YYYY-MM-DD。表示される月も決めます。
data-min / data-max選択可能な範囲(ISO)。範囲外の日は aria-disabled
data-first-day週の最初の曜日: 0 = 日曜(デフォルト)… 6 = 土曜。
data-locale月 / 曜日名のための BCP-47 ロケール。<html lang>、次にブラウザデフォルトへフォールバック。
data-name設定すると、ビヘイビアが選択された ISO 値を持つ隠し <input name="…"> を維持し、カレンダーがフォームでシリアライズされます。
data-target外部フィールドの CSS セレクタ。選択のたびにカレンダーがそこへ値を書き(input / change を発火)、内包するポップオーバーを閉じます — フィールドごとの JavaScript なしカスタム日付フィールド。カレンダーの初期選択もそのフィールドの値からシードされるため、日付は(input に)一度だけ設定して data-value は省きます。
data-nav"select" は月 / 年のタイトルをドロップダウンピッカーに差し替えます(月 / 年ナビゲーションを参照)。

月と曜日の名前は Intl.DateTimeFormat から来ます。週の最初の曜日は data-first-day から取られます(まだ Baseline でない Intl.LocalegetWeekInfo() は使いません)。

ヘッダーはデフォルトで前月 / 翌月の矢印を表示します。 data-nav="select" を足すとタイトルが月と年のドロップダウンに 替わり、遠い月 / 年へ一足でジャンプできます:

年の範囲は data-mindata-max が設定されていればそれに、なければ フォーカス中の年 ±10 に広がります。ドロップダウンのラベルは翻訳可能 (calendar.month / calendar.year)で、矢印もその横で機能し続け ます。

日セルにフォーカスして:

キー動作
/ 前 / 翌日
/ 前 / 翌週
Home / End週の最初 / 最後の日
PageUp / PageDown前 / 翌月
Shift+PageUp / Shift+PageDown前 / 翌年
Enter / Spaceフォーカス中の日を選択

月の端を越えて移動すると、隣の月が対象の日にフォーカスした状態で 再描画されます。グリッドは単一の Tab ストップ(ロービングタブ インデックス)です。data-min / data-max の外の日はフォーカス可能な まま、選択はできません。

日付を選ぶと、コンテナ上でバブリングする hc:calendarchange が発火し、 data-value が更新されます:

calendar.addEventListener('hc:calendarchange', (e) => {
const { value, date } = e.detail; // value: 'YYYY-MM-DD', date: Date
});

data-mode="range" を足すと開始日と終了日を選べます。最初のクリック (または Enter)が開始を、次が終了を設定し(開始 ≤ 終了に なるよう自動入れ替え)、3 回目は新しい範囲を始めます。2 つ目の端を 選んでいる間、暫定の帯がポインタまたはキーボードフォーカスの下で プレビューされます。試してみてください — 下で新しい開始日と終了日を 選べます:

両端の間の日は data-in-range を運びます(data-range-start / data-range-end マーカーと、選択中の data-range-preview* つき)ので、 帯をテーマできます。各変更は hc:calendarrangechange を発火します:

calendar.addEventListener('hc:calendarrangechange', (e) => {
const { start, end, startDate, endDate } = e.detail;
// start / end: 'YYYY-MM-DD' (end is null until the second pick)
});

data-value"START/END" になり、data-name="stay" があれば カレンダーは隠し input を 2 つstay-startstay-end — 書くため、範囲はクライアントサイドの状態なしで htmx 向けに シリアライズされます:

<!-- submits stay-start=2026-05-10 & stay-end=2026-05-14 -->

単一日付モードがデフォルトのままです。(複数月の横並びと週番号は スコープ外のままです。)

data-name を設定すると、カレンダーは隠し input 経由でネイティブ コントロールのようにシリアライズされます — <input name> とまったく 同様に、囲んでいる <form> と一緒に送信されます。下で日付を選んで、 送信値が更新されるのを見てください:

Submits due=2026-05-15

カスタム日付フィールドとして

Section titled “カスタム日付フィールドとして”

ネイティブの日付コントロールが好みでない? これが公認の日付 フィールドパターンです: hc-field の一連の マークアップで、見えるフィールドは hc-input、後置の ボタンがポップオーバーの 中のカレンダーを開き、日を選ぶと入力が埋まってポップオーバーが閉じ ます。素のマークアップなので、コードジェネレーターは日付カラムに そのまま出力でき、 マークアップのバージョニングポリシーの 下で安定しています。

カレンダーは data-target でフィールドへ向けます — 値の書き込みと ポップオーバーの閉鎖はビヘイビアがやってくれます。フィールドごとの JavaScript なしなので、日付フィールドの多いページも純粋なマーク アップのままです。初期の日付は input に(value="…" で)一度だけ 設定してください。カレンダーがそれを継承するため、同期し続けるべき data-value はありません。

Format: YYYY-MM-DD

ポップオーバーは installPopover(data-side / data-align)経由で トリガーの下にアンカーされます。こうしたフィールドは好きなだけ 足せます — それぞれが自身の data-target を持つただのマークアップ です。

フォームの name を運ぶのは誰か

Section titled “フォームの name を運ぶのは誰か”

見える inputname を運び、値を送信します(due=…)— カレンダーは data-target を通じてそれを駆動し、input / change を 発火するため、バリデーションや htmx トリガーはすべての選択を観測 します。ここでカレンダーにも data-name を設定しないでください: 同じ値を持つ 2 つ目の隠しコントロールが描画され、フィールドが二重に 送信されてしまいます。data-name は、フォームに直接置かれた 単体のカレンダーのためのものです(上の フォーム統合を参照)。

name が hc-field 内の本物の input に住んでいるため、 field-errors レシピは そのまま機能します: data-field="due" を持つサーバの項目は input を 見つけ、このフィールドのエラースロットへメッセージを書き、input に aria-invalid の印を付けます。

公認の形は readonly です: カレンダーが唯一の編集者なので入力と グリッドが食い違うことはなく、手入力された日付のクライアントサイド パースもありません(readonly の input は送信されます。ブロック されるのは入力だけです)。入力を許すために readonly を外すなら、 カレンダーはフィールドを(インストール時に)一度だけ読むことを 知っておいてください — 後から入力されたテキストから再シードは しません。入力された値は、field-errors レシピなどでサーバサイド バリデーションしてください。

  • トリガーは普通の popovertarget ボタンです: Enter / Space でポップオーバーが開きます。続く Tab は カレンダーのヘッダー(前月 / 翌月)を歩き、グリッドに着地します — 選択中の日にロービングタブインデックスが載る単一のタブストップ です。
  • グリッド内では完全なカレンダーのキーボードモデルが 適用されます。Enter / Space がフォーカス中の 日を選び、入力が埋まってポップオーバーが閉じます。フォーカスは トリガーボタンへ戻ります(ネイティブ popover のフォーカス復元)。
  • Esc とライトディスミスは、フィールドを変えずに ポップオーバーを閉じます — どちらもネイティブ popover の挙動です。

これらの主張は、まさにこのマークアップに対するブラウザテスト (test-browser/datefield.spec.mjs)で固定されており、生成コードに とってパターンは安定しています。

data-locale<html lang> にフォールバックし、それがサーバで ローカライズされたページの意図されたパスです: lang を一度描画すれば すべての日付フィールドが従い、フィールドごとの属性は不要です。 ビヘイビアが注入するラベル(前月 / 翌月、グリッドのフォールバック名)は i18n カタログから来ます。 見える <label>、トリガーの aria-label、カレンダーの aria-label は、マークアップであなたがローカライズします。

変更イベントから取得します — 例: 選ばれた日の空き状況を読み込む:

<div class="hc-calendar"
data-name="day"
data-hx-get="/availability"
data-hx-trigger="hc:calendarchange"
data-hx-include="this"
data-hx-target="#slots"
aria-label="Pick a day"></div>

変更イベントにインラインで反応します — htmx が data-hx-trigger で 駆動できるのと同じイベントです:

<div class="hc-calendar" aria-label="Pick a date"
_="on hc:calendarchange put event.detail.value into #due"></div>
<output id="due"></output>

さらなるパターン: Hyperscript → コンポーネントイベントへの反応

  • グリッドは WAI-ARIA APG のデートピッカーパターンに従います: <td role="gridcell"> の日セルを持つ role="grid" のテーブル、 ロービングタブインデックス、選ばれた日の aria-selected、範囲外の 日の aria-disabled。各セルの aria-label は完全なローカライズ済み 日付です。
  • 月のタイトルは aria-live="polite" 領域なので、月の変更が読み上げ られます。
  • コンテナには常にアクセシブルな名前(aria-label または aria-labelledby)を与えてください。
トークンパス用途
calendar.bg / fg / border / radius / padding面。
calendar.title-*月のタイトル。
calendar.nav-*前 / 次ボタン。
calendar.weekday-*曜日のヘッダー行。
calendar.day-size / day-radius / day-font-size / day-fg日セル。
calendar.day-hover-bg日のホバー。
calendar.day-selected-bg / day-selected-fg選択された日(data-color に追従)。
calendar.day-range-bg / day-range-preview-bg範囲モードの両端間のバンド / 選択中の仮プレビューバンド。
calendar.day-today-border今日のリング。
calendar.day-outside-fg / day-disabled-fg隣接月 / 範囲外の日。
生成される CSS 変数を表示
--hc-calendar-bg | -fg | -border | -radius | -padding
--hc-calendar-title-font-size | -title-font-weight
--hc-calendar-nav-size | -nav-fg | -nav-hover-bg | -nav-radius
--hc-calendar-weekday-fg | -weekday-font-size | -weekday-font-weight
--hc-calendar-day-size | -day-radius | -day-font-size | -day-fg
--hc-calendar-day-hover-bg | -day-selected-bg | -day-selected-fg
--hc-calendar-day-range-bg | -day-range-preview-bg
--hc-calendar-day-today-border | -day-outside-fg | -day-disabled-fg

複数月の横並び、週番号、時刻の選択、非グレゴリオ暦は先送りです。 範囲選択(範囲選択を参照)と月 / 年のドロップダウン ナビゲーション(月 / 年ナビゲーションを 参照)はサポート済みです。JS 不要のネイティブフィールドは デートピッカーを 参照してください。

  • デートピッカー — ネイティブ <input type="date"> のスキン。非 JS のベースライン。
  • ポップオーバー — カレンダーと組み合わせてドロップダウン日付フィールドを構築。