Panda CSS完全ガイド — ビルド時CSS-in-JSでゼロランタイムの高速スタイリング
Panda CSSは、ビルド時に静的CSSを生成する次世代のCSS-in-JSライブラリです。ゼロランタイムでパフォーマンスに影響を与えず、完全な型安全性を提供し、直感的なAPIでスタイリングできます。この記事では、Panda CSSの基本から実践的な使い方まで徹底的に解説します。
Panda CSSとは
Panda CSSは、Chakra UIチームが開発した、ビルド時にCSSを生成する新しいスタイリングソリューションです。従来のCSS-in-JSライブラリの課題を解決し、パフォーマンスと開発体験の両立を実現します。
主な特徴
- ゼロランタイム - ビルド時にCSSを生成、ランタイムコストなし
- 完全型安全 - TypeScriptで完全な型推論とオートコンプリート
- ユーティリティファースト - Tailwind風の記述も可能
- レシピパターン - コンポーネントバリアントの定義が簡単
- トークンベース - デザイントークンで一貫したデザインシステム
- 条件付きスタイル - レスポンシブ・ダークモード・Hover等を直感的に
- フレームワーク非依存 - React、Vue、Solid、Svelteで動作
- 小さなバンドルサイズ - 生成されるCSSのみで、JS実行なし
なぜPanda CSSなのか
// 従来のCSS-in-JS(styled-components等)
// ❌ ランタイムでスタイル計算 → パフォーマンス低下
// ❌ バンドルサイズ増加
// ❌ SSR時のハイドレーションコスト
// Panda CSS
// ✅ ビルド時にCSS生成 → ゼロランタイム
// ✅ 静的CSS出力でキャッシュ可能
// ✅ 型安全で開発体験向上
// ✅ ファイルサイズ最小化
インストールとセットアップ
初期設定
# Panda CSSのインストール
npm install -D @pandacss/dev
# 初期化
npx panda init
# PostCSSプラグイン(オプション)
npm install -D @pandacss/postcss
設定ファイル
// panda.config.ts
import { defineConfig } from '@pandacss/dev';
export default defineConfig({
// 監視対象ファイル
include: ['./src/**/*.{js,jsx,ts,tsx}'],
exclude: [],
// 出力ディレクトリ
outdir: 'styled-system',
// プリフライト(リセットCSS)
preflight: true,
// JSX framework
jsxFramework: 'react',
// テーマ設定
theme: {
extend: {
tokens: {
colors: {
primary: { value: '#0070f3' },
secondary: { value: '#ff4081' },
},
spacing: {
sm: { value: '0.5rem' },
md: { value: '1rem' },
lg: { value: '2rem' },
},
},
},
},
});
package.jsonのスクリプト
{
"scripts": {
"prepare": "panda codegen",
"dev": "panda --watch"
}
}
CSSのインポート
// src/index.tsx or src/App.tsx
import './index.css';
/* src/index.css */
@layer reset, base, tokens, recipes, utilities;
基本的な使い方
cssユーティリティ
import { css } from '../styled-system/css';
export const Button = () => {
return (
<button
className={css({
bg: 'blue.500',
color: 'white',
px: '4',
py: '2',
borderRadius: 'md',
fontWeight: 'bold',
_hover: {
bg: 'blue.600',
},
})}
>
Click me
</button>
);
};
JSXスタイルプロップ
import { styled } from '../styled-system/jsx';
export const Box = () => {
return (
<styled.div
bg="gray.100"
p="4"
borderRadius="lg"
_hover={{
bg: 'gray.200',
}}
>
Styled Box
</styled.div>
);
};
カスタムコンポーネント
import { styled } from '../styled-system/jsx';
// styled.divベースのカスタムコンポーネント
const Card = styled('div', {
base: {
bg: 'white',
p: '6',
borderRadius: 'lg',
boxShadow: 'md',
},
});
export const MyCard = () => {
return (
<Card>
<h2>Card Title</h2>
<p>Card content goes here</p>
</Card>
);
};
レスポンシブデザイン
ブレークポイント
// panda.config.ts
export default defineConfig({
theme: {
extend: {
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
},
},
},
});
レスポンシブスタイル
import { css } from '../styled-system/css';
export const ResponsiveBox = () => {
return (
<div
className={css({
// モバイル
fontSize: 'sm',
p: '2',
// タブレット以上
md: {
fontSize: 'md',
p: '4',
},
// デスクトップ以上
lg: {
fontSize: 'lg',
p: '6',
},
})}
>
Responsive content
</div>
);
};
配列記法
<styled.div
fontSize={['sm', 'md', 'lg', 'xl']}
padding={['2', '4', '6', '8']}
// sm: sm, md: md, lg: lg, xl: xl
>
Array notation
</styled.div>
デザイントークン
カラートークン
// panda.config.ts
export default defineConfig({
theme: {
extend: {
tokens: {
colors: {
brand: {
50: { value: '#e3f2fd' },
100: { value: '#bbdefb' },
200: { value: '#90caf9' },
300: { value: '#64b5f6' },
400: { value: '#42a5f5' },
500: { value: '#2196f3' },
600: { value: '#1e88e5' },
700: { value: '#1976d2' },
800: { value: '#1565c0' },
900: { value: '#0d47a1' },
},
},
},
},
},
});
使用例:
<styled.button bg="brand.500" color="white" _hover={{ bg: 'brand.600' }}>
Brand Button
</styled.button>
スペーシングトークン
export default defineConfig({
theme: {
extend: {
tokens: {
spacing: {
xs: { value: '0.25rem' },
sm: { value: '0.5rem' },
md: { value: '1rem' },
lg: { value: '2rem' },
xl: { value: '4rem' },
},
},
},
},
});
セマンティックトークン
export default defineConfig({
theme: {
extend: {
semanticTokens: {
colors: {
bg: {
primary: { value: '{colors.white}' },
secondary: { value: '{colors.gray.100}' },
},
text: {
primary: { value: '{colors.gray.900}' },
secondary: { value: '{colors.gray.600}' },
},
},
},
},
},
});
レシピパターン(Recipes)
基本的なレシピ
// panda.config.ts
export default defineConfig({
theme: {
extend: {
recipes: {
button: {
base: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
px: '4',
py: '2',
borderRadius: 'md',
fontWeight: 'semibold',
cursor: 'pointer',
transition: 'all 0.2s',
},
variants: {
variant: {
solid: {
bg: 'blue.500',
color: 'white',
_hover: { bg: 'blue.600' },
},
outline: {
border: '2px solid',
borderColor: 'blue.500',
color: 'blue.500',
_hover: { bg: 'blue.50' },
},
ghost: {
color: 'blue.500',
_hover: { bg: 'blue.50' },
},
},
size: {
sm: { px: '3', py: '1', fontSize: 'sm' },
md: { px: '4', py: '2', fontSize: 'md' },
lg: { px: '6', py: '3', fontSize: 'lg' },
},
},
defaultVariants: {
variant: 'solid',
size: 'md',
},
},
},
},
},
});
使用例:
import { button } from '../styled-system/recipes';
export const Button = ({ variant, size, children }) => {
return <button className={button({ variant, size })}>{children}</button>;
};
// 使用
<Button variant="solid" size="lg">Click me</Button>
<Button variant="outline" size="sm">Cancel</Button>
<Button variant="ghost">Ghost Button</Button>
複雑なレシピ
export default defineConfig({
theme: {
extend: {
recipes: {
card: {
base: {
bg: 'white',
borderRadius: 'lg',
overflow: 'hidden',
},
variants: {
variant: {
elevated: {
boxShadow: 'lg',
},
outline: {
border: '1px solid',
borderColor: 'gray.200',
},
filled: {
bg: 'gray.100',
},
},
size: {
sm: { p: '4' },
md: { p: '6' },
lg: { p: '8' },
},
interactive: {
true: {
cursor: 'pointer',
transition: 'all 0.2s',
_hover: {
transform: 'translateY(-2px)',
boxShadow: 'xl',
},
},
},
},
compoundVariants: [
{
variant: 'outline',
interactive: true,
css: {
_hover: {
borderColor: 'blue.500',
},
},
},
],
defaultVariants: {
variant: 'elevated',
size: 'md',
},
},
},
},
},
});
スロットレシピ(Slot Recipes)
複数パーツを持つコンポーネント向け:
// panda.config.ts
export default defineConfig({
theme: {
extend: {
slotRecipes: {
alert: {
slots: ['root', 'icon', 'title', 'description'],
base: {
root: {
display: 'flex',
gap: '3',
p: '4',
borderRadius: 'md',
},
icon: {
flexShrink: 0,
fontSize: 'xl',
},
title: {
fontWeight: 'bold',
fontSize: 'md',
},
description: {
fontSize: 'sm',
color: 'gray.600',
},
},
variants: {
status: {
info: {
root: { bg: 'blue.50', borderLeft: '4px solid', borderColor: 'blue.500' },
icon: { color: 'blue.500' },
title: { color: 'blue.800' },
},
success: {
root: { bg: 'green.50', borderLeft: '4px solid', borderColor: 'green.500' },
icon: { color: 'green.500' },
title: { color: 'green.800' },
},
warning: {
root: { bg: 'yellow.50', borderLeft: '4px solid', borderColor: 'yellow.500' },
icon: { color: 'yellow.500' },
title: { color: 'yellow.800' },
},
error: {
root: { bg: 'red.50', borderLeft: '4px solid', borderColor: 'red.500' },
icon: { color: 'red.500' },
title: { color: 'red.800' },
},
},
},
defaultVariants: {
status: 'info',
},
},
},
},
},
});
使用例:
import { alert } from '../styled-system/recipes';
export const Alert = ({ status, title, description }) => {
const classes = alert({ status });
return (
<div className={classes.root}>
<div className={classes.icon}>ℹ️</div>
<div>
<div className={classes.title}>{title}</div>
<div className={classes.description}>{description}</div>
</div>
</div>
);
};
// 使用
<Alert status="success" title="Success!" description="Your action completed." />
条件付きスタイル
疑似クラス
<styled.button
bg="blue.500"
color="white"
_hover={{ bg: 'blue.600' }}
_active={{ bg: 'blue.700' }}
_focus={{ outline: '2px solid', outlineColor: 'blue.400' }}
_disabled={{ opacity: 0.5, cursor: 'not-allowed' }}
>
Interactive Button
</styled.button>
疑似要素
<styled.div
position="relative"
_before={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '2px',
bg: 'blue.500',
}}
_after={{
content: '"→"',
ml: '2',
}}
>
Content with pseudo-elements
</styled.div>
データ属性
<styled.div
data-state="active"
_data-state-active={{ bg: 'blue.500', color: 'white' }}
_data-state-inactive={{ bg: 'gray.100', color: 'gray.600' }}
>
Data attribute styling
</styled.div>
ダークモード対応
設定
// panda.config.ts
export default defineConfig({
conditions: {
dark: '[data-theme=dark] &',
},
// または
// conditions: {
// dark: '.dark &',
// },
});
使用例
<styled.div
bg="white"
color="gray.900"
_dark={{
bg: 'gray.900',
color: 'white',
}}
>
Dark mode aware component
</styled.div>
セマンティックトークンでダークモード
// panda.config.ts
export default defineConfig({
theme: {
extend: {
semanticTokens: {
colors: {
bg: {
primary: {
value: { base: '{colors.white}', _dark: '{colors.gray.900}' },
},
secondary: {
value: { base: '{colors.gray.100}', _dark: '{colors.gray.800}' },
},
},
text: {
primary: {
value: { base: '{colors.gray.900}', _dark: '{colors.white}' },
},
secondary: {
value: { base: '{colors.gray.600}', _dark: '{colors.gray.400}' },
},
},
},
},
},
},
});
使用例:
<styled.div bg="bg.primary" color="text.primary">
Automatically adapts to dark mode
</styled.div>
パターンとレイアウト
Flexレイアウト
import { flex } from '../styled-system/patterns';
<div
className={flex({
direction: 'row',
align: 'center',
justify: 'space-between',
gap: '4',
})}
>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>;
Gridレイアウト
import { grid } from '../styled-system/patterns';
<div
className={grid({
columns: 3,
gap: '4',
})}
>
<div>Cell 1</div>
<div>Cell 2</div>
<div>Cell 3</div>
</div>;
Stackパターン
import { stack, hstack, vstack } from '../styled-system/patterns';
// 垂直スタック
<div className={vstack({ gap: '4' })}>
<div>Item 1</div>
<div>Item 2</div>
</div>;
// 水平スタック
<div className={hstack({ gap: '4' })}>
<div>Item 1</div>
<div>Item 2</div>
</div>;
カスタムパターン
// panda.config.ts
export default defineConfig({
patterns: {
container: {
properties: {
maxWidth: { type: 'string' },
},
transform(props) {
return {
maxWidth: props.maxWidth || '1200px',
mx: 'auto',
px: '4',
};
},
},
},
});
使用例:
import { container } from '../styled-system/patterns';
<div className={container({ maxWidth: '1400px' })}>Container content</div>;
アニメーション
トランジション
<styled.div
bg="blue.500"
transition="all 0.3s ease-in-out"
_hover={{
bg: 'blue.600',
transform: 'scale(1.05)',
}}
>
Animated on hover
</styled.div>
キーフレームアニメーション
// panda.config.ts
export default defineConfig({
theme: {
extend: {
keyframes: {
fadeIn: {
'0%': { opacity: 0 },
'100%': { opacity: 1 },
},
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: 0 },
'100%': { transform: 'translateY(0)', opacity: 1 },
},
},
},
},
});
使用例:
<styled.div animation="fadeIn 0.5s ease-in">Fade in animation</styled.div>
<styled.div animation="slideUp 0.3s ease-out">Slide up animation</styled.div>
Vueでの使用
<script setup lang="ts">
import { css } from '../styled-system/css';
const buttonClass = css({
bg: 'blue.500',
color: 'white',
px: '4',
py: '2',
borderRadius: 'md',
_hover: { bg: 'blue.600' },
});
</script>
<template>
<button :class="buttonClass">Click me</button>
</template>
Solidでの使用
import { css } from '../styled-system/css';
export const Button = () => {
return (
<button
class={css({
bg: 'blue.500',
color: 'white',
px: '4',
py: '2',
_hover: { bg: 'blue.600' },
})}
>
Click me
</button>
);
};
パフォーマンス最適化
Atomic CSS生成
Panda CSSは自動的にAtomic CSSを生成:
/* 生成されるCSS */
.bg_blue\\.500 {
background: #3b82f6;
}
.color_white {
color: #fff;
}
.px_4 {
padding-left: 1rem;
padding-right: 1rem;
}
未使用CSSの削除
ビルド時に使用されているスタイルのみ生成されるため、バンドルサイズは最小化されます。
静的抽出
# ビルド時に静的CSS生成
npm run build
まとめ
Panda CSSは、CSS-in-JSの新しいパラダイムを提案する革新的なライブラリです。
主な利点:
- ゼロランタイムで最高のパフォーマンス
- 完全な型安全性で開発体験向上
- レシピパターンで再利用可能なスタイル
- デザイントークンで一貫したデザインシステム
- フレームワーク非依存で柔軟
こんなプロジェクトに最適:
- パフォーマンスが重要なアプリケーション
- 型安全なスタイリングが必要
- デザインシステムを構築したい
- ユーティリティファーストが好き
Panda CSSは、styled-componentsやEmotionのような従来のCSS-in-JSライブラリの問題を解決し、Tailwind CSSのような直感性とTypeScriptの型安全性を組み合わせた、次世代のスタイリングソリューションです。