PandaCSS์ ํจ๊ป CSS-in-JS์ ๋ฏธ๋๋ก
- #PandaCSS
- #CSS-in-JS
- #styled-component
์๋ ํ์ธ์. ํ๋กฌ ์คํ ์ด ํ์์ ํ ์คํธ์ ์ด๊ฒ์ ๊ฒ ์ ๋ํ๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ๊นํํฌ์ ๋๋ค.
์ ๊ฐ ํ๋กฌ ํ์ ํฉ๋ฅํ์ง 9๊ฐ์์ด ๋์ด๊ฐ๋๋ค. 5์์ ์คํํ ํ๋กฌ ์คํ ์ด๋ ๋ค์ํ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋๋ฉฐ ์ปค์ ธ์์ต๋๋ค. ์น๋ทฐ๋ B2B ์๋น์ค๋ฅผ ํฌํจํด ๋ค์ํ ํ๋ก์ ํธ์ ๊ธฐ์ ์คํ๋ ๋์ ๋๊ณ ์์ต๋๋ค.
์ค๋์ ์ ํฌ ํ์ด ์๋ก์ด ํ๋ก์ ํธ๋ค์ PandaCSS๋ฅผ ๋์ ํ๊ฒ ๋ ๋ฐฐ๊ฒฝ๊ณผ ์ด์ , ์ฃผ์ํด์ผํ ์ , ์ ์ฝ ๋ฑ์ ์๊ฐํด๋ณด๋ ค ํฉ๋๋ค. CSS in JS ์ ๋ฐํ์ ์ฉ๋์ด๋ ์ฑ๋ฅ ์ด์, ๋์์ธ ์์คํ ๋ฑ์ ๊ด์ฌ์ด ์๋ ๋ถ์ด๋ผ๋ฉด ํนํ ์ ์ฉํ๊ฒ ๋๊ปด์ง์๋ฆฌ๋ผ ์๊ฐํฉ๋๋ค.
styled component์์ ์ ์ฆ์ ์๊ฐ
styled component๋ฅผ ์ฌ์ฉํ๋ ๋ถ๋ค์๊ฒ ์ด๋ค ์ ์ด ์ข์๋์ง ๋ฌผ์ด๋ณด๋ฉด ๋น์ทํ ์ด์ผ๊ธฐ๋ฅผ ํด์ฃผ์๊ณ ๋ ํฉ๋๋ค. Component๋ก ๋ง๋ค๋ฉด์ jsx ๋งํฌ์ ์ด ๊น๋ํด์ง๋ค๊ฑฐ๋. css์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ฝ๋ค๋ ์์ผ๋ก ๋ง์ด์ฃ . ์ ๋ ๋์ํ๊ณ ์ ํฌ ํ์์๋ styled component๋ฅผ ์ฐ๋ฉด์ css์ ๋ํ ์ดํด๊ฐ ๊น์ด์ง๊ณ , ๋ฉ์ง๊ฒ ํด๊ฒฐํ ์ ์์๋ ๊ฒฝ์ฐ๋ ๋ง์ง ์์๋ ์ถ์ต๋๋ค.
ํ์ง๋ง ํด์ธ ์ฌ์ฉ์ ๋ถ๋ค์ด ๋น ๋ฅด๊ฒ ํ์ด์ง๋ฅผ ๋ณด์ค ์ ์๋๋ก ์ต์ ํํ๋ ค ๊ณ ๋ฏผํ๋ ์ค, ๋ฒ๋ค ์ฉ๋์ด ์ปค์ง๋ค๊ฑฐ๋, ๋ฐํ์ ์๋๊ฐ ๋๋ ค์ง๋ ๋ฑ์ ์ด์๋ฅผ ๋ฐ๊ฒฌํ๊ธฐ ์์ํ์ต๋๋ค.
์ฌ์ฉํ์ง ์๋ CSS๊ฐ ๋ฒ๋ค์ ํฌํจ๋จ
์ด๋ฏธ์ง๋ ํฐํธ ๋ฑ์ ๊ฒฝ๋ํํ๊ณ ๋๋, ์ฌ์ดํธ ์ฑ๋ฅ์ JS ๋ฒ๋ค์ ๋ค์ด ๋ฐ๊ณ hydrationํ ๋๊น์ง์ ์๊ฐ์ผ๋ก ๊ฒฐ์ ๋์์ต๋๋ค. ํนํ code spliting์ ์ ์ฉํ๋ฉด ๊ฐ ํ์ด์ง๋ง๋ค ๋ ๋ฒ๋ค์ ๋ค์ด ๋ฐ์์ผ ํ๊ธฐ ๋๋ฌธ์ ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ์ค์ด๋ ๊ฒ ์ค์ํ์ต๋๋ค.
๊ทธ๋ฌ๋ฉด JS ๋ฒ๋ค ํฌ๊ธฐ์ CSS๊ฐ ๋ฌด์จ ์๊ด์ผ๊น์? CSS in JS๋ผ๋ ๋ง์ฒ๋ผ styled component์ ์คํ์ผ์ js์ด๊ณ ๋ฒ๋ค์ ํฌํจ๋๋ ๊ฒ ๋ฌธ์ ์ ๋๋ค!
๋ค์์ ์ ํฌ ๊ณต์ฉ ์ปดํฌ๋ํธ์ ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ @next/bundle-analyzer ๋ก ๋ถ์ํ ๋ชจ์ต์ธ๋ฐ์. ์ ํฌ๋ ์คํ์ผ์ ๊ฐ ์ปดํฌ๋ํธ๋ง๋ค styles.ts๋ผ๋ ๋ณ๋ ํ์ผ์ ์์ฑํ๋๋ฐ, ๋๋ถ๋ถ์ style์ด ์ฐจ์งํ๊ณ ์๋ ๊ฑธ ๋ณผ ์ ์์ต๋๋ค.
์ ๊ทธ๋ด๊น์? ์ ํฌ ํ๋กฌ ๋์์ธ ์์คํ ์ ์ปดํฌ๋ํธ๋ค๋ ๋ค์ํ variants๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด Button์ด๋ผ ํ๋ฉด primary, secondary, tertiary, outline, quiet ๋ฑ์ด ์๋๋ฐ์. ์๊น์ ๋ฐ๋ผ keycolor, mono, error ๋ฑ์ theme๋ ์์ด์ ๊ทธ ๊ฐ์ง์๊ฐ ๋งค์ฐ ๋ง์ต๋๋ค. disabled ๊ฐ์ด ์ผ๋ฐ์ ์ธ ๊ฑด ๊ณ์ฐํด์ ํ์น๋ค๊ณ ํด๋, keycolor primary, keycolor secondaryโฆ ๋ฑ๋ฑ ์กฐํฉ์ ๋ฐ๋ผ 5 x 3 x n x m โฆ ์ผ๋ก ๊ฐ์ง์๊ฐ ๋์ด๋๋๋ฐ์. ์ด ๋ชจ๋ ์คํ์ผ์ ๋ฃ์ผ๋ styles.ts๋ง ๋น๋ํด์ง๊ฒ ๋ ๊ฒ์ด์ฃ .
๋ชจ๋ ํ์ด์ง์์ ์ด๋ ๊ฒ ๋ค์ํ variants๋ฅผ ๋ชจ๋ ์ฌ์ฉํ์ง๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด error variants๋ง ํด๋ ๋๋ถ๋ถ์ ํ์ด์ง์์ ์ฌ์ฉํ์ง ์์ฃ . ํ์ง๋ง styled component๋ ๊ทธ์ js์์ css๋ฅผ ๋ฌธ์์ด๋ก ๋ฃ์ด๋์ ๊ฒ์ด๋ผ. ์ฌ์ฉํ์ง ์๋ CSS๋ ๋ชจ๋ ๋ฒ๋ค์ ํฌํจ๋ฉ๋๋ค. ํนํ ๊ณต์ฉ ์ปดํฌ๋ํธ๋ผ๋ฉด ๋ชจ๋ ํ์ด์ง์ ํฌํจ๋์ฃ ! ๋ค์์ ์ด์ผ๊ธฐํ๊ฒ ์ง๋ง tailwind ๋ฑ์ ์๋ฃจ์ ๋ค์ ์ด๋ ๊ฒ ์ฌ์ฉํ์ง ์๋ style์ ์์์ css ์คํ์ผ ์ํธ์ ํฌํจ์ํค์ง ์๋๋ฐ, ์์ฌ์ด ์ ์ด ๋ง์์ต๋๋ค.
๋ฌผ๋ก gzip์ด ๋น์ทํ๊ฒ ์๊ธด ์ฝ๋๋ ์ค๋ณต์ด ๋ง๋๋ผ๋ ์ฉ๋์ ์ค์ฌ์ค์ ๊ฐ ์คํ์ผ์ 1~2kb ๋ฐ์ ๋์ง ์์ต๋๋ค. ํ์ง๋ง ์ด๋ฐ ๊ฒ ์ ์ญ ์ ๋ฐฑ ๊ฐ๊ฐ ๋๋ฉด ์ด์ ๋ฌด์ํ ์ ์๋ ์ ๋๊ฐ ๋ฉ๋๋ค. ์ด๊ธฐ์๋ ํฐํธ๋, ์ด๋ฏธ์ง, ์์ด์ฝ, ๋น๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฑ์ด ๋ฌธ์ ์์ฃ . ํ์ง๋ง ์ด๋ฐ ํฐ ์น๊ตฌ๋ค์ ์ต์ ํํ๋ฉด ์ต์ ํํ ์๋ก, ์ค์ด๋ค์ง ์๋ ์คํ์ผ์ ์๋ฆฌ๊ฐ ์ ์ ์ปค๋ณด์์ต๋๋ค.
๋ฐํ์ ์ฑ๋ฅ ์ด์
styled component๋ ํธ๋ฆฌํ๊ฒ? ๋ณ์์ ๋ฐ๋ผ ์กฐ๊ฑด๋ถ ์คํ์ผ๋ง์ ํด์ฃผ๋ ๊ธฐ๋ฅ์ด ์์ต๋๋ค. ์ ํฌ๋ ๊ธฐ์กด์ ์ด๋ฐ ๋ฐฉ์์ ์ฌ์ฉํ๋๋ฐ์. ์ฌ์ฉํ๋ค๋ณด๋ ์ฌ์ฉ์ ์ ๋ ฅ์ด ๋๋ ค์ง๊ฑฐ๋ ๋ฒ๋ฒ ์ด๋ ์ด์ ๋ฑ์ด ์์์ต๋๋ค. rerender๊ฐ ํนํ ์์ฃผ๋๋ TextField ๊ฐ์ ์ปดํฌ๋ํธ๊ฐ ๋ฌธ์ ์๋๋ฐ์.
react๋ signal์ด๋ ์ปดํ์ผ๋ฌ๋ฅผ ์ฐ๋ ๋ค๋ฅธ ํ๋ ์์ํฌ๋ค์ฒ๋ผ ์ํ๊ฐ ๋ณํ๋ฉด ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ์ง์ ๋ณ๊ฒฝํ๋ ๊ฒ ์๋๋ผ, ์ปดํฌ๋ํธ ํจ์๋ฅผ ๋ค์ ์คํํ๊ณ ์ปดํฌ๋ํธ์ ๊ฐ์๋์ ๋ชจ๋ ๋ค์ ๋ง๋ ๋ค์ ์ค์ ๋๊ณผ diffํฉ๋๋ค. ๊ทธ๋ฌ๋ค๋ณด๋ ๋งค๋ฒ styled component๋ ๋ค์ ์คํ๋๊ณ ์. ์คํ์ผ์ด ์กฐ๊ธ์ด๋ผ๋ ๋ฐ๋๋ฉด classname๋ ๋ค์ ๋ง๋ค์ด์ head์ ์ถ๊ฐํ๋ค๋ณด๋. ๋ธ๋ผ์ฐ์ ์๊ฒ๋ ๋งค๋ฒ ์๋ก์ด css๋ฅผ ํ์ฑํ๊ณ , ํ๊ฐํ๊ณ , ๋ ๋ํ๋๋ฐ ๋ณ๋ชฉ์ด ์๊ธฐ๋ ๊ฑด ์๋๊น ์ถ์์ต๋๋ค.
์ ํฌ๋ ์ด๋ฐ ๋ฌธ์ ๋ค์ ๋์ ์ธ ๋ถ๋ถ์ css variable์ ์ฌ์ฉํ๋ค๊ฑฐ๋, aria attribute ๋ฑ์ ์ด์ฉํด ์ ์ ์ผ๋ก ์คํ์ผ๋ง ํ๋ ๋ฐฉ์์ผ๋ก ์ฐํํด์๋๋ฐ์. ์ด์๊ฐ ์์ ๋๋ง๋ค styled component๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ฐ์ง ์๋ ์์ผ๋ก ํด๊ฒฐํ๋ ๊ฒ ๋ฐ๋ณต๋๊ณ ๊ทธ๊ฒ ๋ ๊ฐ๊ฒฐํ๊ณ ์ ๊ทผ์ฑ์ด๋ ์๋งจํฑ์ ์ํด์๋ ๋ ๋์ ๊ฒฝ์ฐ๊ฐ ๋ง์์ก์ต๋๋ค.
/* before */
${ArrowIcon} {
${({ isOpen }) => isOpen && 'transform:rotateZ(180deg);'}
}
/* after */
&[aria-expanded='true'] ${ArrowIcon} {
transform: rotateZ(180deg);
}
๋ฐ๋ฐ๋ฅ๋ถํฐ ๋์์ธ ์์คํ ์ ๋ง๋ค๊ธฐ๊ฐ ์ด๋ ต๋ค
styled component๋ ๋์์ธ ์์คํ ์ ์ํ ๊ธฐ๋ฅ์ ๋ฑํ ์ ๊ณตํ์ง ์์ต๋๋ค. ๋์์ธ ์์คํ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ณ , props์ type์ ์ง์ ํ๋ ๋ฑ์ ์ผ์ ์ง์ ํด์ผ ํฉ๋๋ค. ํนํ ๋ณต์กํ ์ปดํฌ๋ํธ์ type์ ๋ง๋๋ ๊ฑด ์ด๋ ค์ด ์ผ์ธ๋ฐ์. ๋ค์ํ ๊ฒฝ์ฐ์ ์๋ง๋ค ์กฐ๊ฑด๋ถ ์คํ์ผ์ ๋ฌ๊ธฐ ์ํด ์ฝ๊ธฐ๋ ์ด๋ ต๊ณ ๋ณต์กํ ์ฝ๋๊ฐ ๋์ด๋๊ฒ ๋์์ต๋๋ค.
ํนํ ์คํ์ผ๊ณผ ๊ธฐ๋ฅ์ด ์ปดํฌ๋ํธ์ ๊ฒฐํฉ๋๋ฉด์ ๊ฐ์ ๋ฒํผ ์คํ์ผ์ button์๋, link์๋, ๋๋ก๋ ๋ฒํผ์ฒ๋ผ ์๊ธด radio๋ option, tab ๋ฑ์ ์ ์ฉํ๋ ค ํ๋ ํ์ฅ์ฑ์ด ๋จ์ด์ง๊ณ , ๋งค๋ฒ ๊ณต์ฉ ์ปดํฌ๋ํธ๋ฅผ ๊ณ ์ณ์ค์ผ ํ์ต๋๋ค.
export type ButtonRole = 'button' | 'link' | 'checkbox' | 'radio' | 'option' | 'tab';
export type ButtonThemeT = 'key' | 'mono' | 'error';
export type ButtonVariantT = 'primary' | 'secondary' | 'tertiary' | 'outline' | 'quiet';
export type ButtonSizeT = 'h28' | 'h32' | 'h40' | 'h48' | 'h56';
export interface ButtonProps {
role?: ButtonRole;
theme?: ButtonThemeT;
variant?: ButtonVariantT;
size?: ButtonSizeT;
disabled?: boolean;
// ...
}
PandaCSS๋ฅผ ์ ํํ ์ด์
PandaCSS๋ ChakraUI๋ก ์ ๋ช ํ Sage(@thesegunadebayo)์จ์ ์ผ์ฌ์์ ๋๋ค. tailwind, stitches, styled-component, vanilla-extract, cva ๋ฑ ๋ค์ํ ์ํ๊ณ์ ๊ฒฝํ์ ์ถ์ ๋ ๋ ธํ์ฐ๋ฅผ ์ง๋์ฑํด์, ํ๋ถํ๊ณ ๋ค์ฑ๋ก์ด ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค. ์ด๋ฏธ ๋ค์ํ ํ์ฌ์ ๊ด์ฌ์ ๋ฐ๊ณ ์์ฃ .
ChakraUI๋ ๊ธฐ์กด์ ๋ฐํ์ css in js ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ emotion์ ์ฌ์ฉํ๋ฉด์ ์ฌ๋ฌ ์ด์๋ฅผ ๊ฒช์ด์์ต๋๋ค. ํนํ SSR, Next Server Component ๋ฑ์ ์ง์ํ๋ฉด์ ์ ๋ง ๋ค์ํ ์๋ฌ์ ์ด์๊ฐ ๋ง์์ต๋๋ค. ์ ๋ ChakraUI๋ฅผ ์ฌ์ฉํด๋ณธ ์ ์ด ์๋๋ฐ์. ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์คํ์ผ๊ณผ ์ถฉ๋์ด ๋๊ธฐ๋ ํ๊ณ ๊ณ ํต์ค๋ฌ์ด ์ด์๋ค์ ๊ฒช์์ต๋๋ค.
์์ธํ ์ด์ผ๊ธฐ๋ ํ๋ค๊ฐ ์ง์ ์๊ฐํ๊ณ ์์ผ๋ Why Panda?๋ฅผ ์ฝ์ด๋ณด์๊ธฐ ๋ฐ๋๋๋ค.
์๋ฒ ์ปดํฌ๋ํธ(Server Components)์ ์ถ์์ ์๋ฒ ์ฐ์ ํ๋ ์์ํฌ์ ๋ฑ์ฅ์ผ๋ก ๋๋ถ๋ถ์ ๊ธฐ์กด ๋ฐํ์ CSS-in-JS ์คํ์ผ๋ง ์๋ฃจ์ (์: Emotion, styled-component)์ ์์ ์ ์ผ๋ก ์๋ํ ์ ์๊ฑฐ๋ ๋ ์ด์ ์๋ํ ์ ์๊ฒ ๋์์ต๋๋ค. ์ด ์๋ก์ด ํจ๋ฌ๋ค์์ ์ฑ๋ฅ, ๊ฐ๋ฐ ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ์์ ํฐ ์น๋ฆฌ์ด์ง๋ง CSS-in-JS ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๊ฒ๋ ์๋ก์ด โํ์ ํ๊ฑฐ๋ ์๋๋ฉด ์ฃฝ๊ฑฐ๋โ์ ๊ธฐ๋ก์ ์๊ฒํ์ต๋๋ค.
๊ทธ๋ฌ๋ฉด ์ ํฌ ํ์์ ์ฌ์ฉํ ์ฌ๋ก๋ค์ ํตํด, PandaCSS์ ๊ธฐ๋ฅ๊ณผ ์ฅ์ ์ ์๊ฐํด๋ณด๊ฒ ์ต๋๋ค.
๋์์ธ ํ ํฐ
PandaCSS๋ ๋์์ด๋์ ๊ณต์ ํ๋ ๋์์ธ ํ ํฐ์ ์ฝ๊ฒ ์ ์ํ ์ ์์ต๋๋ค. ๊ธฐ์กด์ styled-component๋ฅผ ์ฌ์ฉํ ๋์๋ ๋ฐ๋ก ๋ณ์๋ฅผ ๋ง๋ค์ด์ ๋งค๋ฒ importํ๊ณ ${}
์ผ๋ก ๋ฃ์ด์ค์ผ ํ๋๋ฐ์.
// before
import styled from 'styled-components';
import { BreakPoint, ColorVar, Typography } from 'Styles';
export const SectionTitle = styled.h3`
${Typography.Display2_600}
${BreakPoint.Tablet} {
${Typography.W_Display3_600}
}
color: ${ColorVar.text.strong};
margin-bottom: "1.5rem",
`;
// after
// pandacss ๊ฐ ์์ฑํ ๋ฐํ์ js๋ฅผ importํฉ๋๋ค
import { styled } from '../../styled-system/jsx';
export const SectionTitle = styled.h3({
base: {
textStyle: { base: "Display2_600", tablet: "W_Display3_600" },
color: "text.strong",
mb: "1.5rem",
}
});
typescript๋ฅผ ์ด์ฉํด ์๋์์ฑ๋ ์ง์ํฉ๋๋ค. ํ์ ์ ํ๋ํ๋ ์ง์ ๋ฌ์์ค ํ์๋ ์์ด์!
์ปดํฌ๋ํธ ๋ ์ํผ
๋ค์ํ ์ปดํฌ๋ํธ์ variants๋ recipe์ slot recipe๋ฅผ ์ด์ฉํด ์ฝ๊ฒ ์ ์ํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ๋ง๋ recipe๋ component์ ๊ธฐ๋ฅ๊ณผ ๋ ๋ฆฝ์ ์ด๋ผ, ์ด๋ค ์ปดํฌ๋ํธ์๋ ์ฝ๊ฒ ๋ถ์ผ ์ ์์ต๋๋ค.
import { Link } from 'my-router-library';
<Link
className={button({ size: "h40" })}
href={page.url.next}
aria-label="๋ค์ ํ์ด์ง"
>
>
</Link>
๋ฌผ๋ก ์ด๋ฅผ styled component์ฒ๋ผ jsx component๋ก ๋ง๋ค์ด์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ์์์ prop์ผ๋ก variant๋ฅผ ์ง์ ํ ์ ์๊ฒ ๋ฉ๋๋ค. ๋ฌผ๋ก ํ์ ์คํฌ๋ฆฝํธ์ ์๋ํฐ ์ง์๋ ์ ๋๊ณ ์.
import { Link } from 'my-router-library';
import { styled } from '../../styled-system/jsx';
const LinkButton = styled(Link, button)
<LinkButton
size="h40"
href={page.url.next}
aria-label="๋ค์ ํ์ด์ง"
>
>
</LinkButton>
์ ์ ์ธ css๋ก ๋ค์ํ ํ๋ ์์ํฌ๋ฅผ ์์ฒด ์ง์
ํํธ styled component๋ emotion ๋ฑ์ vue๋ svelte, solid ์ ๊ฐ์ ๋ค๋ฅธ ํ๋ ์์ํฌ์์ ์ฌ์ฉํ๊ธฐ ์ด๋ ค์ ์ต๋๋ค. Sage์จ๋ chakra-ui๋ฅผ vue๋ก ํฌํ ํ๋ฉด์ ์ด ๋ฌธ์ ๋ฅผ ๊นจ๋ฌ์ผ์ ๊ฒ ๊ฐ์๋ฐ์.
์ ํฌ ํ ์ญ์ ์ต๊ทผ์ qwik์ ํ์ ๋์ ํ๋ฉด์ styled component๋ฅผ ์ฌ์ฉํ ์ ์์์ต๋๋ค. ์๋ก์ด ํ๋ ์์ํฌ๋ฅผ ๋์ ํ๋ ๊ฒ๋ ํฐ ํ์ต ๋น์ฉ์ด ํ์ํ๋ฐ, ๋ค์ํ ์ ๊ท ํ๋ก์ ํธ๋ง๋ค ์๋ก ๋ค๋ฅธ ์คํ์ผ๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ํ์ต ๋น์ฉ์ ๋ช ๋ฐฐ๋ก ์ปค์ง ๊ฒ์ ๋๋ค.
์ด ์ญ์ tailwind๋ vanilla extract ๋ ๋ฌผ๋ก ์ด๊ณ , emotion๋ framework agnosticํ ์ฝ์ด ๋ก์ง์ ์ ๊ณตํด์ ์ด๋ฏธ ํด๊ฒฐํ ๋ฌธ์ ์์ต๋๋ค.
Panda๋ tailwind์ฒ๋ผ ์ ์ ์ผ๋ก style sheeet๋ฅผ ์ปดํ์ผํ๋ ์ ๊ทผ๋ฒ์ ์ฐ๊ณ ์์ต๋๋ค. js ์ฝ๋๋ ๊ทธ์ className์ ์์ฑํด์ฃผ๋ ์ญํ ๋ง ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์์์ ๋ณด์ฌ๋๋ฆฐ LinkButton์ ๋ค์์ฒ๋ผ ๋ณํฉ๋๋ค.
<a class="button button--theme_keycolor button--variants_primary button--size_h40 button--shape_square" href="/2" aria-label="๋ค์ ํ์ด์ง">
>
</a>
<style>
@layer recipes {
.button--size_h40 {padding: 0 .75rem;height: 2.5rem;font-size: .9375rem;font-weight: 500;line-height: 1.25rem}
.button--theme_keycolor {--primary: var(--colors-keycolor-primary);--secondary: var(--colors-keycolor-secondary);--on_primary: var(--colors-keycolor-on_primary);--tertiary: var(--colors-keycolor-tertiary);--on_secondary_tertiary: var(--colors-keycolor-on_secondary_tertiary)}
.button--variants_primary {background-color: var(--primary);color: var(--on_primary)}
.button--shape_square {border-radius: .5rem}
@layer _base {
.button {width: 100%;box-sizing: border-box;display: flex;justify-content: center;align-items: center;flex-shrink: 0;--transition-duration: .125s;transition-duration: .125s;--transition-prop: filter outline;transition-property: filter outline;--transition-easing: ease-in-out;transition-timing-function: ease-in-out;position: relative}
.button:is(:disabled, [disabled], [data-disabled]) {opacity: .24}
.button _focus-visible {outline: 1px solid;outline-color: var(--colors-selected-on)}
.button:is(:hover, [data-hover]) {filter: brightness(.95)}
.button:is(:active, [data-active]) {filter: brightness(.9)}
}
}
</style>
DaisyUI ๋ฑ์ ์ฌ์ฉํด๋ณด์ ๋ถ์๊ฒ๋ ์ต์ํ ๋ชจ์์ด์ฃ . ์ด๋ฌํ ๋ณต์กํ style์ stylesheet์๋ง ํฌํจ๋๊ธฐ ๋๋ฌธ์, js ๋ฒ๋ค๋ ๋ ์ฐจ์งํ์ฃ . ๋์ ์ผ๋ก ์คํ์ผ์ ์์ฑํ๋ ๊ฒ ์๋๋ ๋ฐํ์ ์ฑ๋ฅ ์ด์๋ ์์ต๋๋ค. ๋ฌผ๋ก Next App dir์ ๋ฌผ๋ก ์ด๊ณ Qwik์ด๋ Astro์๋ ๋ฌธ์ ์์ด ํตํฉ๋ ์ ์์์ต๋๋ค.
๊ทธ์ pure css๋๊น์!
condition์ ์ด์ฉํด aria, dark, ๋ฐ์ํ๋ ์ฝ๊ฒ
์์ styled-component์์ aria๋ฅผ ์ด์ฉํด ์กฐ๊ฑด๋ถ ์คํ์ผ๋ง์ ํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ๋๋ ธ๋๋ฐ์. aria attribute๋ฅผ ์ด์ฉํ๋ฉด ํ ์คํธ๋ ์ฝ๊ณ , ์ฑ๋ฅ์์ผ๋ก๋ ์ข์ต๋๋ค. ํ์ง๋ง css์ ๊ธฐ๋ณธ ๋ฌธ๋ฒ์ ์ข ์ฅํฉํ๊ฒ ๋๊ปด์ง ์๋ ์๋๋ฐ์. ์๋ฅผ ๋ค์ด ํ์ฌ ์ฒดํฌ๋ ์์์ ์์ ๋ฐ๊พธ๊ณ ์ถ๋ค๋ฉด ๋ค์์ฒ๋ผ ํด์ผ ํ์ต๋๋ค.
const LangSelectButton = styled.a`
color: {ColorVar.text.six};
&[aria-checked='true'] {
color: {ColorVar.text.strong};
}
`
์ด๋ฅผ panda์์๋ ์ธ๋๋ฐ_
๋ก ์์ํ๋ _checked
condition์ ์จ์ ์ฝ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
css({
color: { base: 'text.six', _checked: 'text.strong' }
})
์ด๋ฌํ ์กฐ๊ฑด์ ๋ค์ํ aria์ ๋ํด preset์ด ์ ์๋์ด ์๊ณ ์. ๋คํฌ๋ชจ๋์ ๊ฐ์ condition์ ์ง์ ์ ์ํ ์๋ ์์ต๋๋ค. (ํ์ ์คํฌ๋ฆฝํธ ์๋์์ฑ๋ ์์์ ๋ฉ๋๋ค.)
์๋ฅผ ๋ค์ด ๋ค์์ฒ๋ผ _dark
condition์ panda.config.ts
์ ์ ์ํ๋ฉดโฆ
export const frommPreset = definePreset({
conditions: {
dark: '@media (prefers-color-scheme: dark)'
},
// ...
})
์๊น ๊ฐ์ ๋์์ธ ํ ํฐ๋ ๋คํฌ๋ชจ๋์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ค์ ํ ์ ์์ต๋๋ค. (๋ผ์ดํธ ๋ชจ๋์ผ ๋ ๊ธฐ๋ณธ๊ฐ์ base
๋ก ์ค์ ํฉ๋๋ค)
export const colors = defineSemanticTokens.colors({
background: {
value: { base: '#f9f9fc', _dark: '#121315' }
},
keycolor: {
primary: {
value: { base: '#fb4866', _dark: '#fa5571' }
},
...
},
...
});
๊ทธ๋ฌ๋ฉด ์๋์ผ๋ก ์กฐ๊ฑด์ ๋ฐ๋ผ ํ ํฐ๋ค์ด ์์ฑ๋ฉ๋๋ค.
:where(:root, :host) {
--colors-background: #f9f9fc;
--colors-keycolor-primary: #fb4866;
/* ... */
}
@media (prefers-color-scheme: dark) {
:where(:root, :host) {
--colors-background: #121315;
--colors-keycolor-primary: #fa5571;
/* ... */
}
}
PandaCSS๋ฅผ ์ฐ๋ฉด์ ์กฐ์ฌํด์ผํ ์ ์ฝ, ์ฃผ์ํ ์ ๋ค
ํ์ง๋ง Panda์๋ ์ ์ฝ๊ณผ ํ๊ณ, ์ฃผ์ํ ์ ๋ค์ด ์์ต๋๋ค. ๋๋ถ๋ถ ์ ์ ์ผ๋ก ์ปดํ์ผํ๋ ํน์ฑ์์ ์ค๋ ํ๊ณ์ ๋๋ค. tailwind์ ๊ฐ์ ์ ์ ์ธ ํน์ฑ์ ๊ฐ์ง ์คํ์ผ๋ง ์๋ฃจ์ ์ ์ฌ์ฉํด๋ณด์ จ๋ค๋ฉด ์ต์ํ ์ด์์ด์ค์ง๋ ๋ชจ๋ฅด๊ฒ ๋๋ฐ์.
static nature๋ก ์ธํ tracking ์ด์
panda๋ ๋ฐํ์์ด ์๋๋ผ ๋น๋ ํ์์ ์ ์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค. ๊ทธ๋์ ํ ํฐ์ด๋ ๋ ์ํผ๋ฅผ ์์ ํ๊ณ ๋๋ฉด panda codegen
๋ช
๋ น์ด๋ฅผ ์จ์, ๋ค์ ์์ฑ์ ํด์ค์ผ ํฉ๋๋ค. ์ด๊ฒ ๋ฒ๊ฑฐ๋กญ๊ณ ์์ฃผ ์ฝ๋๋ฅผ ์์ ํ์ ๋ค๋ฉด --watch
๋ฅผ ์จ์ ์ฝ๋๊ฐ ์์ ๋ ๋๋ง๋ค ์ฌ์์ฑํ๊ฒ ํ์๋ ๊ฒ๋ ๋ฐฉ๋ฒ์
๋๋ค.
tailwind์ฒ๋ผ pandacss๋ ์ฌ์ฉํ๋ variants์ classname๋ง ์ถ์ ํด์, ์ฌ์ฉํ์ง ์๋ ๊ฑด ๋ฒ๋ค๊ณผ ์คํ์ผ ์ํธ์ ํฌํจ์ํค์ง ์์ผ๋ ค ํฉ๋๋ค. webpack ๋ฑ์ ๋ฒ๋ค๋ฌ์์ tree shaking์ด ๊ฐ๋ฅํ๋๋ก ๋ชจ๋ ํจ์ ํธ์ถ์๋ /*#__PURE__*/
๊ฐ ๋ถ์ด์ ๋์ต๋๋ค.
๋๋ถ์ ๋ฒ๋ค ์ฌ์ด์ฆ๋ ์์์ง์ง๋งโฆ ๋ด๊ฐ ๋ง๋ ์ปดํฌ๋ํธ์ ์คํ์ผ์ด ์ ์ฉ๋์ง ์๋ ์ด์๊ฐ ์๊ธฐ๊ณ ๋ ํฉ๋๋ค. ๋ถ๋ช css class๋ ์ ๋ฌ๋ ค ์๋๋ฐ style์ ๋ณด์ด์ง ์๋ ๊ฒ์ด์ฃ !
๊ทธ๋์ pandacss ๋ฌธ์์์๋ ์ด๋ฌํ ์ ์ฝ์ ๋ฐ๋ณตํด์ ๊ฐ์กฐํฉ๋๋ค.
Due to static nature of Panda, itโs not possible to track the usage of the recipes in all cases.
์ค๋ฌด์์ ๊ฒช์ ์ฌ๋ก๋ฅผ ์๋ก ๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ์์์ ๋ง๋ค์๋ LinkButton
์ ๊ธฐ์ตํ์๋์? LinkButton์ ์ค์ ๋ก ์ฌ์ฉํด๋ณด๋ฉด ์คํ์ผ์ด ์ ์ฉ๋์ง ์์์ ๋นํฉํ๊ฒ ๋ฉ๋๋ค. ๋ค์์ ํ์์ mono button์ ์์์ธ๋ฐ, ์์ด๋ ํจ๋ฉ์ด ์ ํ ์ ์ฉ๋์ง ์์์ ํ
์คํธ์ ์์ด์ฝ๋ง ๋ณด์ด๊ณ ์์ต๋๋ค.
<LinkButton
href={Url[props.value].google}
target='_blank'
theme='mono'
variants='tertiary'
size='h48'
>
Google Play store
<ArrowForward />
</LinkButton>
์ด๋ฐ ๋นํฉ์ค๋ฌ์ด ๊ฒฝ์ฐ๋ฅผ ๋ง๋์๋ฉด ์ผ๋จ ํ๋ค ๋ฌธ์์์ tracking ๊ด๋ จํ ๋ถ๋ถ์ ๊ฒ์ํด๋ณด์๊ธฐ๋ฅผ ์ถ์ฒ๋๋ฆฝ๋๋ค. ์๋ฅผ ๋ค์ด ์์ ๋ฌธ์ ๋ LinkButton์ด๋ผ๋ jsx์ prop์ ์ถ์ ํ ์ ์๋๋ก, recipe config์ ์ถ๊ฐํด์ฃผ๋ฉด ๋ฉ๋๋ค.
import { defineRecipe } from '@pandacss/dev'
const buttonRecipe = defineRecipe({
className: 'button',
base: {
// ...
},
variants: {
// ...
},
// JSX ํํธ๋ฅผ ์ถ๊ฐํฉ๋๋ค. ์ ๊ทํํ์์ ์ธ ์๋ ์์ต๋๋ค.
jsx: ['Button', 'LinkButton']
})
๊ทธ๋ฌ๋ฉด ๋ค์์ฒ๋ผ ์์๊ฒ ๋ค์ ์คํ์ผ์ด ์ ์ฉ๋ฉ๋๋ค. ์ด๋ฐ ํํธ๊ฐ ์์ผ๋ฉด ์ปดํ์ผ๋ฌ๋ LinkButton์ด ๊ทธ๋ฅ ์ปดํฌ๋ํธ๊ฐ ์๋๋ผ, panda์ recipe๊ฐ ์ ์ฉ๋ ์ปดํฌ๋ํธ๋ผ๋ ๊ฑธ ์ ์ ์๊ณ , ์ฌ์ฉ๋ variant๋ฅผ bundle์ ํฌํจ์ํฌ ์ ์๊ฒ ๋ฉ๋๋ค.
๋ค์ํ ๋ฐฉ๋ฒ ์ค์ ํ์ค์ ์ ํ๊ธฐ
react๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋ ๋ค์ํด์ ์ ๋ ์๋ธ์จ์ด๋ผ๊ณ ๋ถ๋ฅด๊ณ ๋ ํ๋๋ฐ์. pandacss๋ ๋ค์ํ css in js ์๋ฃจ์ ๋ค์์ ์ฝ๊ฒ migration ํ ์ ์๋๋ก ๋ค์ํ ๊ธฐ๋ฅ์ ๋ชจ๋ ์ง์ํฉ๋๋ค. ๊ทธ๋์ ํ ๊ฐ์ง๋ฅผ ํ ์ ์๋ ๋ฐฉ๋ฒ์ด ๋๋ฌด ๋ง์ ๊ฒ ๋ฌธ์ ์ธ๋ฐ์. ์ ํฌ๋ ํ ๋ด๋ถ์์ ์ด๋ค ๋ฐฉ์์ด ์ข์์ง๋ฅผ ๋ ผ์ํ๊ณ ํฉ์ํ๋ ๊ณผ์ ์ ๊ณ์ํ๊ณ ์์ต๋๋ค.
ํ๋ค๊ฐ ์ ๊ณตํ๋ ๋ฐฉ๋ฒ๋ค์ ๋น๊ตํด๋ณด์๊ณ ํ๋๋ฅผ ๊ณจ๋ผ์ ํ์ ํ์ค์ผ๋ก ์ผ์ผ์๊ฑฐ๋, ์ด๋ค ๊ฒฝ์ฐ์ ์ด๋ค ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ค์ง ํ์ค์ ๋ช ํํ ์ ํ์๋ ๊ฒ ์ข์ต๋๋ค.
์๋ฅผ ๋ค์ด ๋ธ๋ก๊ทธ์ ์ ๋ชฉ์ ์คํ์ผ๋ง ํ๊ณ ์ถ๋ค๊ณ ํฉ์๋ค. ๊ทธ๋ฌ๋ฉด ๋ค์์ฒ๋ผ ์ธ๋ผ์ธ ์คํ์ผ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ฌธ์์์๋ ์ด ๋ฐฉ์์ ๊ธฐ๋ณธ์ผ๋ก ๋ณด์ฌ์ฃผ์ง๋ง, ์ธ๋ผ์ธ์ ๋ฑํ ๊ถ์ฅํ๋ ๊ฑด ์๋๋๋ค.
<h3
className={css({
textStyle: {
base: "Heading4_600",
tablet: "Heading2_600",
},
})}
>
{post.data.title}
</h3>
ํ์ง๋ง tailwind์ ๋ฌ๋ฆฌ ๋๋ฌด ์ฅํฉํ๊ณ , ๋งํฌ์ ๋ ๋ฑ๋ฑํด์ง๊ณ ๋ถํธํด๋ณด์ผ ์ ์์ต๋๋ค. ํนํ ์ค๋ณต๋๋ ๊ฒฝ์ฐ๋ ๋ง๋ค๋ฉด css ๋ณ์๋ฅผ ๋ฐ๊นฅ์ผ๋ก ๋นผ๋ ์๋ ์์ต๋๋ค.
const titleClass = css({
textStyle: {
base: "Heading4_600",
tablet: "Heading2_600",
},
})
<h3 className={titleClass}>
{post.data.title}
</h3>
ํํธ ์ธ๋ผ์ธ์ ์ทจํฅ์ด์ง๋ง ๋ ๊ฐ๊ฒฐํ๊ฒ ์ฐ๊ณ ์ถ๋ค๋ฉด panda๋ chakra๋ ์ฌ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ฒ๋ผ attribute๋ก style์ ์ ์ ์๋ ์์ต๋๋ค.
import { styled } from '../../styled-system/jsx';
<styled.h3
textStyle={{
base: "Heading4_600",
tablet: "Heading2_600",
}}
>
{post.data.title}
</styled.h3>
ํ์ง๋ง ์ฌ์ ํ inline style์ด ์ซ๊ณ , styled component ์ฒ๋ผ ์ฐ๊ณ ์ถ๋ค๋ฉด, styled component๋ก ๊ฐ์ธ์ ๊น๋ํ๊ฒ ๋ง๋ค ์๋ ์์ต๋๋ค.
import { styled } from '../../styled-system/jsx';
const Title = styled('h3', {
base: {
textStyle: {
base: "Heading4_600",
tablet: "Heading2_600",
}
}
})
<Title>
{post.data.title}
</Title>
tailwind๋ฅผ ์ค๋ ์จ์ค์ ํ์ด๋ผ๋ฉด utility class๊ฐ ์๋ ์ ๊ฒ์ด๋ผ๋ ๊ฑธ ๋ชจ๋ ๊ณต๊ฐํ์๋ฆฌ๋ผ ์๊ฐํฉ๋๋ค. ๋งํฌ์ ๊ณผ ์คํ์ผ์ ๊ฐ์ด ๋๋ ๊ฑด ํธ๋ฆฌํ๊ธฐ๋ ํ์ง๋ง, ์ํํ๊ธฐ๋ ํฉ๋๋ค. ์ปดํฌ๋ํธ์ ๊ฐ์ ์ถ์ํ๋ฅผ ์ ์ฌ์ฉํ์๊ณ tailwind ๋ฑ์ ์ ์ง๋ณด์๋ฅผ ์ํ ๊ถ์ฅ์ฌํญ๋ ์ดํด๋ณด์ธ์.
ํนํ ๋ฐ๋ณต๋๋ pattern์ด ์๊ณ ์ญํ ๋ ๋ช ํํ๋ค๋ฉด, ์ฐ๋ฆฌ ํ๋ง์ ํจํด์ ๋ง๋ค๊ณ ๋์์ด๋์๋ ์ฝ์ํด์ ์ฐ์๋ฉด ์ข์ต๋๋ค. ๋ค๋ง, ๋์์ด๋์ ์ฝ์๋ ๊ฒ ์๋ ์ถ์ํ๋ ํญ์ ๊นจ์ง๊ฒ ๋์ด ์๋ค๋ ๊ฑธ ๊ธฐ์ตํ์ธ์!
๋ชจ๋ CSS๋ก ๊ตฌํ ๋ธ๋ผ์ฐ์ ์ง์ํ๊ธฐ
panda๋ where์ด๋ layer์ ๊ฐ์ ๋ชจ๋ CSS๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋๋ถ์ css ์ฉ๋๋ ์ค์ด๋ค๊ณ ์ง๋ช ๋ ๊ฐ์ ์ด์๋ ์ค์ด๋๋ ๊ฑด ์ข์ ์ผ์ ๋๋ค๋ง. ๊ตฌํ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ง์ํด์ผ ํ๋ ํ์์๋ ๋์ ํ๊ธฐ ์ด๋ ต์ง ์์๊น ์ถ์ผ์ค์ง๋ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
์ค์ ๋ก panda์ ๋ธ๋ผ์ฐ์ ์ง์ ๋ฌธ์๋ฅผ ๋ณด๋ฉด, ํฌ๋กฌ 99, ์ฌํ๋ฆฌ15.4 ์ด์์ ์ง์ํ๋ค๊ณ ํ๊ณ ์์ต๋๋ค.
์ ํฌ ํ๋กฌ ์คํ ์ด๋ ์ฌํ๋ฆฌ 14์ ์ผ์ฑ ์ธํฐ๋ท์ ์ง์ํ๊ณ ์์๊ณ , ์ต๊ทผ์ ์ค๊ตญ์ด ์ง์์ ์์ํ๋ฉด์ ์ค๊ตญ์์ ๋ง์ด ์ฐ์ด๋ 360 ๋ฑ์ ๋ธ๋ผ์ฐ์ ๋ ์ง์ํ๊ฒ ๋์๋๋ฐ์. ์ค์ ๋ก ์ด๋ฐ ๊ตฌํ ๋ธ๋ผ์ฐ์ ์์ ํ์ด์ง๋ฅผ ์ด์ด๋ณด๋ ์ ํ style์ด ์ ์ฉ๋์ง ์์์ต๋๋ค.
๊ทธ๋ฌ๋ฉด ์ด๋ฐ ๊ฒฝ์ฐ์๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋ฉด ์ข์๊น์?
panda๋ css์ babel์ธ postcss๋ฅผ ์ด์ฉํ๊ธฐ ๋๋ฌธ์, ์ ์ ํ postcss ํ๋ฌ๊ทธ์ธ์ ์ด์ฉํ๋ฉด ๊ตฌํ ๋ธ๋ผ์ฐ์ ์์๋ ๋์๊ฐ๋ css๋ก polyfill์ ํ ์ ์์ต๋๋ค. ์ ํฌ๋ ๊ณต์ ๋ฌธ์์์ ์ถ์ฒํ๋ ํ๋ฌ๊ทธ์ธ ์ธ์๋ ๋ช ๊ฐ์ง๋ฅผ ๋ ์ถ๊ฐํด์ ์ฐ๊ณ ์์ต๋๋ค.
// postcss.config.js
module.exports = {
plugins: [
'@pandacss/dev/postcss',
'autoprefixer',
'@csstools/postcss-cascade-layers',
'@csstools/postcss-is-pseudo-class': {},
'@csstools/postcss-media-minmax': {}
]
}
์ด๋ ๊ฒ postcss ์ค์ ์ ํด์ฃผ๋ฉด ๋ค์์ฒ๋ผ ์คํ์ผ์ด ์ ์ฉ๋ ๋ชจ์ต์ ๋ณผ ์ ์์ต๋๋ค.
ํ์ง๋ง ์ฌ์ ํ ์์ด ์ ๋๋ก ๋ค์ด๊ฐ์ง ์๋๋ฐ์. ์ด๋ design token์ ์ ์ํ ๋ ์ฌ์ฉํ๋ :where
์ด postcss๋ก polyfill์ด ๋ถ๊ฐ๋ฅํ css์ด๊ธฐ ๋๋ฌธ์
๋๋ค. :where
์ design token์ css variable๋ก ์ ์ํ ๋ ์ฌ์ฉํ๋๋ฐ์. token์ ์ด์ฐจํผ ์์ฃผ ๋ฐ๋๋ ๊ฒ ์๋๋ผ์ ์ ํฌ๋ build ์ ์์ฑ๋ css variable์ :where
์ ์ฌ์ฉํ์ง ์๋ ๋ฐฉ์์ผ๋ก ์ง์ ์คํ์ผ์ํธ๋ฅผ ๋ง๋ค์ด์ html์ inlineํด์ฃผ๊ณ ์์ต๋๋ค.
/* before */
:where(:root, :host):not(#\#):not(#\#) {
--shadows-elevation_01: 0px 1px 2px 0px rgba(0, 0, 0, .1);
--shadows-elevation_02: 0px 2px 4px 0px rgba(0, 0, 0, .1);
/* ... */
}
/* after */
:root {
--shadows-elevation_01: 0px 1px 2px 0px #0000001a;
--shadows-elevation_02: 0px 2px 4px 0px #0000001a;
/* ... */
}
์ด์ ๊ฑฐ์? ์ ์์ ์ผ๋ก ํ์ด์ง๊ฐ ๋ณด์ด๋ ๊ฑธ ํ์ธํ ์ ์์ต๋๋ค.
ํ์ง๋ง ์ฌ์ ํ ํด๊ฒฐ๋์ง ์๋ ๋ฌธ์ ๋ ์์์ต๋๋ค. flex gap ์ polyfill์ ์ด์ํ ์๋ฌ๊ฐ ๋ง์์, ๊ฒฐ๊ตญ margin์ ์ง์ ์ฃผ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํด์ผ ํ์ต๋๋ค. postcss plugin์ ์ด์ฉํด ์ css๋ก ๋ณํ๋๋ฉด์ ์คํ์ผ ์ํธ์ ์ฉ๋์ด ์ปค์ง๊ธฐ๋ ํฉ๋๋ค.
์๋ก์ด ๊ธฐ์ ์๋ ํ๊ณ๊ฐ ํญ์ ์๊ธฐ ๋ง๋ จ์ ๋๋ค. ํ์ง๋ง ๋ฌธ์๋ฅผ ์ ์ฝ๊ณ ํด๊ฒฐ์ฑ ์ ํ์ํด์ ๋ชจ๋ํ ๊ฐ๋ฐ์ ๊ฒฝํ์ ๋ณด์กดํ๋ฉด์๋, ๊ตฌํ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ง์ํ๋ ์๊ตฌ์ฌํญ๋ ๋ง์กฑ์ํฌ ์ ์์์ต๋๋ค. ์์ผ๋ก๋ ์ด ํ๊ณ๋ฅผ ๋์ด ๊ฐ์ ํ ์ ์๋ ๋ ์ข์ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๋ ค ํฉ๋๋ค.
PandaCSS์ CSS-in-JS์ ๋ฏธ๋
PandaCSS๋ 2023๋ 11์ 30์ผ ํ์ฌ 0.19.0 ๋ฒ์ ์ด์ง๋ง, ์ค๋ 12์์ 1.0์ ๋ฐํํ ์์ ์ ๋๋ค. ์ถ์๋์ง๋ ์ผ๋ง๋์ง ์์์ง๋ง, ๊ธฐ์กด ์ํ๊ณ์ ์ํ์ฐฉ์ค์ ๊ฒฝํ์์ ๋ฐฐ์ด ๊ตํ์ ํ ๋๋ก ๋ง๋ค์ด์ก๊ธฐ ๋๋ฌธ์ ๋ค์ํ ํ๊ฒฝ๊ณผ ํ๋ ์์ํฌ์์ ๋ฌธ์ ์์ด ๋์๊ฐ๋๋ค.
์์ ์ด์ผ๊ธฐ ๋๋ฆฐ ์ ์ฝ์ด๋ ์ฃผ์ํ ์ ๋ ์ ๋ฌธ์ํ๊ฐ ๋์ด ์๊ณ ์ฌ์ง์ด ๊ณต์ ์์ ๊ฐ์๋ ์์ต๋๋ค. ์ด๋ฅผ ์ ์ดํดํ๊ณ ์ ์คํ๊ฒ ๋์ ํ์ ๋ค๋ฉด, ์ํ์ฐฉ์ค๋ ์ค์ด๊ณ ์ด๋์ ์ปค์ง์ง ์์๊น ์ถ์ต๋๋ค. (์ ํฌ ๋ธ๋ก๊ทธ๋ Astro์ PandaCSS๋ก ๋ง๋ค์์ต๋๋ค.)
์ ํฌ๊ฐ ๊ฒช์ ์ํ์ฐฉ์ค๊ฐ PandaCSS ๋์ ์ ๊ณ ๋ฏผํ๋ ๋ถ๋ค์๊ฒ ๋์์ด ๋๋ฉด ์ข๊ฒ ์ต๋๋ค. ํนํ ํ์ฌ์์ ๋์์ธ ํ ํฐ๊ณผ ๋์์ธ ์์คํ , ํน์ ๊ณต์ฉ ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด๋ฏธ ์ ๋ฆฝํด์ ์ฌ์ฉํ๊ณ ์๊ฑฐ๋, ๋ง๋ค์ด๋๊ฐ๋ ์ค์ด์๋ผ๋ฉด ๋์ฑ ๊ทธ๋ ์ต๋๋ค.
Sage ์จ๋ Framework agnosticํ headless component ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Ark UI์ Zag.js๋ ๋ง๋ค๊ณ ์๋๋ฐ์. ์ด๋ค์ ์กฐํฉํด์ ์ฌ์ฉํ์ ๋ค๋ฉด React๋ฅผ ์ฐ์๋ ๋ถ๋ ๋ค๋ฅธ ํ๋ ์์ํฌ๋ฅผ ์ฐ๋ ํ๋ ๋ชจ๋ ์ฌ์ฉ์๊ฐ ์์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ์ฌ์ดํธ๋ฅผ ๋น ๋ฅด๊ฒ ๋ง๋ค์ด๋ด์ค ์ ์์ ๊ฒ๋๋ค.
๋ ์ฐ๊ณ ์ถ์ ๊ธ์ ๋ง์๋ฐ, ์๊ฐ์ ๋ถ์กฑํ๋ค์. ๋ ๋ค์ํ ์ฃผ์ ๋ก ๋ค์ ์ฐพ์๋ต๊ฒ ์ต๋๋ค.