HTML/CSSとJavaScriptの使い分けから考えるアクセシブルなUI実装
フロントエンド開発で直面する重要な判断の1つが、「UIコンポーネントをHTML/CSSで実装すべきか、それともJavaScriptを使うべきか」という選択です。この記事では、アクセシビリティとパフォーマンスの観点から、適切な実装手法の選び方を考えました。
HTML/CSSで実装すべきケース
1. 擬似クラスによる状態制御
シンプルで静的なインタラクションは、JavaScriptを使わずCSSの擬似クラスで実装します。
/* ボタンの基本スタイル */
.button {
background: var(--color-primary);
transition: 200ms ease-in-out;
transition-property: background, transform;
}
/* アクティブ状態 */
.button:active {
transform: translateY(0);
}
/* フォーカス状態 */
.button:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
/* 無効状態 */
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
2. レスポンシブデザイン
viewportに応じたUIの制御には、CSSのメディアクエリを使用することで、クライアントサイドのパフォーマンスを最適化できます。
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
}
@media (width <= 30em) {
.product-grid .product-card:nth-child(n+8) {
display: none;
}
}
❌ 避けるべき実装例
function Grid({ items }) {
const { width } = useWindowSize();
const displayCount = width < 768 ? 8 : 9;
const visibleItems = items.slice(0, displayCount);
return (
<div className="grid">
{visibleItems.map(item => (
<Item key={item.id} {...item} />
))}
</div>
);
}
JavaScriptでの実装の問題点:
- CLS(Cumulative Layout Shift)の発生
- SSRとCSRでのハイドレーションの不一致
JavaScriptが必要なケース
以下のようなインタラクティブなUIコンポーネントでは、適切なキーボード操作とアクセシビリティをサポートするためにJavaScriptの使用が不可欠です。
- ドロップダウンメニュー
- タブパネル
- ダイアログ
- アコーディオン
など
ドロップダウンメニューの実装例
実際にドロップダウンメニューを例に、なぜJavaScriptが必要になるのか見ていきましょう。
❌ 避けるべきCSS実装
<div class="dropdown">
<button class="dropdown-trigger">メニュー</button>
<ul class="dropdown-content">
<li>項目1</li>
<li>項目2</li>
</ul>
</div>
<style>
.dropdown-content {
display: none;
}
.dropdown:hover .dropdown-content {
display: block;
}
</style>
CSSのみでの実装の問題点:
- WAI-ARIAの適切な設定ができない
- キーボード操作に対応できない
- 「ドロップダウンメニューだ!Enterで開いて...」->「できひん...」となる
- フォーカス管理が不可能
アクセシブルな実装のための要件
WAI-ARIAのガイドラインでは、ドロップダウンメニューに対して以下のような要件が定められています
- aria-expandedによる開閉状態の管理
- aria-controlsによるメニューの関連付け
- キーボード操作
- CopyEnter/Space: メニューの開閉
- ↑ ↓: メニュー項目間の移動
- Esc: メニューを閉じる
など
✅ 推奨されるJavaScript実装
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
function handleKeyDown(e) {
switch (e.key) {
case 'ArrowDown':
focusNextItem();
break;
case 'ArrowUp':
focusPreviousItem();
break;
case 'Escape':
closeAndFocusTrigger();
break;
case 'Enter':
case ' ':
toggleMenu();
break;
}
}
return (
<div className="dropdown" onKeyDown={handleKeyDown}>
<button
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
aria-controls="dropdown-menu"
>
メニュー
</button>
{isOpen && (
<ul
id="dropdown-menu"
role="menu"
>
{items.map(item => (
<li
key={item.id}
role="menuitem"
tabIndex={-1}
>
{item.label}
</li>
))}
</ul>
)}
</div>
);
}
ヘッドレスUIライブラリの活用
上記のJavaScriptの実装はめちゃくちゃ冗長です。
アクセシブルなUIコンポーネントの実装には、他にも以下のような多くの考慮が必要になります。
- アニメーション制御
- クリックアウトサイドの処理
- スクリーンリーダー対応
- モバイルデバイス対応
- ブラウザ間の互換性確保 など...
これらすべてを自前で実装することは非効率的で、バグの発生リスクも高まります。そこで、ヘッドレスUIライブラリの使用を推奨します。
ヘッドレスUIは、独自のスタイルを持たないのでサイトのトンマナから外れず、アクセシビリティやインタラクションなどの豊富なUIコンポーネントの機能を提供してくれます。
主要なライブラリ
-
Radix UI
- WAI-ARIA完全準拠
- スタイリングの自由度が高い
- アクティブなコミュニティとメンテナンス
-
React Aria
- Adobe開発のUIフックライブラリ
- 包括的な国際化対応
ライブラリを選ぶ際は、APG(ARIA Authoring Practices Guide)への準拠をチェックしましょう。実装の品質を判断する良い指標になります。
まとめ
ホバーやレスポンシブデザインなどの単純な状態変化はHTML/CSSで実装し、キーボード操作やWAI-ARIAの動的な制御が必要な場合はJavaScriptを使用します。
ただし、アクセシビリティに配慮したUIコンポーネントの実装は複雑になりがちなため、RadixUIなどのヘッドレスUIライブラリの活用を推奨します。これにより、アクセシビリティとパフォーマンスを両立しつつ、実装の負荷を大幅に削減できます。
参考文献
We are hiring
COUNTERWORKS では一緒に働く仲間を絶賛募集中です。
今後の更なる成長のためには圧倒的に仲間が不足しています。皆さまのご応募お待ちしております!

ポップアップストアや催事イベント向けの商業スペースを簡単に予約できる「SHOPCOUNTER」と商業施設向けリーシングDXシステム「SHOPCOUNTER Enterprise」を運営しています。エンジニア採用強化中ですので、興味ある方はお気軽にご連絡ください! counterworks.co.jp/
Discussion