メディアクエリに依存した要素の表示切り替えを `display: contents` でユーティリティコンポーネント化

画面幅、メディアクエリに依存した要素の表示切り替え

  • React + CSS Modules を使っている前提イメージ
  • JSX 上で表示・非表示の分岐が明示されてほしい
  • CSS を掘らないと分からないのは見通しが悪く感じる
  • matchMedia() ベースの Hooks にすると SSR で難儀する
  • 一貫性のためにサーバーサイドコードで頑張るのも億劫である
  • 表示・非表示だけなら純粋な CSS で実現したい

display: contents を使ってみた

メディアクエリで display: none と block を切り替えれば良いという単純な話ではなく、親要素が Flexbox や Grid だった場合を想定する必要があるので、表示されている状態ではボックスモデル的に虚無になってほしい。

contents これらの要素は自身のために特定のボックスを生成しません。擬似ボックスやその子ボックスで置き換えられます。なお、 CSS >Display Level 3 仕様書では、 contents の値が「普通ではない要素」 — 置換要素のように、 CSS ボックスの純粋な概念に従って表示されない要素に影響する方法を定義しています。 display - CSS: カスケーディングスタイルシート | MDN

ということで思いだしたのが表題の通り display: contents であり、いつの間にか使っても良さそうなフェーズになっていたので採用。

サンプルコード

JSX

import styles from './WhenVisible.module.css';
import type { PropsWithChildren } from 'react';

type WhenVisibleProps = {
  mobile?: boolean;
  tablet?: boolean;
  desktop?: boolean;
};

export const WhenVisible = ({
  mobile = false,
  tablet = false,
  desktop = false,
  children,
}: PropsWithChildren<WhenVisibleProps>) => {
  const classNames = [
    styles.container,
    mobile ? styles.mobile : '',
    tablet ? styles.tablet : '',
    desktop ? styles.desktop : '',
  ].join(' ');

  return <div className={classNames}>{children}</div>;
};

CSS

.container {
  display: none;
}

/* @custom-media --desktop-viewport */
@media screen and (min-width: 1040px) {
  .desktop {
    display: contents;
  }
}

/* @custom-media --tablet-viewport */
@media screen and (min-width: 601px) and (max-width: 1039px) {
  .tablet {
    display: contents;
  }
}

/* @custom-media --mobile-viewport */
@media screen and (max-width: 600px) {
  .mobile {
    display: contents;
  }
}

Usage

export const Header = () => {
  return (
    <header>
      <WhenVisible desktop>
        <HeaderDesktop />
      </WhenVisible>
      <WhenVisible mobile tablet>
        <HeaderMobile />
      </WhenVisible>
    </header>
  );
};

a11y Issues?

display: contents 大部分のブラウザーの現在の実装では、アクセシビリティツリーから display の値が contents であるすべての要素を削除します (ただし子孫は残ります)。これにより、その要素自身は読み上げソフトでは読み上げられなくなります。これは CSS 仕様書によれば正しくありません。 display - CSS: カスケーディングスタイルシート | MDN

とありますが子孫 (children) が残るなら、今回のユースケースであれば実用上の問題はないと判断。

リハビリ記事でした

久々に自分でまとまった量の CSS を書いてまして。 以前も CSS 〜デザイン得意パーソンにお任せ気味だったので、ハンズオンで触れると隔世の感がある... c⌒っ.ω.)っ


Author

ahomuAyumu Sato

overflow, Inc.VPoE

東京資本で生きる名古屋の鳥類

Web 技術、組織開発、趣味など雑多なブログ。技術の話題は zenn にも分散して投稿しています。

Bio: aho.mu
X: @ahomu
Zenn: ahomu
GitHub: ahomu

Related

Latest

Archives

Tags

Search