動的な背景色でテキストの読みやすさを確保する

ウェブ機能: contrast-color()

再利用可能な UI コンポーネント(バッジやボタンなど)を構築する場合や、動的なテーマをサポートする場合、予測できない背景色に対してテキストを読みやすくすることは困難です。contrast-color() CSS 関数は black または white に解決され、指定された背景色とのコントラスト比を最大化します。

最適なテキストの色を決定する

要素に変数または動的に挿入された背景色がある場合は、ブラウザに依存して最も安全なテキスト色を計算できます。

必須: contrast-color() 関数を使用して color プロパティを設定し、背景色(またはそのカスタム プロパティ)を引数として渡します。

.badge {
  /* Define the background color as a custom property */
  --badge-bg: #e02424;
  background-color: var(--badge-bg);
  /* MANDATORY: Dynamic contrasting color */
  color: contrast-color(var(--badge-bg));
}

禁止事項: background-color に contrast-color() を使用する。これは、特定の背景色に対して前景色(テキストや枠線など)をコントラストさせるために特別に設計されています。

テーマ設定と統合する

contrast-color() は、CSS カスタム プロパティを含む有効な <color> 値を受け入れるため、デザイン システム内で特に強力です。背景テーマを変数として一元化することで、テーマが切り替わったとき(ユーザー指定の色に切り替わったときも含む)に、前景コンテンツのコントラストが自動的に更新されるようになります。

推奨: contrast-color() 内で共有バックグラウンド変数を参照して、アプリケーション全体で同期を保証します。

.theme-card {
  background-color: var(--theme-surface-color);
  color: contrast-color(var(--theme-surface-color));
}

.theme-container[data-theme="light"] {
  --theme-surface-color: #f4f4f4;
}
.theme-container[data-theme="dark"] {
  --theme-surface-color: #1a1a1a;
}
.theme-container[data-theme="custom"] {
  --theme-surface-color: var(--user-custom-theme);
}

禁止事項: 背景が動的な場合に、インタラクション状態ごとに個別のテキスト色をハードコードする。これを行うと、contrast-color() が防止しようとしている不一致が再び発生します。

代替戦略

contrast-color() のないブラウザをサポートするには、代替を指定する必要があります。背景色が既知で固定されている場合は、ハードコードされたコントラスト カラーを使用します。色が不明な動的な背景の場合は、UI の要件とブラウザのサポート ターゲットに基づいて、下の表から戦略を選択します。

戦略 おすすめのコンテンツ 考慮事項
相対色構文 CSS のみで高品質のコントラストを自動計算。 最高品質の CSS のみのフォールバック。
テキストのシャドウ どんな背景でも読みやすさが向上します。 「汚い」または「光っている」ように見えることがあり、すべてのデザインに適合するとは限りません。
テキストのストローク フォントの太さを維持しながら、エッジのコントラストを確保する。 可能であれば、paint-order を使用して、文字の細線化を回避します。
半透明オーバーレイ テキストの背後に最小限のコントラスト領域を確保する。 テキストの下の背景色を変更します。別のテキスト要素が必要です。
SVG フィルタ 背景の変化に応じて更新されるリアクティブなコントラスト。 別のテキスト要素が必要。ハッキーな実装。

RCS は、対応しているブラウザで、ダイナミック カラーの最も堅牢な自動フォールバックを提供します。

@supports (color: oklch(from red l c h)) {
  .badge {
    /* Highest threshold that passes WCAG. Higher values may be more legible. */
    --threshold: 0.623;
    --l: max(0, sign(var(--threshold) - l));
    color: oklch(from var(--badge-bg) var(--l) 0 h);
  }
}

@supports (color: contrast-color(red)) {
  .badge {
    color: contrast-color(var(--badge-bg));
  }
}

代替 CSS フォールバック

推奨: RCS が不十分またはサポートされていない場合は、設計上の制約に最も適した代替手段を選択します。

オプション 1: テキストのストローク

.badge--stroke {
  -webkit-text-stroke: 4px black;
}

オプション 2: paint-order を使用したテキスト ストロークで文字の形状を維持する

.badge--stroke {
  -webkit-text-stroke: 4px black;
  paint-order: stroke fill;
}

オプション 3: 半透明の背景オーバーレイ

.badge--overlay {
  position: relative;
  color: #fff;
  overflow: hidden;
}
.badge--overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  background-color: rgb(0 0 0 / 0.4);
  z-index: 0;
}
.badge--overlay span {
  position: relative;
  z-index: 1;
}

オプション 4: テキストのシャドウ

.badge--shadow {
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.8);
}

オプション 5: SVG フィルタ

  1. この SVG フィルタ定義を読み込みます。この WebKit のバグの影響を受ける Safari のバージョンをサポートする必要がある場合は、HTML ドキュメントに直接追加する必要があります。このバグにより、フィルタの外部 URL 参照とデータ URL 参照が妨げられます。それ以外の場合は、外部ファイル(SVG ファイルまたは CSS にインライン化されたファイル)から読み込む必要があります。

    <!-- Technique from https://miunau.com/posts/dynamic-text-contrast-in-css/ -->
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="0" style="display: none;">
      <defs>
        <filter id="contrast-filter" color-interpolation-filters="sRGB">
          <feColorMatrix type="matrix"
            values="0.2126 0.7152 0.0722 0 0
              0.2126 0.7152 0.0722 0 0
              0.2126 0.7152 0.0722 0 0
              0 0 0 1 0"/>
          <feMorphology operator="dilate" radius="2"/>
          <feComponentTransfer>
            <feFuncR type="linear" slope="-255" intercept="128"/>
            <feFuncG type="linear" slope="-255" intercept="128"/>
            <feFuncB type="linear" slope="-255" intercept="128"/>
          </feComponentTransfer>
          <feComposite operator="in" in2="SourceGraphic"/>
        </filter>
      </defs>
    </svg>
    
  2. テキストを <span> でラップします。

    <div class="badge--svg">
      <span>Badge content</span>
    </div>
    
  3. <span> にフィルタを適用します。

.badge--svg span{
  color: var(--badge-bg);
  filter: url(#contrast-filter);
}