ダークモード
willink DS のダークモードは data-theme 属性の契約です(.dark class ではありません)。data-theme は 未指定 / "dark" / "light" の 3 状態を取り、 semantic role が cascade 上でフリップします(ADR-0013)。
data-theme 契約
<!-- 未指定: OS 設定に追従(auto) -->
<html>…</html>
<!-- 強制 dark(OS を上書き) -->
<html data-theme="dark">…</html>
<!-- 強制 light(OS を opt-out) -->
<html data-theme="light">…</html>未指定のとき DS は OS 設定(prefers-color-scheme)に 自動追従します。明示的に data-theme="dark" を付けると常に dark、data-theme="light" は OS が dark でも light に固定します(OS の opt-out)。 auto の挙動は次の media query で実現されています。
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
/* 21 の semantic role がここで dark にフリップ */
}
}フリップする role(代表例)
willink.dark 拡張で 21 の semantic role が dark にフリップします — core(bg / fg / muted / border)、 text-emphasis ladder(fg-strong / fg-emphasis / fg-secondary / fg-subtle / fg-faint)、 surfaces(surface-subtle / surface-muted / track / surface-inverted / surface-inverted-fg)、 brand state(brand-hover / brand-active / brand-soft / brand-soft-fg)、 feedback(success / warning / danger)。代表的な値の対応は以下の 通りです(gradient endpoint も dark で調整されますが、これは preset 内部変数で semantic role ではありません)。
| role | light | dark |
|---|---|---|
bg | #ffffff | neutral-950 |
fg | neutral-900 | neutral-50 |
border | neutral-200 | neutral-800 |
brand-hover | brand-700 | brand-500 |
brand-soft | brand-100 | brand-950 |
一方で mode-invariant(フリップしない)なものもあります — numeric scale (neutral / brand の 50〜950)、brand(= brand-600)、brand-fg(#ffffff)、ring(= brand)、accent-cyan / accent-pink。 numeric step を直接使う consumer は dark コントラストを自分で担保してください。
dark: variant は不要
フリップは :root の cascade で起きるため、コンポーネント側に Tailwind の dark: variant は 一切不要です。data-theme を切り替えるだけで、 再 render なしにランタイムで再テーマ化されます。
非 Tailwind consumer
Tailwind を使わない consumer は @willink-labs/css-tokens の dark バンドルを追加します。これは上記と同じフリップを CSS 変数の代入として 運びます。
/* Tailwind を使わない consumer (WordPress / Astro / Vue / plain CSS) */
@import "@willink-labs/css-tokens/tokens.css";
@import "@willink-labs/css-tokens/tokens.dark.css";次は ブランドとテーマ で単一ノブ(--color-brand) の仕組みを確認する。