오늘은 css 라이브러리 그중에서 개인적으로 제일 좋다고 생각하는 Panda Css에 대해서 작성을 해보겠습니다.
Panda Css가 무엇일까요 ?
과거 Next js App Router가 처음 나왔을 때 App Router에서 사용할 수 있는 css in js 라이브러리는 어떤 것들이 있을까 찾아봤습니다. 시간이 좀 지나 다시 확인해 보니
이렇게 padda css가 있더라고요. 이때부터 Panda Css에 관심이 생겨 찾아보고 테스트도 해보면서 참 좋은 라이브러리 같다는 생각이 들었습니다.
이제 본격적으로 panda css에 대해서 알아 보겠습니다 !
간단하게 사용해보자
이제 한번 사용해 볼까요 ??
Next js 세팅 공식 문서 참고하면 쉽게 사용할 수 있어요.
panda-config.ts 파일을 만들고 이렇게 세팅한 뒤에
import { defineConfig } from "@pandacss/dev";
export default defineConfig({
jsxFramework: "react",
preflight: true,
include: ["./src/**/*.{js,jsx,ts,tsx}"],
exclude: [],
jsxStyleProps: "minimal",
outdir: "styled-system",
});
outdir로 설정한 styled-system
폴더에서
styled-system
├── css
│ ├── conditions.mjs
│ ├── css.d.ts
│ ├── css.mjs
│ ├── cva.d.ts
│ ├── cva.mjs
│ ├── cx.d.ts
│ ├── cx.mjs
│ ├── index.d.ts
│ ├── index.mjs
│ ├── sva.d.ts
│ └── sva.mjs
├── helpers.mjs
├── jsx
│ ├── aspect-ratio.d.ts
│ ├── aspect-ratio.mjs
│ ├── bleed.d.ts
│ ├── bleed.mjs
│ ├── box.d.ts
│ ├── box.mjs
│ ├── center.d.ts
│ ├── center.mjs
│ ├── circle.d.ts
│ ├── circle.mjs
│ ├── container.d.ts
│ ├── container.mjs
│ ├── cq.d.ts
│ ├── cq.mjs
│ ├── divider.d.ts
│ ├── divider.mjs
│ ├── factory-helper.mjs
│ ├── factory.d.ts
│ ├── factory.mjs
│ ├── flex.d.ts
│ ├── flex.mjs
│ ├── float.d.ts
│ ├── float.mjs
│ ├── grid-item.d.ts
│ ├── grid-item.mjs
│ ├── grid.d.ts
│ ├── grid.mjs
│ ├── hstack.d.ts
│ ├── hstack.mjs
│ ├── index.d.ts
│ ├── index.mjs
│ ├── is-valid-prop.d.ts
│ ├── is-valid-prop.mjs
│ ├── link-box.d.ts
│ ├── link-box.mjs
│ ├── link-overlay.d.ts
│ ├── link-overlay.mjs
│ ├── spacer.d.ts
│ ├── spacer.mjs
│ ├── square.d.ts
│ ├── square.mjs
│ ├── stack.d.ts
│ ├── stack.mjs
│ ├── visually-hidden.d.ts
│ ├── visually-hidden.mjs
│ ├── vstack.d.ts
│ ├── vstack.mjs
│ ├── wrap.d.ts
│ └── wrap.mjs
├── patterns
│ ├── aspect-ratio.d.ts
│ ├── aspect-ratio.mjs
│ ├── bleed.d.ts
│ ├── bleed.mjs
│ ├── box.d.ts
│ ├── box.mjs
│ ├── center.d.ts
│ ├── center.mjs
│ ├── circle.d.ts
│ ├── circle.mjs
│ ├── container.d.ts
│ ├── container.mjs
│ ├── cq.d.ts
│ ├── cq.mjs
│ ├── divider.d.ts
│ ├── divider.mjs
│ ├── flex.d.ts
│ ├── flex.mjs
│ ├── float.d.ts
│ ├── float.mjs
│ ├── grid-item.d.ts
│ ├── grid-item.mjs
│ ├── grid.d.ts
│ ├── grid.mjs
│ ├── hstack.d.ts
│ ├── hstack.mjs
│ ├── index.d.ts
│ ├── index.mjs
│ ├── link-box.d.ts
│ ├── link-box.mjs
│ ├── link-overlay.d.ts
│ ├── link-overlay.mjs
│ ├── spacer.d.ts
│ ├── spacer.mjs
│ ├── square.d.ts
│ ├── square.mjs
│ ├── stack.d.ts
│ ├── stack.mjs
│ ├── visually-hidden.d.ts
│ ├── visually-hidden.mjs
│ ├── vstack.d.ts
│ ├── vstack.mjs
│ ├── wrap.d.ts
│ └── wrap.mjs
├── tokens
│ ├── index.d.ts
│ ├── index.mjs
│ └── tokens.d.ts
└── types
├── composition.d.ts
├── conditions.d.ts
├── csstype.d.ts
├── global.d.ts
├── index.d.ts
├── jsx.d.ts
├── parts.d.ts
├── pattern.d.ts
├── prop-type.d.ts
├── recipe.d.ts
├── selectors.d.ts
├── static-css.d.ts
├── style-props.d.ts
└── system-types.d.ts
이런 구조의 코드들이 만들어지는 모습입니다. 이제 사용해 볼게요.
import { css } from "../../styled-system/css";
export default function Home() {
return (
<>
<div className={css({ fontSize: "413px", color: "yellow.400" })}>
🐼 판다 css 체고 🐼
</div>
</>
);
}
이렇게 간단히 짜고 빌드를 한 뒤 켜보면
잘나오는 모습입니다 ! 좀 더 살펴볼까요 ??
<!DOCTYPE html>
<html lang="en">
<head>
<meta charSet="utf-8"/>
<meta name="viewport" content="width=device-width"/>
<meta name="next-head-count" content="2"/>
<link rel="preload" href="/_next/static/css/2aa64ec9ec764c49.css" as="style" crossorigin=""/>
<link rel="stylesheet" href="/_next/static/css/2aa64ec9ec764c49.css" crossorigin="" data-n-g=""/>
이런 식으로 2aa64ec9ec764c49.css
이라는 정적 파일을 불러오고 이 정적 파일 안에는 global css
에서 설정한 기본적인 @layer reset, base, tokens, recipes, utilities들이 들어가 있고
@layer utilities {
.text_red\.100 {
color: var(--colors-red-100)
}
.bg_gray\.100 {
background: var(--colors-gray-100)
}
.bg_blue\.100 {
background-color: var(--colors-blue-100)
}
.bg_red\.200 {
background: var(--colors-red-200)
}
.fs_413px {
font-size: 413px
}
방금 만든 fontSize : 413px
은 이렇게 fs_413px
라는 이름으로 utilities layer
안에 들어가 있네요.
특징들
공식문서 확인해 볼까요 ?
공식 문서 첫 화면으로 확인할 수 있는 건
- typescript를 지원한다. (타입 추론이 가능)
- 빌드 타임 때 스타일을 생성한다.
- React Server Component (RSC) 호환
- 정말 매력적인 특징들 같습니다. 특히
Next js App Router
에서 필수적인 RSC 호환이 너무 마음에 드네요.
typescript를 지원
요즘 웹 개발에서는 Typescript를 많이 사용합니다. 그리고 Typescript의 편리한 기능인 타입 추론이 있습니다.
export default defineConfig({
jsxFramework: "react",
preflight: true,
include: ["./src/**/*.{js,jsx,ts,tsx}"],
exclude: [],
jsxStyleProps: "minimal",
outdir: "styled-system",
theme: {
tokens: {
spacing: {
veryBig: {
value: "1000px",
},
},
},
},
});
이런 식으로 theme에 spacing veryBig이라는 값을 만들어주고
panda codegen
명령어를 통해 스타일을 생성해 주면styled-system/tokens/tokens.d.ts
경로에
export type SpacingToken = "veryBig" | "-veryBig"
이렇게 타입이 생기고
이렇게 사용할 때 나오는 모습입니다.
build time css in js
css in js
인데 앞에 build time이라는 말이 붙었네요. 이건 기존의 있던 css in js 라이브러리들과는 어떤 점이 다른지 알아보겠습니다.
build time css in js는 이름에서 알 수 있듯 js로 css를 빌드 시간에 만든다
는 의미입니다. 이에 반해 runtime css in js는 런타임에서 스타일을 동적으로 생성합니다.
runtime의 대표적인 styled-components와 build time의 panda css를 비교해 보겠습니다.
styled-components
import { useState } from "react";
import { styled } from "styled-components";
const Box = styled.div<{ color: string }>`
background-color: blue;
color: ${(props) => props.color};
`;
export default function StyledComponentsPage() {
const [state, setState] = useState(false);
return (
<Box color={state ? "black" : "red"}>
<button onClick={() => setState((prev) => !prev)}>토글</button>
</Box>
);
}
이런 식으로 간단한 코드를 짜고 브라우저 개발자도구 콘솔에
document.querySelectorAll("style")[0].sheet?.cssRules
이걸 입력해 styled components가 만든 스타일을 확인해 보겠습니다.
토글로 style을 바꾸기 전에는 하나의 cssRules이 있네요. 이제 토글 버튼을 클릭해 style을 바꾸고 확인해 보면
기존의 cssRules에 하나가 추가되어 있는 모습입니다.
이제 panda css를 살펴볼까요 ?
panda css
import { useState } from "react";
import { styled } from "../../styled-system/jsx";
const Box = styled("div", {
base: {
backgroundColor: "blue",
},
variants: {
state: {
true: {
color: "black",
},
false: {
color: "red",
},
},
},
});
export default function PandaCssPage() {
const [state, setState] = useState(false);
return (
<Box state={state}>
<button onClick={() => setState((prev) => !prev)}>토글</button>
</Box>
);
}
이런식으로 만들고 결과를 확인해 보면
<link rel="preload" href="/_next/static/css/823ea399a40ade4a.css" as="style" crossorigin="">
이 css 파일에
.bg_blue {
background-color: blue
}
.text_black {
color: #000
}
.text_red {
color: red
}
이렇게 빌드 타임 때 만들어진 style이 들어가 있는 모습입니다.
React Server Component
RSC(React Server Component) 에서 사용해 보겠습니다.
import { css } from "../../styled-system/css";
interface Data {
id: number;
name: string;
username: string;
email: string;
address: {
street: string;
city: string;
state: string;
zipcode: string;
};
}
const getData = async () => {
const result = (await fetch("https://dummyapi.online/api/users", {
cache: "no-store",
}).then((data) => data.json())) as Data[];
return result;
};
export const RSC = async () => {
const datas = await getData();
return (
<ul
className={css({
color: "#61be94",
})}
>
{datas.map((data) => (
<li key={data.id}>
{data.email} {data.address.city}
</li>
))}
</ul>
);
};
이런 식으로 간단한 서버 컴포넌트를 만들고
import { RSC } from "@/components/RSC";
import { Suspense } from "react";
export default function Home() {
return (
<main>
<Suspense fallback={<div>loading</div>}>
<RSC />
</Suspense>
</main>
);
}
이런 식으로 사용했습니다.
결과를 확인해 보면 html에
<link rel="stylesheet" href="/_next/static/css/ffe4f9e3cd3c2877.css" data-precedence="next"/>
이렇게 정적 css파일이 있고
@layer utilities {
.text_\#61be94 {
color: #61be94
}
}
스타일이 잘 생성되어 있습니다.
장점
panda css에는 수많은 장점이 있다고 생각합니다. Build Time 때 css를 정적으로 만든다. RSC 호환, 타입 추론 등 위에서 언급한 특징들은 중복된 내용이므로 제외하도록 하겠습니다.
다양한 사용 방법
여러 라이브러리에서 영감을 얻어서 그런지 모르겠지만 panda css는 여러 방법으로 사용할 수 있습니다.
기본적인 style
export default function Home() {
return (
<>
<div className={css({ fontSize: "413px", color: "yellow.400" })}>
🐼 판다 css 체고 🐼
</div>
</>
);
}
이 코드는
import { styled } from "../../styled-system/jsx";
const Box = styled("div", {
base: {
fontSize: "413px",
color: "yellow",
},
});
export default function Home() {
return <Box>🐼 판다 css 체고 🐼</Box>;
}
이렇게 사용할 수도 있어요.
Recipe
Recipes라는 것의 사용법도 다양한데
우선 위에서 봤던 것처럼
import { useState } from "react";
import { styled } from "../../styled-system/jsx";
const Box = styled("div", {
base: {
backgroundColor: "blue",
},
variants: {
state: {
true: {
color: "black",
},
false: {
color: "red",
},
},
},
});
export default function PandaCssPage() {
const [state, setState] = useState(false);
return (
<Box state={state}>
<button onClick={() => setState((prev) => !prev)}>토글</button>
</Box>
);
}
이렇게 사용할 수 있습니다.
그리고 cva
를 사용해서
const boxStyle = cva({
base: {
backgroundColor: "blue",
},
variants: {
state: {
true: {
color: "black",
},
false: {
color: "red",
},
},
},
});
export default function PandaCssPage() {
const [state, setState] = useState(false);
return (
<div
className={boxStyle({
state,
})}
>
<button onClick={() => setState((prev) => !prev)}>토글</button>
</div>
);
}
이런 식으로 처리도 가능합니다.
그리고 panda.config.ts 파일에
const boxStyle = defineRecipe({
className: "boxStyle",
base: {
backgroundColor: "blue",
},
variants: {
state: {
true: {
color: "black",
},
false: {
color: "red",
},
},
},
});
export default defineConfig({
jsxFramework: "react",
preflight: true,
include: ["./src/**/*.{js,jsx,ts,tsx}"],
exclude: [],
jsxStyleProps: "minimal",
outdir: "styled-system",
theme: {
recipes: {
boxStyle,
},
},
});
이렇게 recipes를 직접 등록하고
import { boxStyle } from "../../styled-system/recipes";
export default function PandaCssPage() {
const [state, setState] = useState(false);
return (
<div
className={boxStyle({
state,
})}
>
<button onClick={() => setState((prev) => !prev)}>토글</button>
</div>
);
}
이렇게 사용할 수 있습니다. 하지만 이방법은 제대로 작동을 안 할 수도 있는데요. 위에 styled 함수를 사용한 방법이나 cva를 사용한 방법은 나올 수 있는 css의 경우의 수들을 미리 정적으로 만들어놓지만 config 설정한 recipe는 코드에 있는 css만 만들어놓습니다. 쉽게 말하면
className={boxStyle({
state,
})}
이렇게 하면 안 되고
className={boxStyle({
state : true,
})}
이런식으로 panda가 한눈에 알아볼 수 있게 직접 명시를 해줘야 합니다.
import { boxStyle } from "../../styled-system/recipes";
boxStyle({ state: true });
boxStyle({ state: false });
export default function PandaCssPage() {
const [state, setState] = useState(false);
return (
<div
className={boxStyle({
state,
})}
>
<button onClick={() => setState((prev) => !prev)}>토글</button>
</div>
);
}
이런식으로 위에서 직접 명시를 해주면 잘 동작합니다.
편리한 유틸
panda css에는 편리한 유틸들도 있는데요. 우선 breakpoints가 좋아 보였습니다.
panda-config.ts
export default defineConfig({
jsxFramework: "react",
preflight: true,
include: ["./src/**/*.{js,jsx,ts,tsx}"],
exclude: [],
jsxStyleProps: "minimal",
outdir: "styled-system",
theme: {
recipes: {
boxStyle,
},
breakpoints: {
size1: "200px",
size2: "400px",
size3: "600px",
},
},
});
이렇게 breakpoints 를 설정해 주면 보통 저 size1, size2, size3에 해당하는 경우만 따로 처리할 수 있는데요.
panda css는 어떤 점이 좋을까요 ?
여기 사진을 보면 size1, size2, size3로 만들어진 다양한 breakpoints들이 있습니다. 이런 반응형 처리를 만들어주고 타입 추론까지 해주고... 너무 편리한 거 같습니다.
단점
지금까지 panda css의 장점에 대해서 알아봤습니다. 이제 단점에 대해서도 알아보겠습니다.
다양한 사용 방법
위에서 장점으로 언급된 다양한 사용 방법이 한편으로는 너무 높은 자유도를 부여해 단점으로 느껴질 수도 있겠다 생각했습니다. 어떤 사람은 className에 css함수로 스타일을 잡는 반면 어떤 사람은 styled 함수로 스타일을 잡을 수도 있습니다.
이러한 부분은 팀에서 스타일 컨벤션을 잡고 컨벤션 잡은 대로 개발을 해야 한다고 생각합니다.
아쉬운 스타일 생성
공식 문서의 Dynamic Styling관련된 내용을 보면 한 가지 걸리는 부분이 있습니다.
export default function Home() {
const [color, setColor] = useState("blue");
return (
<div
className={css({
color,
})}
>
🐼 판다 css 체고 🐼
</div>
);
}
이런식으로 사용하면 적용이 안되는 문제가 있는데요. 하지만
export default function Home() {
const [color, setColor] = useState("blue");
css({ color: "blue" });
return (
<div
className={css({
color,
})}
>
🐼 판다 css 체고 🐼
</div>
);
}
이렇게 css({ color: "blue" });
한번 선언을 해주면 해당 스타일이 생깁니다. 다시 css({ color: "blue" });
이 코드를 빼어 원래대로 되돌린 다음에 다시 화면을 확인해 보면
이렇게 아까는 안되던 색상이 적용되어 있습니다.
이제 빌드를 해서 결과를 보면
다시 안 나와있습니다. 이렇게 개발과정에서는 스타일이 잘 적용되어 있는 거 같더라도 막상 빌드를 하고 보면 안되어 있는 경우가 있어서 디버깅이 불편하다는 단점이 존재하는 거 같습니다.
이거랑 같이 쓰면 개꿀
- ark ui : radix ui와 같은 headless ui
- park ui : shadcn의 panda css (or tailwind) + ark ui 버전혼자 만드는 웹서비스인만큼 디자인도 혼자 했어야 해서 어려웠는데 park ui를 사용해서 편하게 디자인 시스템을 만들었던 거 같습니다.
특히 이런식으로 - 제가 원하는 색상으로 설정하면 설정한 대로 공식 문서의 스타일이 바뀌어서 더 편리했던 거 같습니다.
- 이 panda css를 사용해서 개인적으로 웹서비스를 간단하게 만들고 기능을 추가하면서 관리하는 중인데요. 이거입니다 ㅎ.
이것을 만들때 저 2개를 사용해서 만들었습니다.
'frontend' 카테고리의 다른 글
웹 어셈블리 찍먹 ! (0) | 2024.05.23 |
---|---|
싱글스레드 언어인 JS에서는 Promise가 어떻게 동작할까 ? (0) | 2024.05.15 |
🚀 "우당탕탕 도서관" 프론트엔드 개발자 글쓰기 커뮤니티 2기 모집 안내 🚀 (0) | 2024.05.04 |
??? : 추상화 잘합니다. (대충 할 말 빙빙 돌려 말한다는 뜻) (0) | 2024.04.01 |
번들러 없이 개발하다 머리깨진썰 푼다. (부제: 번들러에 대해서 공부해보자) (1) | 2024.03.26 |