mcpField
T
tailwind-to-stylex

tailwind-to-stylex

github.com/shadcn-labs開発2867 回インストール4 週間前に更新
リポジトリ

tailwind-to-stylex について

Tailwindのユーティリティクラスを、描画されるスタイルを保ってStyleXへ移行するスキルです。CSSの解決、条件や構造セレクターの処理、フレームワーク別APIの適用、変更の検証を案内します。

主な機能

  1. 01TailwindクラスをCSSに解決
  2. 02条件をStyleXの値に変換
  3. 03マークアップ変更が必要なセレクターへの対応
  4. 04フレームワーク別のpropsまたはattrs適用
  5. 05未変換箇所の報告と移行の検証

活用シーン

  1. 01コンポーネントをTailwindからStyleXへ移行
  2. 02レスポンシブや状態のバリアントを変換
  3. 03再構成が必要なユーティリティの特定
  4. 04移行ファイルの型チェックとlint

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/shadcn-labs/skills --skill tailwind-to-stylex

インストール後、Claude Code で/tailwind-to-stylexを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

SKILL.zh-CN.md
evals/
evals.json
evals/files/button/
package.json
evals/files/button/src/
Button.tsx
Spinner.tsx
evals/files/button/src/lib/
utils.ts
evals/files/navbar/
package.json
tailwind.config.ts
evals/files/navbar/src/
Navbar.tsx
evals/files/price-card/
package.json
vite.config.ts
evals/files/price-card/src/
PriceCard.tsx
references/
mapping.md
stylex-rules.md

詳細

Tailwind から StyleX への移行

Tailwind のユーティリティクラスを StyleX スタイルに変換します。className="..." 文字列を stylex.create({...}) 呼び出し内のエントリに置き換え、stylex.props(...) で適用します。

レンダリング結果を維持してください。移行中にコンポーネントを再設計しないでください。スタイルを 正確に変換できない場合は、黙ってデザインを変更するのではなく、ステップ 6 でフラグを立ててください。

中心となる仕組み: 解決してから再形成する

tw-to-stylex codemod と同じ方法でクラス文字列を変換します。各クラスを それが生成する CSS に解決し、その CSS を StyleX オブジェクトに再形成します。クラス名から 推測しないでください。計算された CSS が、同一に保たなければならない振る舞いです。

"px-4 py-2 text-sm font-medium hover:bg-blue-600"
        │
        ▼   resolve to CSS using the project's Tailwind version and config
padding-inline: 1rem; padding-block: .5rem;
font-size: .875rem; line-height: 1.25rem; font-weight: 500;
:hover { background-color: #2563eb }
        │
        ▼   reshape to StyleX with camelCase properties and conditional values
{
  paddingInline: '1rem',
  paddingBlock: '0.5rem',
  fontSize: '0.875rem',
  lineHeight: '1.25rem',
  fontWeight: 500,
  backgroundColor: { default: null, ':hover': '#2563eb' },
}

クラスが w-[37px] のような任意の値、プロジェクトのカスタムテーマ、またはプラグインを使用している場合でも、 値はクラスまたはプロジェクト設定から取得されるため、CSS への解決は機能します。 カスタム設定や馴染みのないプラグインによって出力が不明確になる場合は、 プロジェクトの tailwind.config / CSS の @theme を確認するか、推測せずに解決してください。

変換でつまずく 2 つのポイント

条件はプロパティ値の中に存在する

Tailwind は hover:、md:、dark: などの状態およびレスポンシブバリアントを 別々のルールに分散させます。一方、StyleX は 条件をプロパティ値の中にネストします。プロパティに何らかの条件がある場合は常に default キーが 必須です。ベース値がない場合は null を使用します。default がないと、StyleX が 条件を無視する可能性があります。

// md:flex hover:opacity-80  ->  conditions live on each property
display: { default: 'block', '@media (min-width: 768px)': 'flex' },
opacity: { default: 1, ':hover': 0.8 },
一部のセレクタには構造的な変更が必要

space-x-*、divide-*、group/group-hover:*、peer/peer-*:*、およびタイポグラフィの prose はすべて、子孫、兄弟、または祖先の状態セレクタに依存しています。StyleX は 単一の要素をスタイルするため、これらは直接マッピングできません。これらを再構築し、ユーザーに伝えてください。たとえば、 space-x を flex 親の gap に置き換えるか、group-hover の状態を React または CSS 変数に持ち上げます。各ケースについては references/mapping.md を参照してください。

ワークフロー

各変更をレビュー可能に保つため、一度に 1 つのコンポーネントファイルずつ作業します。

ステップ 1: StyleX がセットアップされていることを確認する

StyleX には @stylexjs/stylex と、Babel、Vite、Next.js、Webpack、 または rspack 用のコンパイラプラグインが必要です。プラグインがないと、実行時に stylex.props が何も返さず、 移行が壊れているように見えます。package.json とバンドラー設定を確認してください。セットアップされていない場合は、 references/stylex-rules.md のプロジェクトセットアップのセクションを読んでください。ユーザーがセットアップを 要求した場合は構成し、そうでない場合は変換前に何が不足しているかを説明してください。また、スタイルの適用方法が異なるため、 React DOM と react-strict-dom を特定してください。

ステップ 2: Tailwind の使用箇所を見つける

ファイル内のすべての className と class を、cn(...)、clsx(...)、 twMerge(...) への動的呼び出し、テンプレートリテラル、三項演算子を含めて特定します。忠実に変換するには、 各要素に適用される可能性のあるクラスの完全なセットと、それがどのような条件下かを把握する必要があります。

ステップ 3: stylex.create オブジェクトを構築する

base、label、iconActive など、異なる要素やバリアントごとに 1 つの名前付きエントリを作成します。$1 や $2 ではなく、説明的な名前を使用してください。各エントリのクラスを CSS に解決し、次の StyleX のルールを適用します。

  • すべてのプロパティに camelCase を使用します。たとえば、border-radius は borderRadius になります。
  • longhand または単一値の shorthand を使用します。StyleX はマージ競合を引き起こすため、複数値の shorthand に対して警告を出します。border: '1px solid red' は borderWidth: 1, borderStyle: 'solid', borderColor: 'red' に分割してください。2 値の padding は paddingBlock / paddingInline に分割してください。padding: 16 のような単一値の shorthand は有効です。
  • 数値は長さプロパティにおいて px を表します。width: 24 は 24px を意味します。その他の単位は '1.5rem'、'50%'、'100vh' のように文字列として保持してください。
  • 条件はプロパティ値に属し、default が必要です。モバイルファーストの順序では、プレフィックスのないクラスが default であり、ブレークポイントは min-width を持つ @media キーになります。
  • 状態、ダークモード、構造セレクター、任意の値、グラデーション、アニメーションのためのモディファイアと条件の対応表は references/mapping.md にあります。単純なユーティリティを超えるものに遭遇したら常にそれを読んでください。
ステップ 4: スタイルを適用する

最初の 3 つのステップはどのフレームワークでも同じです。変わるのはアプリケーターだけです:

  • React DOM、Preact、react-strict-dom、およびその他の JSX スプレッドフレームワークでは、stylex.props(...) をスプレッドします。これは { className, style } を返します。
  • Solid、Svelte、Vue、Qwik、およびその他の非 React フレームワークでは、stylex.attrs(...) を使用します。これは要素にバインドするためのプレーンな class 文字列と文字列の style 値を返します。stylex.props と同じ順序でスタイルを渡してください。

次に、どちらのアプリケーターを使用する場合でも:

  • 結果を div、span、button などの小文字のホスト要素に直接適用します。古い className または class を置き換えてください。
  • コンポーネントは stylex.props や stylex.attrs の出力を自動的に受け入れません。スタイルトークンを style プロップを通して渡し、コンポーネントが自身のホスト要素にそれらを適用できるようにしてください。アプリケーターの出力をコンポーネントにそのまま渡して動作すると決めてかからないでください。
  • 条件付きの合成を維持します。アプリケーターは左から右へマージし、最後の値が優先され、cn(...) と一致します。cn('base', isActive && 'active') は stylex.props(styles.base, isActive && styles.active) または stylex.attrs を使った同じ呼び出しになります。渡されたオーバーライドは最後に残るため、呼び出し側が引き続き優先できます。
ステップ 5: 有用な場合に繰り返されるデザイントークンを抽出する

ブランドカラー、スペーシングステップ、その他のテーマ値がファイル全体で繰り返される場合、またはプロジェクトがセマンティックトークンを使用している場合は、ハードコードする代わりに .stylex.ts ファイルで stylex.defineVars を検討してください。createTheme を使用する変数は、クラスベースの dark: テーマ設定も置き換えます。references/stylex-rules.md のテーマ設定のセクションを参照してください。一回限りの値に対してこれを強制しないでください。

ステップ 6: 変換されなかったものを報告する

サマリーでは、セレクターベースのユーティリティ、解決されなかったクラス、および直接マッピングされなかったマークアップの変更をリストアップします。ホバー、ダークモード、またはレスポンシブスタイルを警告なしに失うことが、ここで最も重要な失敗です。

ステップ 7: 検証する

変更されたファイルに対して型チェックとリンターを実行します。プロジェクトに StyleX ESLint プラグインがあれば、無効な shorthand、default の欠落、および不明なプロパティを検出します。報告されたものを修正してください。移行された要素に余計な className / Tailwind のインポートが残っていないことを確認してください。Tailwind のディレクティブと設定は、1 つのファイルの後ではなく、コードベース全体が移行された後にのみ削除してください。依存関係がインストールされていない場合は、その旨を伝え、実行すると主張するのではなく、チェックを構造的なものとしてマークしてください。

実例

// Before
import { cn } from '@/lib/utils'

export function Badge({ active, className }: Props) {
  return (
    <span
      className={cn(
        'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold',
        active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600',
        className,
      )}
    >
      Status
    </span>
  )
}
// After
import * as stylex from '@stylexjs/stylex'

const styles = stylex.create({
  base: {
    display: 'inline-flex',
    alignItems: 'center',
    borderRadius: '9999px',
    paddingInline: '0.625rem',
    paddingBlock: '0.125rem',
    fontSize: '0.75rem',
    lineHeight: '1rem',
    fontWeight: 600,
  },
  active: { backgroundColor: '#dcfce7', color: '#166534' },
  inactive: { backgroundColor: '#f3f4f6', color: '#4b5563' },
})

export function Badge({ active, style }: Props) {
  return (
    <span
      {...stylex.props(styles.base, active ? styles.active : styles.inactive, style)}
    >
      Status
    </span>
  )
}

text-xs は font-size と line-height の両方を生成するため、変換では両方が維持されます。 呼び出し側の上書きは className から style に移動しました。これは StyleX がクラス文字列ではなく スタイルを渡すためです。cn の三項演算子は、同じ後勝ちの順序を持つ stylex.props の三項演算子になりました。 この例では React を使用しています。Solid、Svelte、Vue、Qwik では、styles オブジェクトは バイト単位で同一です。変わるのは stylex.props の呼び出しが stylex.attrs になることだけで、返された class/style をそのフレームワークの属性のバインド方法でバインドします。

リファレンスファイル

  • references/mapping.md には、Tailwind のモディファイアとバリアントの表、および space-x、divide、group、peer、ダークモード、任意値、グラデーション、 疑似要素、アニメーション、sr-only を扱う方法が含まれています。単純な単一値のユーティリティを 超えるものについては、これを読んでください。
  • references/stylex-rules.md では、StyleX の記述ルール、よくある間違い、プロジェクトのセットアップ、 stylex.props と stylex.attrs のフレームワークサポート、および defineVars と createTheme によるテーマ設定を扱っています。StyleX をセットアップするとき、非 React フレームワークを移行するとき、または ESLint プラグインが何かを指摘したときに読んでください。

コメント