チェック描画
チェック時にレ点が線で描かれるアニメーション。操作の手応えを視覚で強調する。
プレビュー
操作クリック/タップで動きます。
仕様書
- 向く場面
- ToDo の完了チェック
- 同意など押した手応えを強調したい単発操作
- 取り消し線と連動させたい項目
- 避ける場面
- 短時間に何十個も連続でチェックする表
- 一部選択(indeterminate)が要る親子チェック
- マーク形状を変えて dasharray を直さない
- 構成要素
- ネイティブ checkbox
- 装飾の枠
- レ点(SVG)
- ラベル文
- 持っている状態
- 通常
- キーボード
Tab でチェックボックスへ、Space で切替(ネイティブ input の動作)。フォーカス時は装飾の枠に 2px の輪郭が出る
- 支援技術
label がネイティブ input を包み、名前と checked 状態はブラウザが伝える。装飾の枠と SVG は aria-hidden。完了は取り消し線と色の両方で示す。reduced-motion で即時描画
- 動き
レ点の描画 300ms(stroke-dashoffset・50ms 遅延)、枠色と文字色 200ms。reduced-motion で停止
デザインの要点
- input は opacity:0 で隠し display:none にしない。消すとキーボードのフォーカスも Space も効かなくなる
- レ点は stroke-dasharray をパスの実測長(22)に合わせ、dashoffset を 0 へ動かして「線が引かれる」ように見せる
- 枠色の変化 200ms のあと 50ms 遅らせてレ点を描く。同時に始めると塗りと線が混ざって手応えが薄い
- 隠した input の :focus-visible を兄弟の枠へ転写する。見えない要素にリングが出ても位置が分からない
使いどころ
完了したかどうかを視覚的に強調したいチェック操作に向く。ToDoリストの完了チェック、利用規約への同意チェックのように「押した」という手応えをユーザーに返したい単発の操作が適している。特に本実装はcheck-textに取り消し線と色変化を自動で連動させているため、タスク完了を線で消す表現とセットで使うと最も効果が出る。逆に、テーブルの行選択や一括操作用のチェックボックスのように、短時間に何十個も連続でチェックする場面には向かない。stroke-dashoffsetの描画に0.3秒かかる設計なので、連打すると前のチェックの線が引き切る前に次を押すことになり、もっさりした印象になる。また一部だけ選択されたことを示すindeterminate状態のスタイルをこの実装は持たないため、親子関係のある一括選択チェックボックスにそのまま使うと、実際にはindeterminateなのに見た目は未チェックのままになる。
仕組み
ネイティブのinput type=checkboxをposition:absolute; opacity:0; width/height:0で視覚的に消しつつDOMには残し、キーボード操作とchecked状態の管理をブラウザに任せている。見た目を担うcheck-boxはaria-hidden付きの装飾要素で、中のsvg polylineにstroke-dasharray:22とstroke-dashoffset:22を仕込み、チェック時にdashoffsetを0へ0.3秒のcubic-bezierで動かすことで「線が引かれる」動きを作る。状態の伝播はすべて一般兄弟結合子(~)によるCSSだけで完結しており、.check-input:checked ~ .check-boxで枠線と背景色を、中のcheck-markでdashoffsetを、.check-input:checked ~ .check-textで文字色と取り消し線を同時に切り替える。JSは一切使われておらず、labelでinputとテキストを囲んでいるためクリック領域の判定もブラウザ標準に委ねている。
実装の注意
見落としやすいのは、隠したinputにフォーカスが当たったときの視覚的なフォーカスリングの扱い。inputはwidth/height 0で視覚的な輪郭を持たないため、ブラウザ既定のoutlineが仮に出ても実質見えず、キーボードでTab移動しているユーザーは今どのチェックボックスにいるか分からなくなる。この見本では .check-input:focus-visible ~ .check-box に outline を当てて、隣の装飾枠へリングを転写している。この兄弟セレクタを外すとリングは消える。次に、stroke-dasharray:22はこのpolyline(3,10 8,15 17,5)の実測パス長に厳密に合わせた値なので、チェックマークの形やviewBoxを変更する際にこの数値を再計算し忘れると、線が短すぎて途中で止まる、または始点に余りが出るといった見た目の破綻が起きる。また兄弟結合子はマークアップの順序に依存するため、input・check-box・check-textの順を入れ替えると3つの連動が音もなく効かなくなる。
アクセシビリティ
labelでネイティブのinput type=checkboxを包む構成なので、ラベルの読み上げ・Spaceキーでのトグル・checked状態の通知といった基本はブラウザとスクリーンリーダーの組み合わせにそのまま乗っており、装飾のcheck-boxとsvgにはaria-hiddenが付いて二重読み上げも防いでいる。視覚的な入力欄がwidth/height 0のため、キーボード操作時の「今ここにいる」という手がかりは装飾側に出す必要があり、.check-input:focus-visible ~ .check-boxにoutline 2px(offset 2px)を当ててリングを隣の枠へ転写している。prefers-reduced-motion: reduceではborder-color・background・stroke-dashoffset・colorの4つのtransitionを止め、レ点は即座に描かれる(終状態は同じ)。一部選択を示すindeterminate状態のスタイルも用意されていないため、親子チェックボックスに転用するなら別途JSでindeterminateプロパティを操作し、対応するCSSを足す必要がある。
調整して使う
調整しやすいのは3点。まず描画時間とイージング(既定0.3秒のcubic-bezier)で、規約同意のような一度きりの重要な操作なら0.4〜0.5秒に伸ばして間を演出し、密なリストでは0.15秒程度に縮めて操作感を優先する。次にマークの形で、polylineを別の形(プラス記号など)に差し替えることもできるが、その際は必ずgetTotalLength()で新しいパスの実測長を測り、stroke-dasharray/dashoffsetの22という数値を置き換える必要がある。最後にbox-shapeで、border-radius: 7pxの角丸正方形を999pxにすれば丸いラジオボタン風の見た目になり、タスクリストのような密なUIではサイズを20px前後に縮めるとよい。取り消し線付きのcheck-textはToDo向けの演出なので、規約同意や設定のON/OFFに転用する場合は取り消し線のスタイルだけ外すべきである。
コード(コピーして使えます)
<label class="check-label">
<input class="check-input" type="checkbox">
<span class="check-box" aria-hidden="true">
<svg class="check-svg" viewBox="0 0 20 20" fill="none">
<polyline class="check-mark" points="3,10 8,15 17,5"/>
</svg>
</span>
<span class="check-text">タスクを完了</span>
</label>