カラーテーマ
<html>(または任意のサブツリー)の data-color 属性は、アクセント
パレット — フォーカスリング + すべての primary アクションボタン /
チェック済みチェックボックス / 現在のページネーション項目 — を、
マークアップを再描画せずに差し替えます。5 つのテーマが同梱され、各
アクセントシェードはライトとダーク両方のモードで WCAG AA コントラストを
満たすよう選ばれているため、アクティブな data-theme にかかわらず同じ
属性が機能します。
data-color が変えるのはアクセントだけ — primary アクションの色と
フォーカスリングです。
data-neutral(面 / テキスト /
枠線のランプ)、data-theme(ライト / ダーク)、data-density
(comfortable / compact / dense)とは直交します。同じ祖先に設定すれば
すべて一緒にカスケードします — 例えば data-color="teal" data-neutral="slate" は、冷たい slate の UI に teal のアクセントです。
グレーを変えたければ data-neutral へ。このページはアクセントだけを
扱います。
組み込みテーマはいずれもアクセントのみです — そのパレットは以下の
primary / hover / ソフトティント / text-on-primary / focus-ring のセット
です(soft スウォッチは半透明で、市松模様の上に表示しています):
defaultteallimeorangefuchsiadata-color の値 | Primary bg | Hover bg | Primary 上のテキスト | 色相 | 典型的な用途 |
|---|---|---|---|---|---|
(なし) / default | blue.600 | blue.700 | 白 | 264° | デフォルトの中立系 SaaS — Linear、AWS 系 |
teal | teal.600 | teal.700 | 白 | 192° | 落ち着いたプロダクト UI、ダッシュボード、開発ツール |
lime | lime.600 | lime.700 | 白 | 120° | サステナビリティ、アグリテック、アウトドア |
orange | orange.600 | orange.700 | 白 | 48° | エネルギッシュ / 生産性、コマース |
fuchsia | fuchsia.600 | fuchsia.700 | 白 | 336° | マーケティング、クリエイターツール、コンシューマ向け |
上の値はすべてprimitive ランプの シェードに解決されます — これらのテーマは新しい色を導入せず、アクセントを 別のランプに向け直しているだけです。
この表が 5 通りではなく 1 つの形をしている点に注目してください。どのテーマも
静止面に 600、hover に 700、フォーカスリングに 500、そして白文字を
使います。これは偶然ではなくランプの性質です — ステップ 600 は OKLCH の
明度 0.54 に位置し、明度 0.554 未満ならどの色相でも白文字が AA を
満たします。アクセントを選ぶとは色相角を選ぶことであり、残りは自動的に
決まります。
5 つの色相も恣意的ではありません。アクセント五角形 — blue を起点に
色相環を 72° 間隔で分割した頂点です。この間隔は 2 つの性質を同時に
もたらします。どの 2 アクセントも互いの濃淡違いには見えません(旧セットは
indigo が blue から 12.6° でした)。そしてどのアクセントもステータス色と
衝突しません — 旧 rose は error の赤から 9.7° しか離れておらず、rose
テーマのアプリでは主要アクションとエラー表示が視覚的に同一の色になって
いました。五角形の各頂点は error / warning / success の色相から 21° 以上
離れています。
6 色目が欲しい場合は テーマビルダーの出番です — 任意の色相を同じラダーに通せば、この 5 つとまったく同じ構成の軸が 生成されます。
ライブプレビュー
Section titled “ライブプレビュー”下の各行は、異なる data-color 値を持つラッパーの中で同じボタン +
チェックボックス + ラジオ + アラートを描画しています。Starlight ヘッダーの
Theme ボタンで行全体をダークモードに切り替え、どのテーマもダークの
面で読めることを確認してください。
各行はテーマを認識するすべての面を試せます: primary ボタン、ghost
ボタン(ホバーでソフトティント)、インプット(フォーカスでテーマ色の
枠線 + リング)、チェック済みチェックボックス、チェック済みラジオ。行内の
ヘルパーテキストを選択するとテーマ色の ::selection ハイライトが
見えます。
Default(blue)
Section titled “Default(blue)”Orange
Section titled “Orange”Fuchsia
Section titled “Fuchsia”ページへの適用
Section titled “ページへの適用”サイト全体のテーマなら、<html> に属性を設定します:
<html data-color="lime"> ...</html>セクション単位の上書き(例: 中立的なアプリの中のマーケティング用 ランディング面)にはローカルに付けます:
<section data-color="orange"> <button class="hc-button" data-variant="primary">Subscribe</button></section>この属性が変えるのはアクセントパレットだけです。面 / 背景 / テキストの
色には触れません — それらは
data-theme(ライト / ダーク)の所有物です。
ライト / ダークトグル — installThemeToggle()
Section titled “ライト / ダークトグル — installThemeToggle()”data-theme の設定はあなたの仕事です — サーバサイドでも、同梱の
installThemeToggle() ビヘイビア(自動初期化の
/behaviors エントリに
含まれます)でも:
<button type="button" class="hc-button" data-variant="ghost" data-hc-theme-toggle data-persist="hc-theme"> <span aria-hidden="true">◐</span></button>-
有効なテーマは、
<html>にdata-themeがあればそれ、なければ OS の設定(prefers-color-scheme)です。クリックで反転し、data-theme="light|dark"を明示的に書き込みます。 -
data-persist="<key>"(任意)は選択をlocalStorageに保存し、 インストール時に復元します。フラッシュのない復元のためには、 スタイルシートより前に<head>へこれもインライン展開してください (ビヘイビアだけでは初回描画の後に走ります):<script>try {var t = localStorage.getItem('hc-theme');if (t === 'light' || t === 'dark') document.documentElement.setAttribute('data-theme', t);} catch (e) {}</script> -
トグルは状態を
aria-pressed("true"= ダーク)で反映し、アイコン のみの場合は i18n カタログ からデフォルトのaria-labelを得ます(themeToggle.label。作者が 書いたaria-labelが優先)。変更のたびにバブリングするhc:themechangeイベント(detail.theme)を発火します。 -
data-theme="dark"をハードコードしたページはそのまま動き続けます — トグルは単にその明示値から始まります。
何が変わるか
Section titled “何が変わるか”data-color の値ごとに次のトークンが上書きされます
(color.{name}.tokens.json 配下):
--hc-color-focus-ring--hc-color-link--hc-color-link-hover--hc-color-link-visited--hc-color-action-primary-bg--hc-color-action-primary-fg--hc-color-action-primary-border--hc-color-action-primary-hover-bg--hc-color-action-primary-hover-border--hc-color-action-primary-soft-bgprimary-soft-bg は、テーマの primary カラーを color-mix() と
transparent で 12 % ティントにしたものです(混色は in oklab、割合は 5 テーマ共通)。同じ値が
ライトとダークどちらの面でも正しくブレンドされるため、モード別の
バリアントは不要です。
コンポーネントはこれらを自身の --hc-{component}-* 変数を通じて消費
します(密度と同じ var() 間接参照パターン)。カスケードは
data-color → --hc-color-action-primary-* → --hc-button-primary-* /
--hc-input-focus-border / --hc-checkbox-checked-* などへ流れます。
カスケードの影響を受けるコンポーネントと面:
- ボタン —
data-variant="primary"の背景とホバー状態。 - ボタン —
data-variant="ghost"のホバー背景は、中立グレーではなくテーマのティントを拾うようになりました。 - インプット —
:focus-visibleの枠線色はテーマのフォーカスリングシェードに従います。 - チェックボックス —
:checkedの塗り(バリアント上書きがないとき)。 - ラジオ —
:checkedの塗り。 - ページネーション —
aria-current="page"のセル。 - すべてのコンポーネントの
:focus-visibleリング —--hc-color-focus-ringが直接駆動。 ::selection— テキスト選択ハイライトはprimary-soft-bgを使うため、ページ上の文章をハイライトすることが控えめなブランドの合図になります。
意図的に影響しないコンポーネントとトークン:
- (
hc-buttonの)data-variant="secondary"— 設計上、中立グレー。primaryがブランドテーマのアクションとして際立ち続け、secondaryはどのテーマでも第二階層の中立 CTA として読めます。 data-variant="error"/data-variant="success"— セマンティックな意味を持つため、アクセントにかかわらず赤 / 緑のまま。- semantic カラー(
info、success、warning、error)— 情報パレット。固定。 --hc-color-muted-bg— 汎用のミュート面背景。ライトとダークの間でのみ切り替わり、カラーテーマ間では決して変わりません。- 面 / 背景 / テキストの色 —
data-theme(ライト / ダーク)の所有物。 - エレベーションシャドウ(
--hc-shadow-sm/-md/-lg/-overlay、 スクロールヒント用の--hc-shadow-edge)—data-themeの所有物: ダークの段はアルファが強めで、ドロップダウン、ダイアログ、ドロワーが ダークの面でも読めるようにしています。フルカスタムテーマは他の トークン同様に 5 段を上書きできます。 - コンテナのパディング / ギャップ —
data-densityの所有物。
リンクだけはアクセントかつテーマ依存
Section titled “リンクだけはアクセントかつテーマ依存”他のアクセントトークンはライトとダークで同じ値を持ちます。ランプの 600
はどの色相でも白文字が乗るので、primary ボタンはテーマを気にしません。
リンクだけは違います。リンクは自前のサーフェスではなくページ面の上に
乗るテキストであり、両方の背景に対して 4.5:1 を満たす単一の段が存在し
ないためです(blue.500 はライト面で 3.61:1、blue.600 はダーク面で
3.33:1)。そのためリンクはライトで 600 / 700 / 800、ダークで 400 /
300 / 200 を読み、default 以外の各アクセントはライト用ファイルの隣に
color.{name}.dark.tokens.json を持ち、複合セレクタ
[data-theme="dark"][data-color="{name}"] の下に出力されます。
:visited はカスタムプロパティを使えません。visited 依存の宣言で
var() を解決するとページがカスケード経由で履歴ビットを読み出せてしまう
ため、エンジンは意図的に解決を拒否します。つまり色はリテラルである必要が
あります:
/* 書きたいけれど書けないもの */a:visited { color: var(--hc-color-link-visited); }15 色すべてが --hc-color-bg と --hc-color-surface に対して 4.5:1 を
満たし、将来の再ラダー化で静かに AA を割らないようスペックで固定しています。
--hc-color-muted-bg は意図的にこの保証の外です。あれはコンポーネントの
ティント(ボタンの hover、無効フィールド、datagrid ヘッダ、skeleton、
avatar)であり、自分のサーフェス上の前景色はコンポーネントが持ちます。
実際に持つ必要があるのが hc-chat です。アシスタントの吹き出しは
muted-bg で塗られ、かつ散文なのでリンクが実際に置かれます。吹き出しは
同じランプを 1 段先に進めた色を自前で張り直し、:visited ルールは持ちま
せん — レイヤーは詳細度に勝ち hc.components は hc.base の後に来るので、
resting のルールが visited 状態も覆い、吹き出し内では visited は
unvisited と統一されます。
外れるのは resting のリンク段だけで(ライト 600 が 4.40:1、ダーク 400 が
3.85:1、最悪アクセント)、これを 600 から動かすとリンクが
action.primary から切り離され、「そのテーマのアクセント」ではなく単なる
任意の青になってしまいます。
トークンビルドがこのリテラルをテーマごとに 1 ルールずつ @layer hc.base
へ焼き込みます。消費者側が手では表現できない唯一の部分なので、助言では
なくキット本体として同梱しています。裸のアンカーは既定でスタイルされます。
hc.base.css はすでにドキュメントの背景と文字色を持っていたのに <a> の
手前で止まっており、コンポーネント外のアンカーはすべて UA の
-webkit-link の青と :visited の紫のままでした。この 2 色は
data-theme / data-color / data-neutral のいずれにも追従しません。
visited を unvisited と統一したい場合(コンソールは閲覧履歴ではなく道具
です)、トークンではなくルールを上書きします。--hc-color-link-visited
を設定しても単体では何も変わりません。焼き込まれたルールはそれを読まない
からであり、それこそが焼き込んでいる理由です:
/* レイヤー外なので hc.base のすべてに勝つ */a:visited { color: var(--hc-color-link); }独自テーマの作成
Section titled “独自テーマの作成”組み込みの 5 テーマは特別ではありません — どれも小さなアクセント トークンのセットにすぎません。独自のブランドパレットの追加は、テーマを ビルドの一部として出荷したいか、ビルドなしで動いているページに載せたい かに応じて、2 つのパスのどちらかです。
カスケードの実際の動き(重要)
Section titled “カスケードの実際の動き(重要)”カラーテーマは 7 つの --hc-color-action-primary-* semantic 変数
ではありません。コンポーネントはそれらを直接読みません — 各自の
--hc-{component}-* 変数(--hc-button-primary-bg、
--hc-checkbox-checked-bg、--hc-pagination-current-bg、…)を読み、
ビルドがテーマブロックごとにそれぞれへ具体的な値を焼き込みます。
hc.tokens.css の各 [data-color] ブロックが 7 個ではなく約 50 個の
コンポーネント変数を再宣言しているのはそのためです。
したがって、実行時に semantic 変数だけを上書きしてもボタンや
チェックボックスは塗り替わりません(カスタムプロパティ参照は宣言した
要素で解決され、凍結された状態で継承されるため、入れ子の
[data-color] ラッパーで再解決させることはできません)。正しい実行時
上書きはコンポーネント変数 — ブロック全体 — を再宣言する必要が
あります。そのブロックは手で書かず、テーマビルダーで生成してください。
パス A — 生成された CSS ブロックを貼り付け(リビルドなし)
Section titled “パス A — 生成された CSS ブロックを貼り付け(リビルドなし)”テーマビルダーの
Theme CSS block を使います。@hypermedia-components/core/css の後に
読み込まれる任意のスタイルシートに追加し、属性を設定します。ビルド
ステップなし。サイト全体でもサブツリーでも機能します:
<html data-color="brand">…</html><!-- or scope it to one section --><section data-color="brand">…</section>生成されるブロックは組み込みテーマのブロックと同じ形です — 影響する すべてのコンポーネント変数が、あなたの色に解決されています:
@layer hc.tokens { [data-color="brand"] { --hc-color-action-primary-bg: #7c3aed; --hc-button-primary-bg: #7c3aed; --hc-checkbox-checked-bg: #7c3aed; --hc-pagination-current-bg: #7c3aed; /* …radio, tabs, slider, calendar, focus ring, ::selection tint, … */ }}ブロックを足すよりトークンスタイルシートを丸ごと置き換えたい?
ビルダーの Full token CSS エクスポートは、差し替え可能な完全な
hc.tokens.css(すべての組み込みテーマ + あなたのテーマ)です。
パス B — トークンソースを追加(ビルドに同梱)
Section titled “パス B — トークンソースを追加(ビルドに同梱)”テーマを hc.tokens.css(および独立軸ファイル
hc.tokens.color-brand.css)に同梱される第一級の軸にするには、DTCG
ソースを追加してトランスフォーマーに登録します。
-
color.teal.tokens.jsonの形をなぞってpackages/core/src/tokens/color.brand.tokens.jsonを 作ります。primitive のシェードがあればそれを参照し、なければ生の hex をインラインで:{"$description": "Brand color theme. Emitted under [data-color=\"brand\"].","color": {"focus-ring": { "$type": "color", "$value": "{primitive.color.violet.500}" },"link": { "$type": "color", "$value": "{primitive.color.violet.600}" },"link-hover": { "$type": "color", "$value": "{primitive.color.violet.700}" },"link-visited": { "$type": "color", "$value": "{primitive.color.violet.800}" },"action": {"primary": { "bg": { "$type": "color", "$value": "{primitive.color.violet.600}" },"fg": { "$type": "color", "$value": "{primitive.color.white}" },"border": { "$type": "color", "$value": "{primitive.color.violet.600}" } },"primary-hover": { "bg": { "$type": "color", "$value": "{primitive.color.violet.700}" },"border": { "$type": "color", "$value": "{primitive.color.violet.700}" } },"primary-soft": { "bg": { "$type": "color", "$value": "color-mix(in srgb, {primitive.color.violet.600} 12%, transparent)" } }}}} -
隣に
color.brand.dark.tokens.jsonを作り、ダーク用のリンク段を 持たせます。これが要るのはリンクだけです — 他のアクセントトークンは 両テーマで同じ値です(上記):{"$description": "Brand links on the dark surface.","color": {"link": { "$type": "color", "$value": "{primitive.color.violet.400}" },"link-hover": { "$type": "color", "$value": "{primitive.color.violet.300}" },"link-visited": { "$type": "color", "$value": "{primitive.color.violet.200}" }}} -
scripts/token-transform.mjsに両方を登録します。DEFAULT_SOURCESにエントリを、AXIS_NAMESPACES/COLOR_RAMPS_WITH_DARKに名前空間を足します:// in DEFAULT_SOURCES, next to the other color.* lines{ namespace: 'color.brand', file: 'color.brand.tokens.json', selector: '[data-color="brand"]' },// and after them, the compound dark block — both selector forms, because// data-theme and data-color need not sit on the same element{ namespace: 'color.brand.dark', file: 'color.brand.dark.tokens.json',selector: '[data-theme="dark"] [data-color="brand"], [data-theme="dark"][data-color="brand"]',overlay: ['theme.dark', 'color.brand.dark'] },// in AXIS_NAMESPACES, so it also ships as a standalone axis fileconst AXIS_NAMESPACES = [..., 'color.brand'];// in COLOR_RAMPS_WITH_DARK, so that file carries both halvesconst COLOR_RAMPS_WITH_DARK = [..., 'brand']; -
リビルド:
pnpm --filter @hypermedia-components/core build。 トランスフォーマーが参照を解決し、どのコンポーネント末端がテーマ 依存かを分類し、[data-color="brand"]の下へ自動で再出力します — 組み込みテーマと同じ機構です。
どちらのパスもアクセントトークンだけを上書きします。面 / テキストの色は
data-theme、コントロールのサイズは data-density の所有のままです。
アクセシビリティ
Section titled “アクセシビリティ”各テーマは、ボタンの text-on-primary の組み合わせで WCAG AA コントラストを満たします。検査した結果ではなく、構成上の帰結です:
| テーマ | テキスト色 | Primary bg | コントラスト比 |
|---|---|---|---|
| default | 白 | blue.600 | 5.31 : 1 |
| teal | 白 | teal.600 | 4.83 : 1 |
| lime | 白 | lime.600 | 4.89 : 1 |
| orange | 白 | orange.600 | 5.36 : 1 |
| fuchsia | 白 | fuchsia.600 | 5.70 : 1 |
5 つが狭い帯に収まるのは、個別に調整したからではなく明度を共有している
からです。OKLCH の明度を固定すると、コントラストは色相にも彩度にもほとんど
依存しません — ステップ 600 で 360 色相すべてを掃引しても 4.73〜5.86 : 1、
ある色相で彩度を 0 から 0.25 まで振っても 5.06 → 5.43 しか動きません。明度
だけで保証が成立するため、新しいアクセント色相にコントラスト検討は不要です。
teal が 5 つの中で最も厳しく、下限の 4.83 : 1 を決めています。
packages/core/test/ramp.test.mjs はこの表全体に加えて五角形の間隔自体も
検証しているので、AA を下回るアクセントやステータス色相へ寄ったアクセントは
ページに届く前にユニットテストで落ちます。
1 つ知っておくべき帰結として、ステップ 500 はテキスト面ではありません。
明度 0.62 では彩度の高い色相が白文字(3.4〜4.0 : 1)にも暗い文字
(4.4〜5.2 : 1)にも届かないため、500 はフォーカスリングとアクセント専用で、
ラベルを載せる面はすべて 600 が担います。
テーマビルダーは色を 選ぶそばから同じコントラスト検査をライブで実行するため、カスタムテーマの text-on-primary ペアが AA を満たすかは即座にわかります。