Accessibility
物差しは@kkhys/styles / @kkhys/ui /@kkhys/search)がtokens.css とuchu.css からpath:line で
Contrast 14 pairs
各トークンをcolor-scheme を
文字
| サンプル | 前景 / 背景 | 用途 | ライト | ダーク |
|---|---|---|---|---|
--c-texton --c-bg | 本文 | 19.48:1AAA | 17.41:1AAA | |
--c-subon --c-bg | 補助テキスト・日付・ナビ | 5.24:1AA | 7.19:1AAA | |
--c-linkon --c-bg | 本文リンク | 5.01:1AA | 7.64:1AAA | |
--c-link-visitedon --c-bg | 訪問済みリンク(prose) | 6.77:1AA | 6.66:1AA | |
--c-dangeron --c-bg | 警告テキスト(逸脱の | 5.48:1AA | 5.03:1AA | |
--c-surface-texton --c-surface | サーフェス上の | 14.01:1AAA | 15.68:1AAA | |
--c-subon --c-surface | サーフェス上の | 4.68:1AA | 5.79:1AA | |
--c-primary-texton --c-primary | 塗りボタン・ | 11.09:1AAA | 11.20:1AAA | |
--c-texton --c-selection | 選択中の | 17.16:1AAA | 8.85:1AAA |
非テキスト
| サンプル | 前景 / 背景 | 用途 | ライト | ダーク |
|---|---|---|---|---|
--ringon --c-bg | フォーカスリング | 3.61:1AA | 4.00:1AA | |
--c-primaryon --c-bg | 塗りボタンの | 11.09:1AA | 13.92:1AA | |
--c-scrollbaron --c-bg | スクロールバーの | 1.40:1対象外 | 1.76:1対象外 | |
--c-borderon --c-bg | 境界線(髪の | 1.24:1対象外 | 1.25:1対象外 | |
--c-surfaceon --c-bg | サーフェス(カード・行の | 1.12:1対象外 | 1.24:1対象外 |
境界線・サーフェス・スクロールバーはscrollbar-color で--ring--c-ring で
Focus 6 rules
キーボードフォーカスはbase.css の:focus-visible で
- フォーカスリングは
base.css の :focus-visible 1 本。 要素ごとに 色や形を 変えない outline なので
角丸に 追従し、 レイアウトを 揺らさず、 Forced Colors でも 消えない。 kiso.css の outline-offset 3px を 2px で 上 書きしている。 --ring は --c-ring の 70% 透過で、 背景に 対して 3:1 を 超える (上の 表)。 outline: var(--ring-width) solid var(--ring)--ring-width: 2px--ring-offset: 2px
- outline: none を
書くのは、 同じ 要素で :focus-visible を 再宣言する ときだけ .btn と
検索ダイアログの 入力欄が その形。 入力欄は 行の 中に 箱を 持たないので、 リングの 代わりに 同じ色の 下線を box-shadow で 引き、 Forced Colors では outline に 戻す。 リングを 消したまま 代替を 置かない 要素を 作らない。 - hover で
現れる UI には :focus-within の 対を 必ず 置く 目次パネルと
見出しの アンカーアイコン。 ポインタが ない 環境でも Tab で 同じ ものが 出る。 .toc:focus-within .toc-panel.heading-link:focus .anchor-wrapper
- 見た目を
消した フォーム部品は display: none ではなく opacity: 0 で ツリーに 残す テーマ切替の
ラジオ。 キーボードと 読み上げには 本物の input が 届き、 リングは label:has(:focus-visible) で 描く。 - クリップされる
要素の リングは 負の offset で 内側に 描く full-bleed な
行、 ピル型の ページャ、 横スクロールする チップ。 Conventions の Interaction & focus と 同じ 規則。 outline-offset: calc(-1 * var(--ring-width))
- スキップリンクを
body の 先頭に 置き、 main#main へ 飛ばす 共有 SkipLink。
フォーカスされるまで sr-only で、 Tab の 1 回目に 左上へ 現れる。 ラベルは ページの 言語で 渡す。 ヘッダーが 本文の 中に ある art と diary には 置かない。 <SkipLink label="本文へ移動" /><main id="main">
Motion
動きはtransition に@keyframesprefers-reduced-motion: reduceanimation-duration
装飾的なprefers-reduced-motion: no-preferencematchMedia で
Text
文字サイズはhtmlviewportmaximum-scaleuser-scalable=no は--fs-sm(14px)、--fs-xs--fs-2xs は--lh-base
日本語の<wbr> をword-break: keep-all とoverflow-wrap: anywheretext-wrap: balance。
Structure 8 rules
- html lang は
文書の 言語。 日本語サイトは ja、 英語だけの art と lgtm は en lgtm の
法務ページは 日本語版が あるので、 レイアウトが lang を prop で 受ける。 - 本文は
main#main に 入れる。 ヘッダー・ナビ・目次・ フッターは その 外 レイアウトが
main を 持つのが 基本形。 trends は ページ側(digest-view、 About、 Archive)が 持つ。 - nav には
aria-label を 付け、 同じ ページの 複数の nav を 名前で 区別する。 ラベルは ページの 言語 サイトナビ・
フッターナビ・目次・ページャ・ 日付ナビが 1 ページに 同居する。 共有コンポーネントは ラベルを prop で 受ける (SiteFooter の navLabel、 SiteHeader の backLabel)。 - ナビの
現在地は aria-current="page"。 見た目は --fw-medium + --c-text - ページに
h1 を 1 つ。 他に 見出しの ない ページは SiteHeader の heading で タイトルを h1 に する lgtm の
一覧・詳細と memo の フィード・スレッド・ タグが その形。 memo の プロフィールは 名前が h1 なので heading を 切る。 lgtm の 法務ページは タイトルを h1 と して 描く。 - 日付は
time [datetime]、 折りたたみは details/summary、 検索 UI は search 要素 意味を
持つ HTML 要素が ある ものは role で 作り直さない。 - list-style を
消したリストには role="list" Safari は
list-style: none の リストを リストと して 読まない。 kiso.css は list-style-type: "" で 全体を 守っているので、 明示は art の グリッドだけ。 - アンカーの
飛び先には scroll-margin。 見出しへの リンクは 見出し全体
Images 4 rules
- alt は
3 通り: 内容を 表す文、 空、 キャプションに 任せて 空 art の
一覧は 作品名、 diary は 日付と 番号、 アバターは 人名。 figcaption が 本文を 持つ図版と 装飾は alt="" で 二重読みを 避ける。 me の 図版は キャプションが alt と 同文の ときだけ 空にし、 別文なら alt を 残す。 memo の 添付は frontmatter の images [] .alt、 lgtm は エントリの description.txt を "LGTM over … " に して alt に する。 images: - file: 01.jpg alt: 干し芋の袋description.txtLGTM over <description>
- apps/art/src/pages/index.astro:28
- apps/diary/src/pages/index.astro:39
- apps/memo/src/components/thread-post.astro:84
- apps/art/src/components/work-figure.astro:19
- apps/memo/src/components/profile-header.astro:25
- apps/me/src/utils/image-alt.ts:6
- apps/memo/src/content.config.ts:20
- apps/memo/src/components/memo-images.astro:19
- apps/lgtm/src/loaders/lgtm-dir-loader.ts:32
- apps/lgtm/src/utils/alt.ts:5
- canvas の
演出は role="img" + aria-label、 noscript で テキスト代替 - JS 無効でも
画像が 見えるよう、 全レイアウトが BlurLoadNoscript で ぼかしを 解く - アイコンだけの
操作は 親に aria-label、 SVG は aria-hidden Conventions の
Icons と 同じ。 lgtm は さらに title で hover 時の 説明を 重ねる。
Keyboard & pointer 11 rules
- モーダルと
メニューは ネイティブ dialog / popover。 フォーカストラップ・ Esc・ 背景クリックは ブラウザ任せ 自前で
書くのは 「開いたら 入力欄へ」 「Esc で 入力を 保持したまま 閉じる」の 2 点だけ。 - リスト内の
移動は ↑↓。 IME 変換中は キーを 奪わない isComposing の
あいだは Esc も 矢印も 候補選択に 使われるので、 ダイアログは 何もしない。 - トグルは
button [aria-pressed]。 見た目は 属性セレクタで 描き、 状態を class と 二重に 持たない - JS 前提の
UI は hidden 属性で 出荷し、 初期化が 終わってから 外す JS なしでは
効かない トグルを、 効かないまま 見せない。 - localStorage は
try/catch で 包み、 読めない ときは 既定表示に 落とす - カード全体
クリックは ポインタ向けの 拡張。 キーボードには 中に 本物の a を 置く link-card は
カード全体が a。 memo の data-href 委譲は 選択中テキストと 内部リンクを 除外し、 日時を 投稿への リンクに して キーボードの 入口に する。 - スクロールする
領域は キーボードでも 届くように する。 tabindex="0" か、 中に フォーカスできる もの - ターゲットは
24px 角が 下限。 文字だけの ボタンは ::before で 当たり 判定を 広げる 24px は
WCAG 2.2 の 2.5.8 の 最小値。 丸アイコンボタンは 1.5rem、 主要ボタンは 2.25〜2.5rem。 見た目を 変えたくない 小さな トグルは inset: -0.25rem 0 の 擬似要素で 足りる。 height: 2.25remwidth: 1.5rem&::before { inset: -0.25rem 0 }
- ショートカットは
修飾キー付き(⌘K / Ctrl+K)だけ。 単キーは 置かない WCAG 2.1.4 は
単キーの ショートカットに 無効化か 付け替えを 求める。 設定画面の ない 静的サイトでは どちらも 用意できないので、 検索を 開く / は 持たない。 Alt を 除外するのは AltGr で 文字を 打つ配列の ため。 - テキスト入力は
16px 未満に しない iOS が
フォーカス時に ページを ズームするのを 防ぐ。 拡大を 禁止する viewport 設定は 持たない。 font-size: max(16px, var(--fs-base))width=device-width, initial-scale=1
- 新しい
タブで 開くリンクは sr-only で 「(新しい タブで 開く )」を 添える アイコンは
aria-hidden なので、 読み上げには 文字で 伝える。 memo の 本文リンクは rehype-external-links の content で 同じ span を 差し込む。 <span class="sr-only">(新しいタブで開く)</span>
Announcements 7 rules
- コピーなど
視覚でしか 返らない 操作は、 sr-only の role="status" に 結果を 書く アイコンの
差し替えだけでは 読み上げに 何も 起きない。 文言は ページの 言語で、 1〜1.5 秒後に 消す。 <span class="sr-only" role="status">status.textContent = "コピーしました"
- 非同期の
結果は role="status" に 書く。 空の ときも 要素を 消さない display: none に
すると ライブリージョンから 外れ、 最初の 告知が 落ちる。 空の ときは sr-only 相当で 残す。 - 無限スクロールは
aria-live="polite"。 終端は status、 失敗は alert で 差を つける 既定の
文言は 英語。 日本語サイトは endMessage / errorMessage / loadingLabel を 渡す。 - スピナーは
role="status" + aria-label。 回転だけを 見せない ラベルは
label prop。 既定は "Loading"、 memo は 「読み込み中」。 - .sr-only は
clip-path 版の 定型 overflow: clip の
祖先の 中でも 隠れたままに なる。 - title 属性だけに
情報を 持たせない title は
ポインタの hover でしか 出ず、 タッチと キーボードには 届かない。 日時の 完全表記のように、 見える 文字の 補足に 限る。 意味を 持つ 記号には sr-only の 名前を 添える。 - 色だけの
目印には sr-only の テキストを 添える 一覧の
下書きマーク (赤い バー)。
Color & theme
色はlight-dark() をcolor-schemeで<meta name="color-scheme">box-shadow ではなくoutlinemark を
色だけでprefers-contrast
Known gaps 2 items
重い順。
- 中alt 未記入の
memo 添付画像 1.1.1 Non-text Contentfrontmatter に
images [] .alt を 持たない 旧 memo(151 件、 155 枚)は "Image 1" のような 番号だけで、 何が 写っているか 伝わらない。 新規投稿は studio が alt を 必須に するので 増えない。 対処: memo-content の
各 index.md に images [] .alt を 書く (コンテンツ側の 作業)。 書いた 分から 番号の alt が 消える。 - 低--fs-2xs は
10.4px 1.4.4 Resize Textキャプション・トークン名・
ページャに 29 箇所。 拡大は できるので 違反ではないが、 既定の ままでは 読みづらい。 対処: 新規の
UI では --fs-xs(12px)を 下限にし、 --fs-2xs は 等幅の メタ情報に 限る。
Checklist 10 items
画面を
- main#main が
1 つ、 h1 が 1 つ、 nav には aria-label、 body の 先頭に SkipLink - Tab だけで
全操作に 届く。 カードは 中に 本物の a、 hover で 出る ものは focus-within - Tab で
リングが 見える。 クリップされる 要素は 内側リング - 色は
トークンのみ。 上の 表で AA 以上の 組み合わせから 選ぶ - 画像の
alt は 内容・ 空・ キャプションの どれか。 アイコンだけの 操作は aria-label - トグルは
aria-pressed、 現在地は aria-current、 非同期の 結果と 操作の 結果は role=status - 文字入力は
16px 以上、 押せる ものは 24px 角以上 - 動きは
transition だけ。 animation を 足すなら prefers-reduced-motion で 切る - 読み上げテキスト(aria-label、
status、 新しい タブの 注記)は ページの 言語 - VoiceOver(Safari)と
200% ズームで 一度通す
規約 36 項目、