💭

HTML/CSSとJavaScriptの使い分けから考えるアクセシブルなUI実装

2024/12/25に公開

フロントエンド開発で直面する重要な判断の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 では一緒に働く仲間を絶賛募集中です。
今後の更なる成長のためには圧倒的に仲間が不足しています。皆さまのご応募お待ちしております!

COUNTERWORKS テックブログ

Discussion