liquid-metal-border について
React 向けの実装スキルで、`metal-fx` のアニメーションボーダーをセマンティックなコントロールに適用します。導入、アクセシビリティ、テーマ、モーション、性能、検証を扱います。
主な機能
- 01React 18以降での`metal-fx`導入
- 02セマンティックな要素1つのラップ
- 03状態連動ボーダーと反射の設定
- 04フォーカス、モーション、CSSフォールバックの維持
- 05テーマ、性能、動作の検証
活用シーン
- 01主要または選択中のボタンの装飾
- 02近接要素への任意の反射追加
- 03キーボードフォーカスとモーション低減の確認
- 04描画やWebGLフォールバックの問題調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mengto/ skills --skill liquid-metal-borderインストール後、Claude Code で/liquid-metal-borderを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamlREFERENCES.md
詳細
リキッドメタルボーダー
基本契約
- React 18 以降では
metal-fxを使用します。 button、a、div、articleなど、実際のホスト要素を1つだけラップします。- ホスト要素のセマンティクスとインタラクティブ性を維持します。
MetalFxは視覚的な装飾であり、コントロールではありません。 - アニメーションするボーダーは、アクティブ、選択済み、フォーカス中、ホバー中、または主要なサーフェスにのみ使用します。すべてのコントロールを囲まないでください。
- フォールバックとして、静的な CSS ボーダーまたはフォーカスアウトラインを維持します。WebGL の装飾だけで重要な状態を表現してはいけません。
- アクティブでないインスタンスと、視差効果を減らす設定が有効な環境では、一時停止します。
- ライトモードとダークモードは個別にテストします。反射は意図的にダークモードでのみ描画されます。
編集前の確認事項
- プロジェクトが React を使用し、
reactとreact-domのバージョンが18以降であることを確認します。 - 実際のコントロール、その寸法、角丸、背景、ボーダー、アウトライン、シャドウ、アクティブ状態の情報源、テーマの情報源を確認します。
- メタルを常に表示するか、
active、selected、ホバー、またはフォーカスの状態に応じて表示するかを決めます。 - プロジェクトに手動のテーマ切り替えがあるか確認します。OS の設定に従わない場合は、
theme="auto"ではなく、その状態を使用します。 - 反射光の効果が実際に役立つ周囲の要素を特定します。デフォルトで反射を追加しないでください。
- 以下のプロパティを使用する前に、インストール済みの
metal-fxのバージョンを確認します。
インストールとインポート
プロジェクトのパッケージマネージャーでインストールします。
npm install metal-fx
# pnpm add metal-fx
# yarn add metal-fx
# bun add metal-fx
名前付きの React コンポーネントをインポートします。
import { MetalFx } from "metal-fx";
スタイルシートをインポートしないでください。このパッケージはコンポーネントのスタイルを注入します。ESM と CommonJS のビルドに対応し、SSR に安全で、ハイドレーション後に WebGL パイプラインをマウントします。Next.js の App Router プロジェクトでは、クライアント側の React フックを使用するため、Client Component からレンダリングしてください。
基本的なボタン
反射やカスタムシェーダーのジオメトリを追加する前に、控えめなボーダーから始めます。
import { MetalFx } from "metal-fx";
export function UpgradeButton() {
return (
<MetalFx
variant="button"
preset="chromatic"
theme="auto"
strength={0.85}
>
<button type="button">Upgrade to Pro</button>
</MetalFx>
);
}
ラッパーは子要素のサイズを測定し、その上にメタルリングを描画しながら、子要素のインタラクティブ性を維持します。CSS、Tailwind、またはインラインスタイルで、通常どおり子要素のサイズを設定します。
アクティブ状態のパターン
インスタンスを1つだけマウントし、実際のコンポーネント状態に応じて強度を変えます。視覚効果だけが状態を示す手段にならないよう、aria-pressed、aria-selected、またはネイティブの選択状態の仕組みを使用します。
"use client";
import { useEffect, useState } from "react";
import { MetalFx } from "metal-fx";
function useReducedMotion() {
const [reduced, setReduced] = useState(false);
useEffect(() => {
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
const sync = () => setReduced(query.matches);
sync();
query.addEventListener("change", sync);
return () => query.removeEventListener("change", sync);
}, []);
return reduced;
}
export function LiquidMetalToggle({
selected,
onSelect,
}: {
selected: boolean;
onSelect: () => void;
}) {
const [hovered, setHovered] = useState(false);
const [focused, setFocused] = useState(false);
const reducedMotion = useReducedMotion();
const highlighted = selected || hovered || focused;
return (
<MetalFx
className="liquid-metal-toggle"
variant="button"
preset="chromatic"
strength={highlighted ? 0.9 : 0.14}
paused={reducedMotion || !highlighted}
disableGlow={reducedMotion || !highlighted}
ringCssPx={selected ? 1.5 : 1}
normalizeHostStyles={false}
>
<button
type="button"
className="liquid-metal-toggle__control"
aria-pressed={selected}
onClick={onSelect}
onPointerEnter={() => setHovered(true)}
onPointerLeave={() => setHovered(false)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
>
Auto
</button>
</MetalFx>
);
}
.liquid-metal-toggle {
background: #18181b;
border-radius: 999px;
}
.liquid-metal-toggle__control {
min-height: 40px;
padding: 0 18px;
color: #fafafa;
background: transparent;
border: 0;
border-radius: 999px;
box-shadow: none;
}
.liquid-metal-toggle__control:focus-visible {
outline: 2px solid #ffffff;
outline-offset: 3px;
}
paused を使うと、アクティブでないキャンバスの更新を停止できます。リングは非表示にならず、最後のフレームで停止します。strength は描画される不透明度を 0 から 1 まで変更しますが、シェーダーの速度は遅くなりません。
静的ボーダーとカードのパターン
子要素を透明のままにする必要がある場合は、ラッパーをサーフェスとして使用します。
<MetalFx
variant="button"
preset="silver"
theme="dark"
strength={0.55}
borderRadius={24}
ringCssPx={1}
shaderScale={1.45}
disableGlow
normalizeHostStyles={false}
style={{ background: "#111318" }}
>
<article className="feature-card">
<h3>Realtime review</h3>
<p>Keep feedback attached to the work.</p>
</article>
</MetalFx>
リングが覆われないよう、article の背景は透明に保ちます。コンテンツのレイアウトには子要素を、表示するサーフェスには MetalFx ラッパーを使用します。
完全なカスタマイズ項目
| Prop | 値とデフォルト | 用途 |
|---|---|---|
children | React のホスト要素 1 つ。必須 | 実際のボタン、リンク、チップ、カード、アイコンのコントロールをそのまま保持します。 |
variant | デフォルトは "button"、"circle" | シェーダースケール 1.6 の 1 px ピル型の基本スタイル、またはスケール 1.3 の 2 px コンパクトな円形の基本スタイルを選択します。描画サイズは、計測された子要素のサイズに従います。 |
preset | デフォルトは "chromatic"、"silver"、"gold" | 虹色の玉虫色、クールなスチール、暖かみのあるゴールドから選択します。それぞれにダークモードとライトモードの調整が含まれます。 |
theme | デフォルトは "auto"、"dark"、"light" | prefers-color-scheme の変更にリアルタイムで追従するか、アプリのテーマにエフェクトを固定します。SSR ではダークモードで開始し、その後クライアント側で解決されます。 |
strength | 数値 0..1。デフォルトは 1 | キャンバスとグローの不透明度を調整します。アイドル状態では低い値、アクティブ状態では高い値を使用します。 |
paused | ブール値。デフォルトは false | シルエットを表示したまま、このインスタンスを現在のシェーダーフレームで固定します。 |
borderRadius | CSS ピクセル単位の数値。省略可 | 角丸半径を上書きします。省略した場合は、リサイズ時に子要素の計算済み角丸半径を読み取ります。 |
normalizeHostStyles | ブール値。デフォルトは true | 子要素の背景、境界線、アウトライン、シャドウを削除し、リングと干渉しないようにします。カスタムのフォーカスアウトラインやフォールバックの境界線を維持する場合は false に設定します。 |
reflectionTargets | React 要素の ref の配列。省略可 | 選択した隣接要素に、柔らかな鏡面反射を投影します。ダークモードのみです。 |
disableGlow | ブール値。デフォルトは false | シェーダーリングを維持したまま、移動するハローを取り除きます。 |
shaderScale | 数値。デフォルトは variant の基本値 | 値を大きくすると金属パターンの大きな特徴にズームインし、小さくするとズームアウトします。 |
ringCssPx | 数値。デフォルトは variant の基本値 | 表示されるリングの太さを CSS ピクセル単位で上書きします。 |
scale | 数値。デフォルトは 1 | CSS ズームや意図的に拡大した UI システムに合わせて、すべての絶対ピクセル単位のエンジン定数をまとめて拡大縮小します。 |
className | 文字列。省略可 | 子要素ではなく、MetalFx ラッパーにスタイルを適用します。 |
style | React の CSS プロパティ。省略可 | ラッパーの表示領域のサイズやスタイルを直接指定します。 |
ref | React ref。省略可 | 転送されたラッパーの HTMLDivElement にアクセスします。 |
その他の有効な HTMLDivElement 属性もラッパーに転送されます。
調整の基本値
- メインのピル型ボタン:
variant="button"、strength={0.75}、ringCssPx={1}。 - 選択中のタブまたはフィルター:アイドル時の強度は
0.1–0.2、選択時の強度は0.75–0.9。 - アイコンボタン:
variant="circle"、子要素に正方形のサイズを明示し、strength={0.7}。 - 大きなカード:
ringCssPx={1}、shaderScale={1.3–1.6}、disableGlow。 - プレミアム CTA:まず
chromaticを使用します。ニュートラルな UI にはsilver、暖色系や高級感のある配色にはgoldを使用します。 - 控えめなアクティブ状態:リングが見えにくくなるまで弱める前に、グローを無効にします。
- 2 倍のデザインシステム:シェーダー、リング、グロー、反射の値を個別に 2 倍にする代わりに、
scale={2}を使用します。
通常のコントロールでは strength <= 0.9 を維持してください。最大強度のクロマチックメタルは、ラベルやアイコンより目立つ場合があります。
サイズと角丸
子要素のサイズを指定してください。
<MetalFx variant="circle">
<button style={{ width: 36, height: 36 }} aria-label="Send">
↑
</button>
</MetalFx>
子要素より大きなフレームにするには、ラッパーにサイズを指定し、子要素を引き伸ばします。
<MetalFx style={{ width: 44, height: 44 }} variant="circle">
<button
style={{ width: "100%", height: "100%" }}
aria-label="Send"
>
↑
</button>
</MetalFx>
ラッパーには display: inline-flex が使用されます。ラッパーにも子要素にも固有のサイズがない場合に、循環するパーセント指定のサイズ設定を作成しないでください。
近接反射
隣接要素への明示的な ref のみを渡してください。
import { useRef } from "react";
const chipRef = useRef<HTMLButtonElement>(null);
<>
<button ref={chipRef}>Tools</button>
<MetalFx variant="circle" reflectionTargets={[chipRef]}>
<button aria-label="Send">↑</button>
</MetalFx>
</>
反射処理を無効にするには、reflectionTargets を省略します。ライトモードでは反射が自動的にスキップされます。
アクセシビリティとモーション
- ラッパー内では、ネイティブの
button、a、フォームのセマンティクスを維持します。 - アクティブ状態は、
aria-pressed、aria-selected、チェック状態、または現在のルート状態で保持します。 - 色相やアニメーションだけに依存しないでください。テキスト、アイコン、位置、太さ、または静的なエッジの変化も残します。
normalizeHostStylesの使用には注意してください。デフォルトでは子要素のoutlineが削除されます。コントロール独自のフォーカスリングが必要な場合はfalseに設定し、透明なクロームと:focus-visibleを定義します。- モーションを減らす設定では
pausedを設定します。動きが不要な場合は、移動するグローも無効にします。 - エフェクトはポインターイベントを透過させ、子要素がクリック可能で、キーボードでも操作できることを確認します。
- WebGL が失敗した場合に備え、プレーンな CSS のボーダーまたはサーフェス処理を残します。
パフォーマンス
metal-fx は、マウントされたインスタンス間で単一の共有 WebGL コンテキスト、コンパイル済みシェーダー 1 つ、アニメーションループ 1 つを再利用します。また、IntersectionObserver で画面外の複製を一時停止し、アニメーションフレーム経由でリサイズ処理をデバウンスします。
それでも、エフェクトは選択的に使用してください。
- 待機中のコントロールが並ぶグリッド全体ではなく、主要なサーフェスや状態を持つサーフェスに適用します。
- アクティブでないインスタンスは一時停止します。
- 反射によって視覚的な奥行きが加わる場合を除き、省略します。
- ラップされた要素の寸法を継続的にアニメーションさせないでください。
- 控えめなカードや密集したツールバーでは、
disableGlowを優先します。
検証
プロジェクトの lint、型チェック、本番ビルド、テスト、git diff --check を実行します。その後、Codex ブラウザーで次を確認します。
- パッケージがマニフェストとロックファイルに含まれていることを確認します。
- 1 つの
.metal-fx-rootが 1 つのセマンティックなホストをラップしていることを確認します。 - ホバー、ポインターダウン、選択済み、キーボードフォーカス、無効状態をテストします。
- アクティブ状態がセマンティクスとして公開され、エフェクトを非表示にしても明確であることを確認します。
normalizeHostStylesが有効な場合と無効な場合の両方で、フォーカスリングが表示されることを確認します。- 各レスポンシブブレークポイントで、測定されたサイズと角丸を確認します。
- アプリの実際のライトテーマとダークテーマをテストします。
autoが手動切り替えと一致すると思い込まないでください。 - モーションを減らす設定を有効にし、シェーダーの動きが停止することを確認します。
- コントロールを画面外にスクロールしてから戻し、空白フレームが発生せずにエフェクトが再開することを確認します。
- WebGL、ハイドレーション、リサイズ、または ref のエラーがないか、コンソール出力を確認します。
- ダークモードで明示的に対象指定した場合に限り、隣接するコントロールに反射が適用されることを確認します。
問題の診断
- リングが隠れる: 子要素の不透明な背景がキャンバスの上にあります。デフォルトの正規化を使用するか、子要素を透明にしてラッパーをスタイル設定します。
- フォーカスリングが消えた:
normalizeHostStylesによって子要素のアウトラインが削除されています。falseに設定し、プロジェクトの CSS で背景、ボーダー、シャドウのみをリセットします。 - 角丸が正しくない: 子要素に実際の計算済みの角丸を設定するか、
borderRadiusを明示的に渡します。 - 円がぼやける、または細く見える:
variant="circle"を使用し、ringCssPxを大きくする前に正方形の寸法を明示します。 - パターンが煩雑すぎる:
strengthを下げ、グローを無効にするか、silverを使用します。すぐにリングを太くしないでください。 - ライトモードで反射がない: 想定どおりの動作です。反射はダークモード専用です。
- ズーム後にエフェクトの物理的なサイズが正しくない: ピクセル定数を個別に調整するのではなく、UI のスケールに合わせて
scaleを設定します。 - サーバーコンポーネントでページがクラッシュする: 使用箇所をフレームワークのクライアントコンポーネント境界の内側に移動します。
- サポートされていないハードウェアでボーダーが表示されない: セマンティックなコントロールと CSS フォールバックを完全な状態に保ちます。WebGL リングは拡張機能として扱います。
引き継ぎ
インストールした metal-fx のバージョン、ラップしたコントロール、アクティブ状態のソース、プリセット、テーマ戦略、フォールバックのボーダー、モーション低減時の動作、ビルド/テスト結果、Codex ブラウザーでの検証内容を報告します。ローカルでの準備状況とデプロイ済みの結果は区別してください。