CSS-in-JS とは
CSS のスタイル定義を JavaScript(TypeScript)のコードとして書き、JavaScript に CSS を生成・管理させる仕組みです。
普通、CSS は「別のファイルに書いて、HTML や JSX から className で紐付ける」ものです。CSS-in-JS では、その CSS を JavaScript の値(文字列やオブジェクト)として扱います。
CSS-in-JS が解決する問題
CSS や CSS Modules は静的なファイルとして出力されるため、値によってスタイルを変えるといった動的な処理はできません。パターンごとにクラスを用意し、JS側でそのクラス名を出し分ける、という迂回策を取る必要があります。
CSS-in-JS は、スタイル定義自体が JavaScript です。そのため props や state といった実行時の値を、条件分岐や関数を使いながらそのままスタイルに反映できます。これが CSS-in-JS の最も大きな魅力です。
以下の記事で CSS Modules について解説していますので併せてご覧ください。
https://qiita.com/whopper1962/items/53c8b2b5fca969b83117
CSS-in-JS の種類
CSS-in-JS は、styled-components や Emotion などの「ランタイム CSS-in-JS」と、vanilla-extract、Panda CSS、StyleX などの「ゼロランタイム CSS-in-JS」の 2 種類に分かれています。
ランタイム CSS-in-JS は、コンポーネントのレンダリング時に JavaScript がスタイルを生成し、style タグとして挿入します。書きやすく動的スタイルが得意な反面、追加の JS バンドル量やランタイムでのスタイル挿入といったコストが伴います。
ゼロランタイム CSS-in-JS は、ビルド時にスタイルを静的な CSS ファイルとして抽出することで、このコストを避けられます。
本記事では、書きやすさと実績を重視してランタイム CSS-in-JS を紹介します。ゼロランタイム CSS-in-JS については、別の記事で扱う予定です。
styled-components について
ランタイム CSS-in-JS の長年のデファクトスタンダードだった styled-components は、2025 年 3 月にメンテナンスモードへの移行を発表しました。重大なバグやセキュリティ問題には対応するものの、新機能の開発は行われない、というアナウンスです。
そのため本記事では、styled-components によく似た Emotion を紹介します。
Emotion の導入方法
@emotion/react と @emotion/styled をインストールします。
npm install @emotion/react @emotion/styled
Vite の設定に Emotion を組み込みます。
// vite.config.ts
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [
react({
jsxImportSource: "@emotion/react",
}),
],
});
jsxImportSource を指定するだけで、css prop は動作します。
より快適に開発するためのオプションとして、@emotion/babel-plugin があります。ソースマップの改善やデッドコード削除、minify などを行ってくれますが、必須ではありません。Vite 8 以上のように Babel を標準搭載しない環境で使う場合は、@rolldown/plugin-babel などを介して導入します。
// vite.config.ts
import react from "@vitejs/plugin-react";
import babel from "@rolldown/plugin-babel";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
react({
jsxImportSource: "@emotion/react",
}),
babel({
plugins: ["@emotion/babel-plugin"],
}),
],
});
tsconfig.app.json に jsxImportSource を追加します。
// tsconfig.app.json
{
// ...
"compilerOptions": {
// ...
"jsxImportSource": "@emotion/react",
},
}
Emotion の使い方
■ スタイルを定義する
styled を使うと、HTML タグにスタイルを持たせた React コンポーネントを作れます。
import styled from '@emotion/styled'
const StyledButton = styled.button`
padding: 8px 16px;
background-color: #2563eb;
color: white;
`
export default function Button() {
return <StyledButton>Click</StyledButton>
}
styled.button のように、styled に続けてHTMLタグ名を指定します。styled.div なら div タグ、styled.p なら p タグにスタイルが適用されます。
続くバッククォートの中には、CSSファイルと同じ感覚でスタイルを書きます。
できあがったコンポーネントは、他のコンポーネントと同じように JSX 内で使えます。
■ 引数を受け取り、動的スタイルを定義する
styled で作ったコンポーネントは、props を受け取ってスタイルに反映できます。
const StyledText = styled.p`
color: ${(props) => props.color};
`
<StyledText color="brown">This is text.</StyledText>
バッククォートの中に ${(props) => ...} という形で関数を書くと、コンポーネントに渡した props をスタイルの中で参照できます。
呼び出す側は、通常のコンポーネントと同じように props を渡すだけです。
TypeScript では、styled.p<{ color: string }> のようにジェネリクスで props の型を指定します。
const StyledText = styled.p<{ color: string }>`
color: ${(props) => props.color};
`
型を指定すると、props に補完が効くようになり、誤った型の値を渡した場合はエラーになります。