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

# これは何
Next.js ✕ styled-componentsで暮らしてきた人が、
[Panda CSS](https://panda-css.com/)を数日触った比較感想。

: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:
```jsx
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
//※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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233072/06d9dd5b-1e9c-a250-327b-4369e4e972f0.png)
- キャメルケース
- 値のsyntax-heilightingが効かない
- ネストの記法が少し特殊

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


なお、classNameでの記法もあります。
```jsx
<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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233072/4a2d0878-af94-0e35-a465-9905f9b090d0.png)


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

## theme
### :nail_care: 
theme contextという機能で、中身は自由です。呼び出しには不思議な関数記法が必要です。
```jsx
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](https://panda-css.com/docs/theming/tokens)という機能で、カラー・フォント・余白など10種類以上の規定の「値置き場」があります。呼び出しはシンプルに文字列です。

```jsx
export default defineConfig({
  theme: {
    tokens: {
      colors: {
        primary: { value: 'blue' },
        secondary: {value: 'red'}
      },
      border: {
        ...
      }
    },
  },
});

```
```jsx
const Button = styled('button', {
  base: {
    color:'primary'
  },
});
```

各定数は
- 正しいプロパティでのみ呼べます。例えばカラーの定数はcolorやbackgroundカラー内でしか呼べません。
- `border: 1px solid ${color.primary}` など、値一部として呼ぶことはできません。あくまでフルのCSS値の置き場です。

### :thinking: 感想  
すぐに綺麗に定数が整理できるのは魅力です。
しかし、メディアクエリが使えないため、デバイスごとに変えたい値をtokenにしようとすれば、別々に設定し、呼び出し側で出し分ける必要があります。

例えば「見出しフォントサイズ」をtoken管理すればこうでしょうか
```jsx
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も持てば良い気もします。
```jsx
<Heading level={2} />
```

ということで、tokenはcolorとfontsくらいしか用途が思い当たりません。
デザインによるのか...?

## スタイルのバリエーション
```jsx
const App: React.FC = () => {
  const [isActive, setIsActive] = useState(false);
  return <Button isActive={isActive} />;
};
```


### :nail_care: 

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

### :panda_face: 

[Recipe](https://panda-css.com/docs/concepts/recipes)という機能を使うとこうなります

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

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


### :thinking: 感想

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

## ブレイクポイント
### :nail_care: 
自分で仕組みを作ります
```jsx
const device = {
  PC: `(min-width: 1024px)`,
};

const Heading = styled.div`
 font-size: 20px;
  @media ${device.PC} {
    font-size: 24px;
  }
`;
```
### :panda_face: 
themeに置き場があります
```jsx
export default defineConfig({
  theme: {
    extend: {
      breakpoints: {
        pc: '1024px',
      },
    },
  },
});
const Heading = styled('h2', {
  base: {
    fontSize: '20px',
    pc: {
      fontSize: '24px',
    },
  },
});
```
### :thinking: 感想
便利・綺麗！

# Pandaにしかない機能

## Pattern(Premade)
https://panda-css.com/docs/concepts/patterns

中央寄せ、グリッドなどのレイアウトを助けるコンポーネントがあります。
これらも全てメディアクエリによる出し分けに対応しておらず、用途はかなり限られる印象です。Centerは便利でした

## Pattern(カスタム)

```jsx
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,
          };
        },
      },
    },
  },
});
```
```jsx
import { MyContainer } from 'panda/jsx';

const Content: React.FC<Props> = ({ content }) => (
  <MyContainer width="l">
    {content}
  </MyContainer>
);
```

引数を自由に定義できる便利コンポーネントが作れます。
HTML要素１つで成立するものにしか使えず、自前でReactコンポーネントを作ることに対する優位はわかりません。

# TODO
- spacing tokenの学習
- 中規模サイトをもう１つ作ってみる