GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

カレンダー 選択 UI 9選 — コピペOK

カレンダー 選択 UI は、値一つではなく開始日と終了日のような範囲をまとめて選ばせる部品です。セグメントコントロールに続き、範囲・アバター・検索・ホイール・比率まで、実際の実装で詰まりやすい選択UI9種を、コードとともにまとめました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9つの カレンダー 選択 フォームは人気順ではなく、一度に選ぶ値の数が増える順番で並べました。01・02は一つの範囲(開始〜終了、最小〜最大)を選び、03・04・05はカード・アバター・チップで一つの選択肢を選び、06・07は検索やスクロールで一つの項目を探し、08は比率を、09は複数の項目を一度に別リストへ移します。同じpicker柱で選択肢をピル数個に絞ったのがセグメントコントロールUI 9選で、一覧を下へ開いて選ぶのがセレクトボックス デザイン css 9選です。

01カレンダー範囲ドラッグ選択

日付選択 カレンダーとして何が見えるか

月のグリッドで開始日を選び、続けて終了日を選ぶと、その間の日付が薄く塗られ、両端だけ濃い点になります。

カレンダー範囲ドラッグ選択
新しいタブで開く

01 · カレンダー範囲ドラッグ選択 — 計測 累積面積 18.911% · 動いたフレーム 13/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

방향키 이동aria-selected0.22초

コードのどこを見るか

宿泊予約や休暇申請の期間選択に使えます。

.cal__day.is-range { background: rgba($color, .18); }
.cal__day.is-edge {
  background: $color;
  color: #fff;
  font-weight: 700;
  transform: scale(1.06);
}

02デュアルサム価格帯スライダー

画面で起きること

二つのつまみをドラッグ、または矢印キーで動かすと最小・最大の値が更新され、その間のトラックだけ色で埋まります。

デュアルサム価格帯スライダー
新しいタブで開く

02 · デュアルサム価格帯スライダー — 計測 累積面積 3.265% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointer 드래그role=slider방향키 ±1

コードの読み方

ECサイトの価格帯フィルターや、面積・坪数の範囲フィルターに向いています。

function pctFromX(x) {
  var r = track.getBoundingClientRect();
  return Math.round(Math.max(0, Math.min(100, ((x - r.left) / r.width) * 100)));
}
function setLo(n) { v.lo = Math.max(0, Math.min(n, v.hi - 5)); paint(); }
function setHi(n) { v.hi = Math.min(100, Math.max(n, v.lo + 5)); paint(); }

03カード型プランラジオ選択

どんな動きか

:has()で選んだカードだけが少し浮き上がって枠が明るくなり、右上にチェックバッジがスケールで飛び出します。

カード型プランラジオ選択
新しいタブで開く

03 · カード型プランラジオ選択 — 計測 累積面積 3.631% · 動いたフレーム 6/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

:has()scale 배지0.22초

どこを変えるか

料金プランカードや配送方法カードの選択に使えます。

.plan:has(.plan__radio:checked) {
  transform: translateY(-3px);
  border-color: $color;
  box-shadow: $shadow-raised;
}
.plan:has(.plan__radio:checked) .plan__badge { transform: scale(1); opacity: 1; }

04アバターアイコングリッドピッカー

何が見えるか

グリッドのアバターを一つ選ぶと、そのアバター固有の色でリングが大きくなり、上のプレビュー名も一緒に変わります。

アバターアイコングリッドピッカー
新しいタブで開く

04 · アバターアイコングリッドピッカー — 計測 累積面積 5.119% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

role=radiogroup방향키 순환색상별 링

コードのどこを見るか

プロフィールアバターの選択やキャラクター選択画面に向いています。

function select(idx) {
  cur = idx;
  items.forEach(function (b, i) {
    b.setAttribute('aria-checked', i === idx ? 'true' : 'false');
    b.tabIndex = i === idx ? 0 : -1;
    b.classList.toggle('is-on', i === idx);
  });
  pvEmoji.textContent = items[idx].dataset.emoji;
  pvName.textContent = items[idx].dataset.name;
}

05サイズチップ+数量ステッパーの組み合わせ

画面で起きること

サイズチップを選ぶと単価が変わり、+/-ステッパーで数量を変えるとその場で小計が再計算されます。

サイズチップ+数量ステッパーの組み合わせ
新しいタブで開く

05 · サイズチップ+数量ステッパーの組み合わせ — 計測 累積面積 3.278% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aria-live 수량min/max 비활성화0.22초

コードの読み方

ECサイトでサイズと数量を同じ画面で決める商品オプションに使えます。

function price() {
  return +(radios.filter(function (r) { return r.checked; })[0] || radios[0]).dataset.price;
}
function paint() {
  num.textContent = qty;
  dec.disabled = qty <= 1; inc.disabled = qty >= 9;
  total.textContent = (price() * qty).toLocaleString('ja-JP') + '円';
}

06検索コンボボックスリストボックス

どんな動きか

入力欄に文字を打つと、その文字で始まる項目だけに絞られてリストが開き、矢印キーで動いた項目はaria-selectedで示されます。

検索コンボボックスリストボックス
新しいタブで開く

06 · 検索コンボボックスリストボックス — 計測 累積面積 7.848% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

role=comboboxaria-selected0.18초

どこを変えるか

国・都市の検索選択欄や、宛先を検索して選ぶ場面に向いています。

function render(q) {
  shown = all.filter(function (c) { return c.indexOf(q) === 0 || !q; }).slice(0, 3);
  list.innerHTML = '';
  shown.forEach(function (c, i) {
    var li = document.createElement('li');
    li.setAttribute('role', 'option');
    li.setAttribute('aria-selected', i === active ? 'true' : 'false');
    li.textContent = c;
    list.appendChild(li);
  });
}

07スクロールスナップ数字ホイールピッカー

何が見えるか

縦にスクロールすると中央の枠に来た数字だけ大きく濃くなり、指を離す瞬間scroll-snapがその数字にぴったり止まります。

スクロールスナップ数字ホイールピッカー
新しいタブで開く

07 · スクロールスナップ数字ホイールピッカー — 計測 累積面積 0.316% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scroll-snap-type방향키 스크롤role=listbox

コードのどこを見るか

アラーム時刻の時(じ)選択や、予約人数を選ぶホイールに使えます。

function mark() {
  var idx = Math.round(track.scrollTop / H);
  idx = Math.max(0, Math.min(N - 1, idx));
  if (idx === centered) return;
  centered = idx;
  items.forEach(function (el, i) { el.classList.toggle('is-center', i === idx); });
  track.setAttribute('aria-activedescendant', items[idx].id);
}

08アスペクト比クロッププリセットピッカー

画面で起きること

1:1・4:5・16:9・9:16のチップから一つを選ぶと、プレビューボックスのaspect-ratioがその比率へなめらかに変わります。

アスペクト比クロッププリセットピッカー
新しいタブで開く

08 · アスペクト比クロッププリセットピッカー — 計測 累積面積 12.704% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aspect-ratio 전환role=radiogroup0.3초

コードの読み方

プロフィール写真のクロップ比率選択や、リール・ショート動画のアップロード比率選びに向いています。

function apply(v) { box.style.aspectRatio = v; }
radios.forEach(function (r) {
  r.addEventListener('change', function () { apply(r.value); });
});
.ar__box {
  aspect-ratio: 1 / 1;
  height: 100%;
  border: 2px solid $color;
  transition: aspect-ratio $duration $easing;
}
.ar__radio:checked ~ span { background: $color; color: #fff; }

09左右移動デュアルリストピッカー

どんな動きか

左のリストで項目を選び矢印ボタンを押すと右のリストへ移り、矢印キーでリスト内を移動できます。

左右移動デュアルリストピッカー
新しいタブで開く

09 · 左右移動デュアルリストピッカー — 計測 累積面積 1.42% · 動いたフレーム 7/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

role=listbox다중 선택aria-multiselectable

どこを変えるか

権限・役割の付与画面や、メール配信グループの構成に使えます。

function move(from, to, sel) {
  var keep = [], moved = [];
  from.forEach(function (n) { (sel[n] ? moved : keep).push(n); });
  from.length = 0; Array.prototype.push.apply(from, keep);
  Array.prototype.push.apply(to, moved);
  Object.keys(sel).forEach(function (k) { delete sel[k]; });
  render();
}

落とし穴 — 隠した装飾レイヤーはどこではみ出すか

01を最初に作ったとき、実際のクリック処理とは別にtranslateXだけで動く装飾用のスイープバーを::beforeで重ね、デモのループを独立して回そうとしました。ところがこのバーにtranslateX(366%)を与えると、見た目には舞台の外に出て見えないのにdocument.documentElement.scrollWidthはその分だけ伸びてしまい、480×300でも320px幅でもスクロールバーが生じる「はみ出し」判定を受けました。opacity: 0やpointer-events: noneで隠しても、レイアウト上の位置(と移動距離)はそのまま計測されるためです。結局この装飾レイヤーを消し、実際の.is-range・.is-edgeクラスが500msごとに切り替わる本物の状態変化だけをループに残しました。06の検索リストも似た理由で、閉じた状態でもリストの高さがそのまま残り、初回描画ではみ出しが検出されました — max-height: 0; overflow: hidden;で閉じた状態の箱を実際に畳まないと消えません。03が使う:has()はChrome・Edge 105+、Safari 15.4+、Firefox 121+でしか動かず、それより下のバージョンではカードが浮き上がらないだけで、ラジオの選択自体は正常に動きます。選ばれた人を画面に示すアバター部品はプロフィール UI デザイン 9種に別に集めてあります。9つのファイルがそのまま入ったzipの圧縮パスワードはuv8qjazcです。

アクセシビリティ

03・05・08は本物の<input type="radio">を見た目だけ隠しているので、スクリーンリーダーはラジオとしてそのまま読み上げ、矢印キーで選択肢を移動できます。01はrole="gridcell"を持つ日付マスのaria-selectedを選択のたびに直接書き換えるので、ネイティブのラジオではありませんが選択状態は同じように伝わります。02・04・09はラジオではないカスタムコントロールなので、それぞれrole="slider"・role="radiogroup"・role="listbox"とaria-valuenow・aria-checked・aria-selectedを状態が変わるたびに更新します。06は入力欄のフォーカスを移さずに、強調した項目へaria-selectedを付け替えて示し、07も同じくフォーカスを移さずにaria-activedescendantで中央に来た項目を読み上げます。05の数量はaria-live="polite"なので、ボタンを押すたびに新しい値が静かに読み上げられます。prefers-reduced-motion: reduceでは9つ全てのトランジションを止め、チェック・リング・バッジといった選択状態は開いた・選ばれた最終的な見た目のまま残るので、情報そのものは消えません。

@media (prefers-reduced-motion: reduce) {
  .cal__day, .plan, .ava__item, .ar__box { transition: none; }
}

選択UI以外の単品コンポーネントはセグメントコントロールUI 9選にもあり、同じ選択UIでもセレクト・ドロップダウン系はセレクトボックス デザイン css 9選にまとめています。

FAQ

カレンダー 選択 html と、開始日・終了日を別々に入力する方式は何が違いますか?

日付欄を二つ別々に置くと値はそれぞれ独立して入りますが、01のようにグリッド一つで開始日と終了日を続けて選ぶと、その間の日付が即座に塗りつぶされ、何日間の期間なのかが一目で分かります。宿泊予約のように期間の長さ自体が重要な画面ほど、グリッド型が有利です。日付ではなく時刻と所要時間を選ぶ画面はタイムピッカー ui 9選が受け持ちます。

デュアルサムスライダーで二つのつまみが近づきすぎたらどうなりますか?

02のsetLo・setHiは常に最小値を最大値より5以上小さく保ちます。片方のつまみをもう一方の近くまで押すと、反対側のつまみも最小間隔ぶんだけ一緒に押され、二つのつまみが重なったり順序が入れ替わったりしません。

スクロールスナップのホイールピッカーは、マウスホイールがなくても使えますか?

はい。07のトラックにはscroll-snap-type: y mandatoryが付いているのでタッチスワイプでも自然に止まり、トラックにフォーカスして矢印キーを押すとscrollBy({ top, behavior: 'smooth' })で一段ずつ動きます。マウスホイールは同じスクロールを起こすもう一つの手段にすぎません。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。