これは何
Next.js ✕ styled-componentsで暮らしてきた人が、
Panda CSSを数日触った比較感想。
基本比較
要素 | styled-components | Panda CSS |
---|---|---|
思想 | 君がJSで好きにして | 僕に何でも言ってみ |
CSS in JS | ◯ | ◯ |
JSX記法 | ◯ | ◯ |
実行タイミング | ランタイム/ビルドタイム | ビルドタイム |
出力CSS | 書いた通り | util class祭り |
theme | 自由オブジェクト | 詳細な既成リストを埋めていく |
スタイル出し分け | JS変数でご自由に | 「Recipe」「Pattern」 |
スタイル共通化 | Reactでご自由に | 「Recipe」「Pattern」「theme作り込み」 |
styled-componentsはCSS in JSライブラリであり、propsやtheme ContextなどのReact APIを提供し、あとはユーザーにお任せです。
Panda CSSは↑と似た機能に加えて、縛りのある高機能な管理・整理の仕組みと多数のユーティリティーを持つフレームワークです。
先に暫定の選び方
機能上確定
- アプリケーション外から取得する未定な値を、CSSで多用したい(管理画面でテーマカラーを設定するなど)→styled-componentsしかできない
- UIコンポーネントをサーバーコンポーネントにしたい→Pandaしかできない
それ以外の場合
Pandaは、
- 独自の思想・仕様・便利機能(util,token,pattern)を開発者全員で学習し
- Panda思想にハマる要件...デバイスによる変化が少ない・統一性が高いなど
の場合、素早く/ぶれないコードが量産できる可能性は感じます。中小規模の、デザインの統一感が高いコーポレートサイトなどがハマるでしょうか。
もっと使い込んで見極めたいところです。
↑以外はstyled-componentsです。
独自知識が少なく、自由度が高く、開発環境での出力の可読性も高いです。
Next.jsでSSG/ISRすればパフォーマンスも問題ありません。
要素ごとの論点
基本記法
const Button = styled.button`
background-color: #fff;
border: 1px solid #000;
color: #000;
padding: 0.5rem 1rem;
span {
display: inline-block;
}
&:hover {
opacity: 0.7;
}
`
//※JSXのオブジェクト記法の場合。
const Button = styled('button', {
base: {
backgroundColor: '#fff',
border: '1px solid #000',
color: '#000',
padding: '0.5rem 1rem'
'& span':{
display:'inline-block'
},
_hover:{
opacity:'0.7';
}
}
})
- キャメルケース
- 値のsyntax-heilightingが効かない
- ネストの記法が少し特殊
で、個人的には少し読みづらくなります。
但しプロパティ名の補完は爆速です(TSの型がついているので)。
なお、classNameでの記法もあります。
<button
className={css({
backgroundColor: '#fff',
border: '1px solid #000',
color: '#000',
padding: '0.5rem 1rem'
})}
/>
スタイルが増えるとHTML/JSとしての可読性がゼロになるため、使う場所は少なそうです。
出力(dev環境)
Pandaではコンポーネント名が出ないので要素を探せず、
探しあててもCSSではなくclass名のデバッグになります。
これは大変だ!
theme
theme contextという機能で、中身は自由です。呼び出しには不思議な関数記法が必要です。
const theme = {
colors: {
primary: 'blue',
secondary: 'red'
},
myOwnThemeObj: {
standard: '1px solid gray'
}
}
const App = () => (
<ThemeProvider theme={theme}>
<Button>Button</Button>
</ThemeProvider>
)
const Button = styled.button`
color: ${(({ theme }) => theme.colors.primary)};
`;
Design Tokensという機能で、カラー・フォント・余白など10種類以上の規定の「値置き場」があります。呼び出しはシンプルに文字列です。
export default defineConfig({
theme: {
tokens: {
colors: {
primary: { value: 'blue' },
secondary: {value: 'red'}
},
border: {
...
}
},
},
});
const Button = styled('button', {
base: {
color:'primary'
},
});
各定数は
- 正しいプロパティでのみ呼べます。例えばカラーの定数はcolorやbackgroundカラー内でしか呼べません。
-
border: 1px solid ${color.primary}
など、値一部として呼ぶことはできません。あくまでフルのCSS値の置き場です。
感想
すぐに綺麗に定数が整理できるのは魅力です。
しかし、メディアクエリが使えないため、デバイスごとに変えたい値をtokenにしようとすれば、別々に設定し、呼び出し側で出し分ける必要があります。
例えば「見出しフォントサイズ」をtoken管理すればこうでしょうか
export default defineConfig({
theme: {
tokens: {
fontSize: {
s: {
sp: {
value: '30px',
},
pc: {
value: '60px',
},
},
},
},
},
});
const Heading = styled('div', {
base: {
fontSize: 's.sp',
pc: {
fontSize: 's.pc',
},
},
});
利用側で出し分けを行うくらいなら、
色々隠蔽した「見出しコンポーネント」を作ってそこにfontSizeも持てば良い気もします。
<Heading level={2} />
ということで、tokenはcolorとfontsくらいしか用途が思い当たりません。
デザインによるのか...?
スタイルのバリエーション
const App: React.FC = () => {
const [isActive, setIsActive] = useState(false);
return <Button isActive={isActive} />;
};
const Button = styled.button<{ isActive: boolean }>`
font-size: 24px;
background: ${({ isActive }) => (isActive ? 'red' : 'blue')};
`;
Recipeという機能を使うとこうなります
const Button = styled('button', {
base: {
fontSize: '24px',
},
variants: {
isActive: {
false: {
color: 'blue',
},
true: {
color: 'red',
},
},
},
});
他にもいくつか方法がありますが、「コード内で明示したバリエーションから選択する」点は同じです。
CSSにビルド時に知らない値を入れることはできません。ランタイムではclassを選ぶだけなので。
つまり、CMSからfetchした色にする、ユーザーが入力した高さにする、などはできません。
感想
「バリエーション」を明示するRecipe記法は読みやすい!
未知の値を使いたい時は採用不能です。
ブレイクポイント
自分で仕組みを作ります
const device = {
PC: `(min-width: 1024px)`,
};
const Heading = styled.div`
font-size: 20px;
@media ${device.PC} {
font-size: 24px;
}
`;
themeに置き場があります
export default defineConfig({
theme: {
extend: {
breakpoints: {
pc: '1024px',
},
},
},
});
const Heading = styled('h2', {
base: {
fontSize: '20px',
pc: {
fontSize: '24px',
},
},
});
感想
便利・綺麗!
Pandaにしかない機能
Pattern(Premade)
中央寄せ、グリッドなどのレイアウトを助けるコンポーネントがあります。
これらも全てメディアクエリによる出し分けに対応しておらず、用途はかなり限られる印象です。Centerは便利でした
Pattern(カスタム)
export default defineConfig({
patterns: {
extend: {
myContainer: {
description: 'コンテナ',
properties: {
width: { type: 'enum', value: ['s', 'l'] },
},
transform(props: any) {
const { width, ...rest } = props;
return {
maxWidth: width === 'l' ? '680px' : '500px',
padding: '0 16px',
margin: '0 auto',
pc: {
padding: '100px',
},
...rest,
};
},
},
},
},
});
import { MyContainer } from 'panda/jsx';
const Content: React.FC<Props> = ({ content }) => (
<MyContainer width="l">
{content}
</MyContainer>
);
引数を自由に定義できる便利コンポーネントが作れます。
HTML要素1つで成立するものにしか使えず、自前でReactコンポーネントを作ることに対する優位はわかりません。
TODO
- spacing tokenの学習
- 中規模サイトをもう1つ作ってみる