LoginSignup
2
1

styled-componentsからPanda CSSに乗り換えるべきか

Last updated at Posted at 2023-08-29

これは何

Next.js ✕ styled-componentsで暮らしてきた人が、
Panda CSSを数日触った比較感想。

:nail_care: :scales: :panda_face:

基本比較

要素 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は、

  1. 独自の思想・仕様・便利機能(util,token,pattern)を開発者全員で学習し
  2. Panda思想にハマる要件...デバイスによる変化が少ない・統一性が高いなど

の場合、素早く/ぶれないコードが量産できる可能性は感じます。中小規模の、デザインの統一感が高いコーポレートサイトなどがハマるでしょうか。
もっと使い込んで見極めたいところです。

↑以外はstyled-componentsです。
独自知識が少なく、自由度が高く、開発環境での出力の可読性も高いです。
Next.jsでSSG/ISRすればパフォーマンスも問題ありません。

要素ごとの論点

基本記法

:nail_care:

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;
  }

`

:panda_face:

//※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';
    }
  }
})

Screenshot 2023-08-29 at 20.14.38.png

  • キャメルケース
  • 値のsyntax-heilightingが効かない
  • ネストの記法が少し特殊

で、個人的には少し読みづらくなります。
但しプロパティ名の補完は爆速です(TSの型がついているので)。

なお、classNameでの記法もあります。

<button
  className={css({
    backgroundColor: '#fff',
    border: '1px solid #000',
    color: '#000',
    padding: '0.5rem 1rem'
  })}
/>

スタイルが増えるとHTML/JSとしての可読性がゼロになるため、使う場所は少なそうです。

出力(dev環境)

←styled Panda→
Screenshot-2023-08-30-at-15.32.42.png

Pandaではコンポーネント名が出ないので要素を探せず、
探しあててもCSSではなくclass名のデバッグになります。
これは大変だ!

theme

:nail_care:

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)};
`;

:panda_face:

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値の置き場です。

:thinking: 感想

すぐに綺麗に定数が整理できるのは魅力です。
しかし、メディアクエリが使えないため、デバイスごとに変えたい値を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} />;
};

:nail_care:

const Button = styled.button<{ isActive: boolean }>`
  font-size: 24px;
  background: ${({ isActive }) => (isActive ? 'red' : 'blue')};
`;

:panda_face:

Recipeという機能を使うとこうなります

const Button = styled('button', {
  base: {
    fontSize: '24px',
  },
  variants: {
    isActive: {
      false: {
        color: 'blue',
      },
      true: {
        color: 'red',
      },
    },
  },
});

他にもいくつか方法がありますが、「コード内で明示したバリエーションから選択する」点は同じです。
CSSにビルド時に知らない値を入れることはできません。ランタイムではclassを選ぶだけなので。
つまり、CMSからfetchした色にする、ユーザーが入力した高さにする、などはできません。

:thinking: 感想

「バリエーション」を明示するRecipe記法は読みやすい!
未知の値を使いたい時は採用不能です。

ブレイクポイント

:nail_care:

自分で仕組みを作ります

const device = {
  PC: `(min-width: 1024px)`,
};

const Heading = styled.div`
 font-size: 20px;
  @media ${device.PC} {
    font-size: 24px;
  }
`;

:panda_face:

themeに置き場があります

export default defineConfig({
  theme: {
    extend: {
      breakpoints: {
        pc: '1024px',
      },
    },
  },
});
const Heading = styled('h2', {
  base: {
    fontSize: '20px',
    pc: {
      fontSize: '24px',
    },
  },
});

:thinking: 感想

便利・綺麗!

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つ作ってみる
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1