Open4

Panda CSS

nagayamanagayama

Zero runtime, Type safeをうたうCSS-in-JSライブラリ

https://panda-css.com/

Webサイトにある代表例3つ

1. style-function


import { css } from "./styled-system/css";
import { circle, stack } from "./styled-system/patterns";

function App() {
  return (
    <div className={stack({ direction: "row", p: "4" })}>
      <div className={circle({ size: "5rem", overflow: "hidden" })}>
        <img src="https://via.placeholder.com/150" alt="avatar" />
      </div>
      <div className={css({ mt: "4", fontSize: "xl", fontWeight: "semibold" })}>
        John Doe
      </div>
      <div className={css({ mt: "2", fontSize: "sm", color: "gray.600" })}>
        john@doe.com
      </div>
    </div>
  );
  • css() がスタイルとクラス名を生成して色々いい感じにしてくれる。
    • mt とか Chakra UIでもお馴染みな感じの省略記法がある
    • _hover みたいなのもある
  • circle()とか stack() とかよく使いそうな Pattern が定義されている。追加や拡張もできる
    • 🤔 UIライブラリとして再配布したい場合にこれらはエクスポートできるんだろうか

2. style-props

import { Box, Stack, Circle } from './styled-system/jsx'

function App() {
  return (
    <Stack direction="row" p="4" rounded="md" shadow="lg" bg="white">
      <Circle size="5rem" overflow="hidden">
        <img src="https://via.placeholder.com/150" alt="avatar" />
      </Circle>
      <Box mt="4" fontSize="xl" fontWeight="semibold">
        John Doe
      </Box>
      <Box mt="2" fontSize="sm" color="gray.600">
        john@doe.com
      </Box>
    </Stack>
  )
}

https://panda-css.com/docs/concepts/style-props

  • JSXで使う前提
  • PatternをJSXエレメントとして扱える。Chakraにあるようなやつ
  • 設定(panda.config.ts)で jsxFramework を指定する
  • const StyledButton = styled(Button) もある。なんでもあるな…
  • 「実行時プロパティ名を変更するのはできないよ」とある

3. style-recipes

import { styled } from './styled-system/jsx'
import { cva } from './styled-system/css'

export const badge = cva({
  base: {
    fontWeight: 'medium',
    borderRadius: 'md',
  },
  variants: {
    status: {
      default: {
        color: 'white',
        bg: 'gray.500',
      },
      success: {
        color: 'white',
        bg: 'green.500',
      },
    },
  }
})

export const Badge = styled('span', badge)

https://panda-css.com/docs/concepts/recipes

  • このcvaはあのcvaとは違いPanda用に作られたもの
    • This API was inspired by Stitches(opens in a new tab), Vanilla Extract(opens in a new tab), and Class Variance Authority(opens in a new tab).と書いてある
  • UIコンポーネントはこれとテーマ/トークンを組み合わせて作っていくという感じになりそう
nagayamanagayama

styled-sysytemってなんだ

https://panda-css.com/docs/concepts/styled-system

  • CSS-in-JSの構文(オブジェクトまたはテンプレートリテラル)をクラス名の文字列に変換するために、軽量なランタイムが必要
  • pandaまたはpanda codegenコマンドを実行する際、出力ディレクトリにランタイムが生成される
  • パターン、レシピ、JSXコンポーネントにも使用され、すべてのスタイルがcss関数を介して処理される
  • Astro や RSC などでコンポーネントを静的HTMLにプリレンダリングする場合、css関数などはビルド時に削除され、生成されたクラス名に置き換えられる

ということは、テーマやデザイントークンを更新したりすると、コードジェネレートが必要になるのかな。

nagayamanagayama

動的にスタイリングしたい場合

  • CSSを事前に生成するため色々制約がある
  • UIコンポーネントを提供するケースでは、事前に生成済みのものを使うという感じになるので考慮が必要

https://panda-css.com/docs/guides/dynamic-styling

ダメな例

// ❌ Avoid: Runtime value (without config.`staticCss`)
const Button = () => {
  const [color, setColor] = useState('red.300')
  return <styled.button color={color} />
}
 
// ❌ Avoid: Referenced value (not statically analyzable or from another file)
<styled.div color={getColor()} />
<styled.div color={colors[getColorName()]} />
<styled.div color={colors[colorFromAnotherFile]} />
 
const CustomCircle = (props) => {
  const { circleSize = '3' } = props
  return (
    <Circle
      // ❌ Avoid: Panda can't determine the value of circleSize at build-time
      size={circleSize}
    />
  )
}
nagayamanagayama

Using Panda in a Component Library

https://panda-css.com/docs/guides/component-library

PandaCSSを使って作ったデザインシステム(というかデザイントークンとUIコンポーネントの詰め合わせ)を展開する場合

おすすめパターン

↑のドキュメントで4パターン紹介されていた

  • npmに公開せず、アプリケーションコードがPandaを使用している場合 → use ship build info approach
  • アプリケーションコードがPandaを使用していない場合 → use the static css file approach
  • アプリケーションコードが内部のモノレポにある場合 → use the include src files approach
  • ライブラリコードがコンポーネントを提供せず、トークン、パターン、レシピのみを提供する場合 → use the ship preset approach

下2つは該当しなさそうなので上2つについて考えればいいか?
npmで公開する場合はどうなるんだ? → Use Panda as external package のところがそれに該当するっぽい

static css file approach

  • コンポーネントライブラリとCSSをビルドして、それらを使ってもらう
  • わかりやすい
  • 生成されたCSSをカスタマイズできない
    • これをどのくらい許容できるかだな
  • そうは言ってもアプリケーション側でもスタイリングはする
    • Pandaを使う → このパターンにマッチしなくなる、、
    • Tailwindを使う → デザインシステムの管理どうするの、、

Use Panda as external package

  • outdir用のワークスペースを作る
    • そこに styled-system を生成する
  • なので、スタイルのために@acme-org/styled-system、UIコンポーネントのために @acme-org/components2つ(以上) のパッケージを持つ感じになる
    • いや、別にUIコンポーネントも同梱したければすればいいのか
    • @acme-org/components からも @acme-org/styled-system を使うしアプリケーションからはどちらも使う
      • アプリ側でpanda.config.tsの設定が必要になる
        • アプリ側でテーマやトークンの拡張もできるのかな?できそうな気がする。

あらゆるところにPandaがいるという感じがするけど、アプリ側で個別にスタイリングできるし、拡張性もありそう。
アプリ側でPandaのインストールや設定が必要になるけど、まあそれはそういうものだしなー…

Ship the build info file

  • ↑のアプローチに似てるけど、ソースコードをshipするのではなくて、build info fileをshipする
    • なるほどー??
  • build info fileには静的抽出結果が含まれている。それを元にビルドすすれば同じ結果が得られる
    • アプリ側でこれを生成する必要がない
      • んー。presetをシップするパターンと比べた優位性がわからない、、
        • UIコンポーネントとトークンを分離するならプリセットが共有されればいいのでは?