カレンダー
hc-calendar は、日付を選ぶためのスタイル済みインライン月グリッド —
shadcn の Calendar に相当します。installCalendar が
.hc-calendar コンテナへグリッドを描画し、WAI-ARIA デートピッカーの
キーボードモデルを配線します。作者が書くのは data-* 設定つきの
コンテナだけです。ビヘイビアがヘッダー、ローカライズされた曜日行、
ロービングタブインデックスで管理される <td role="gridcell"> の
6 週グリッドを描画します。
基本の HTML
Section titled “基本の HTML”<div class="hc-calendar" data-value="2026-05-15" aria-label="Pick a date"></div>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.Locale の getWeekInfo() は使いません)。
<div class="hc-calendar" data-value="2026-05-15" data-first-day="1" data-locale="en-GB" data-min="2026-05-04" data-max="2026-06-20" aria-label="Pick a date (Mon start, ranged)"></div>月 / 年ナビゲーション
Section titled “月 / 年ナビゲーション”ヘッダーはデフォルトで前月 / 翌月の矢印を表示します。
data-nav="select" を足すとタイトルが月と年のドロップダウンに
替わり、遠い月 / 年へ一足でジャンプできます:
<div class="hc-calendar" data-value="2026-05-15" data-nav="select" aria-label="Pick a date"></div>年の範囲は data-min…data-max が設定されていればそれに、なければ
フォーカス中の年 ±10 に広がります。ドロップダウンのラベルは翻訳可能
(calendar.month / calendar.year)で、矢印もその横で機能し続け
ます。
日セルにフォーカスして:
| キー | 動作 |
|---|---|
← / → | 前 / 翌日 |
↑ / ↓ | 前 / 翌週 |
Home / End | 週の最初 / 最後の日 |
PageUp / PageDown | 前 / 翌月 |
Shift+PageUp / Shift+PageDown | 前 / 翌年 |
Enter / Space | フォーカス中の日を選択 |
月の端を越えて移動すると、隣の月が対象の日にフォーカスした状態で
再描画されます。グリッドは単一の Tab ストップ(ロービングタブ
インデックス)です。data-min / data-max の外の日はフォーカス可能な
まま、選択はできません。
変更イベント
Section titled “変更イベント”日付を選ぶと、コンテナ上でバブリングする 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 つ目の端を
選んでいる間、暫定の帯がポインタまたはキーボードフォーカスの下で
プレビューされます。試してみてください — 下で新しい開始日と終了日を
選べます:
<div class="hc-calendar" data-mode="range" data-value="2026-05-10/2026-05-14" data-name="stay" aria-label="Pick a date range"></div>両端の間の日は 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-start と stay-end —
書くため、範囲はクライアントサイドの状態なしで htmx 向けに
シリアライズされます:
<!-- submits stay-start=2026-05-10 & stay-end=2026-05-14 -->単一日付モードがデフォルトのままです。(複数月の横並びと週番号は スコープ外のままです。)
フォーム統合
Section titled “フォーム統合”data-name を設定すると、カレンダーは隠し input 経由でネイティブ
コントロールのようにシリアライズされます — <input name> とまったく
同様に、囲んでいる <form> と一緒に送信されます。下で日付を選んで、
送信値が更新されるのを見てください:
<form> <div class="hc-calendar" data-name="due" data-value="2026-05-15" aria-label="Due date"></div> <!-- submits due=2026-05-15 — the hidden input updates as the user picks --></form>カスタム日付フィールドとして
Section titled “カスタム日付フィールドとして”ネイティブの日付コントロールが好みでない? これが公認の日付
フィールドパターンです:
hc-field の一連の
マークアップで、見えるフィールドは
hc-input、後置の
ボタンがポップオーバーの
中のカレンダーを開き、日を選ぶと入力が埋まってポップオーバーが閉じ
ます。素のマークアップなので、コードジェネレーターは日付カラムに
そのまま出力でき、
マークアップのバージョニングポリシーの
下で安定しています。
カレンダーは data-target でフィールドへ向けます — 値の書き込みと
ポップオーバーの閉鎖はビヘイビアがやってくれます。フィールドごとの
JavaScript なしなので、日付フィールドの多いページも純粋なマーク
アップのままです。初期の日付は input に(value="…" で)一度だけ
設定してください。カレンダーがそれを継承するため、同期し続けるべき
data-value はありません。
<div class="hc-field"> <label class="hc-field__label" for="due">Due date</label>
<div class="hc-input-group"> <!-- The value lives here, once — and this input carries the form name. --> <input class="hc-input" id="due" name="due" type="text" readonly value="2026-05-15" placeholder="Pick a date" aria-describedby="due-hint"> <button class="hc-button" type="button" popovertarget="due-cal" aria-label="Choose date">📅</button> </div>
<div id="due-cal" class="hc-popover" popover data-side="bottom" data-align="end"> <!-- data-target seeds from #due, fills it on pick, and closes #due-cal. No JS. --> <div class="hc-calendar" data-target="#due" aria-label="Choose date"></div> </div>
<p class="hc-field__message" id="due-hint">Format: YYYY-MM-DD</p></div>ポップオーバーは installPopover(data-side / data-align)経由で
トリガーの下にアンカーされます。こうしたフィールドは好きなだけ
足せます — それぞれが自身の data-target を持つただのマークアップ
です。
フォームの name を運ぶのは誰か
Section titled “フォームの name を運ぶのは誰か”見える input が name を運び、値を送信します(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 か、自由入力か
Section titled “readonly か、自由入力か”公認の形は readonly です: カレンダーが唯一の編集者なので入力と
グリッドが食い違うことはなく、手入力された日付のクライアントサイド
パースもありません(readonly の input は送信されます。ブロック
されるのは入力だけです)。入力を許すために readonly を外すなら、
カレンダーはフィールドを(インストール時に)一度だけ読むことを
知っておいてください — 後から入力されたテキストから再シードは
しません。入力された値は、field-errors レシピなどでサーバサイド
バリデーションしてください。
キーボードとフォーカス
Section titled “キーボードとフォーカス”- トリガーは普通の
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 は、マークアップであなたがローカライズします。
htmx での利用
Section titled “htmx での利用”変更イベントから取得します — 例: 選ばれた日の空き状況を読み込む:
<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>Hyperscript
Section titled “Hyperscript”変更イベントにインラインで反応します — 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 → コンポーネントイベントへの反応。
アクセシビリティ
Section titled “アクセシビリティ”- グリッドは WAI-ARIA APG のデートピッカーパターンに従います:
<td role="gridcell">の日セルを持つrole="grid"のテーブル、 ロービングタブインデックス、選ばれた日のaria-selected、範囲外の 日のaria-disabled。各セルのaria-labelは完全なローカライズ済み 日付です。 - 月のタイトルは
aria-live="polite"領域なので、月の変更が読み上げ られます。 - コンテナには常にアクセシブルな名前(
aria-labelまたはaria-labelledby)を与えてください。
テーマ用トークン
Section titled “テーマ用トークン”| トークンパス | 用途 |
|---|---|
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 変数
Section titled “CSS 変数”生成される 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 不要のネイティブフィールドは デートピッカーを 参照してください。