CSS ๋จ์ ํ ์คํธ ๊ตฌํ์ ๋ํ ํฌ๊ด์ ์ธ ๊ฐ์ด๋๋ก ๊ฒฌ๊ณ ํ ํ๋ฐํธ์๋ ํ์ง์ ํ๋ณดํ์ธ์. ๊ธ๋ก๋ฒ ์น ๊ฐ๋ฐ ํ์ ์ํ ์ค์ง์ ์ธ ์ ๋ต, ๋๊ตฌ ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์์๋ณด์ธ์.
CSS ํ ์คํธ ๊ท์น ๋ง์คํฐํ๊ธฐ: ๋จ์ ํ ์คํธ ๊ตฌํ์ ์ํ ๊ธ๋ก๋ฒ ๊ฐ์ด๋
์ฌ์ฉ์ ๊ฒฝํ์ด ๊ฐ์ฅ ์ค์ํ๊ณ ์ฒซ์ธ์์ด ์ข ์ข ์๊ฐ์ ์ธ ์น ๊ฐ๋ฐ์ ์ญ๋์ ์ธ ์ธ๊ณ์์ ๊ณ๋จ์ ์คํ์ผ ์ํธ(CSS)์ ํ์ง์ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์๋ ๋์ CSS ํ ์คํธ๋ ์ฃผ๋ก ์๋ ์๊ฐ์ ํ์ธ์ด๋ ๋ ๊ด๋ฒ์ํ ์๋ํฌ์๋ ํ๊ท ํ ์คํธ์ ๊ตญํ๋์์ต๋๋ค. JavaScript ํจ์๋ ๋ฐฑ์๋ ๋ก์ง์ ํ ์คํธํ๋ ๊ฒ๊ณผ ์ ์ฌํ CSS "๋จ์ ํ ์คํธ"๋ผ๋ ๊ฐ๋ ์ ํ์ ํ๊ธฐ ์ด๋ ค์ด ๊ฒ์ฒ๋ผ ๋ณด์์ต๋๋ค. ๊ทธ๋ฌ๋ ํ๋ฐํธ์๋ ๋ณต์ก์ฑ์ด ์ฆ๊ฐํ๊ณ ๋์์ธ ์์คํ ์ด ๊ธ๋ก๋ฒ ์ ํ ์ผ๊ด์ฑ์ ํ์ ์์๊ฐ ๋จ์ ๋ฐ๋ผ ์คํ์ผ์ ๊ฒ์ฆํ๊ธฐ ์ํ ๋ณด๋ค ์ธ๋ถํ๋ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ ์ ๊ทผ ๋ฐฉ์์ ๋จ์ํ ์ ์ตํ ๊ฒ์ด ์๋๋ผ ํ์์ ์ ๋๋ค. ์ด ํฌ๊ด์ ์ธ ๊ฐ์ด๋์์๋ "CSS ํ ์คํธ ๊ท์น"์ด๋ผ๋ ๊ฐ๋ ฅํ ํจ๋ฌ๋ค์์ ์๊ฐํ๊ณ , ์ด๋ฅผ ๋จ์ ํ ์คํธ๋ฅผ ํตํด ๊ตฌํํ์ฌ ํ๋ ฅ์ ์ด๊ณ ์ ๊ทผ ๊ฐ๋ฅํ๋ฉฐ ์ ์ธ๊ณ์ ์ผ๋ก ์ผ๊ด๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ํ๊ตฌํฉ๋๋ค.
๋๋ฅ์ ๊ฑธ์ณ ๊ฐ๋ฐ ํ์ด ์์ผ๋ฉฐ ๋ค์ํ ์ฌ์ฉ์ ๊ธฐ๋ฐ์ ์๋น์ค๋ฅผ ์ ๊ณตํ ๋, ๋์ฟ, ๋ฒ ๋ฅผ๋ฆฐ ๋๋ ๋ด์ ์ ์ด๋์์๋ ๋ฒํผ์ด ๋์ผํ๊ฒ ๋ณด์ด๊ณ ์๋ํ๋๋ก ๋ณด์ฅํ๋ ๊ฒ์ ์ค์ํ ๊ณผ์ ์ ๋๋ค. ์ด ๊ธฐ์ฌ์์๋ CSS์ ๋ํ ๋จ์ ํ ์คํธ ๋ฐฉ๋ฒ๋ก ์ ์ฑํํ๋ ๊ฒ์ด ์ ์ธ๊ณ ๊ฐ๋ฐ์๋ค์ด ์คํ์ผ๋ง์์ ๋น๊ตํ ์ ์๋ ์ ํ์ฑ๊ณผ ์์ ๊ฐ์ ์ป์ ์ ์๋๋ก ์ด๋ป๊ฒ ํ์ ์ค์ด์ฃผ๊ณ ์น ์ ํ์ ์ ๋ฐ์ ์ธ ํ์ง์ ํฌ๊ฒ ํฅ์์ํค๋์ง ์ดํด๋ด ๋๋ค.
CSS ํ ์คํธ์ ๊ณ ์ ํ ๊ณผ์
๊ตฌํ์ ์์ CSS๊ฐ ํนํ ๋จ์ ์์ค์์ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ํ ์คํธํ๊ธฐ ์ด๋ ค์ด ์์ญ์ด์๋ ์ด์ ๋ฅผ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๋ช ํํ ์ ๋ ฅ-์ถ๋ ฅ ํจ์๋ฅผ ์ ๊ณตํ๋ JavaScript์ ๋ฌ๋ฆฌ CSS๋ ์บ์ค์ผ์ด๋ฉ๋๊ณ ์ ์ญ์ ์ธ ๋ฒ์ ๋ด์์ ์๋ํ๋ฏ๋ก ๊ฒฉ๋ฆฌ๋ ํ ์คํธ๊ฐ ๋ณต์กํฉ๋๋ค.
์๊ฐ์ ํ๊ท ํ ์คํธ์ ๋จ์ ํ ์คํธ: ์ค์ํ ๊ตฌ๋ถ
๋ง์ ๊ฐ๋ฐ์๋ค์ ์น ํ์ด์ง ๋๋ ์ปดํฌ๋ํธ์ ์คํฌ๋ฆฐ์ท์ ์บก์ฒํ๊ณ ์๋ํ์ง ์์ ์๊ฐ์ ๋ณ๊ฒฝ์ ๊ฐ์งํ๊ธฐ ์ํด ๊ธฐ์ค ์ด๋ฏธ์ง์ ๋น๊ตํ๋ ๋ฐฉ๋ฒ์ธ ์๊ฐ์ ํ๊ท ํ ์คํธ์ ์ต์ํฉ๋๋ค. Storybook์ `test-runner`, Chromatic ๋๋ Percy์ ๊ฐ์ ๋๊ตฌ๊ฐ ์ด ์์ญ์์ ๋ฐ์ด๋ฉ๋๋ค. ์๊ฐ์ ํ๊ท ํ ์คํธ๋ ์๋ชป๋ ๋ ์ด์์ ๋ณ๊ฒฝ์ด๋ ์๊ธฐ์น ์์ ๋ ๋๋ง์ ํฌ์ฐฉํ๋ ๋ฐ ๋งค์ฐ ์ ์ฉํ์ง๋ง ์ถ์ํ ์์ค์ด ๋ ๋์ต๋๋ค. ์๊ฐ์ ์ผ๋ก ๋ฌด์์ด ๋ณ๊ฒฝ๋์๋์ง ์๋ ค์ฃผ์ง๋ง, ํน์ CSS ์์ฑ์ด ์ ์คํจํ๋์ง, ๋๋ ๊ฐ๋ณ ๊ท์น์ด ๊ฒฉ๋ฆฌ๋ ์ํ์์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฉ๋์๋์ง๋ ๋ฐ๋์ ์๋ ค์ฃผ์ง ์์ต๋๋ค.
- ์๊ฐ์ ํ๊ท: ์ ์ฒด์ ์ธ ๋ชจ์์ ์ค์ ์ ๋ก๋๋ค. ๊ด๋ฒ์ํ ๋ ์ด์์ ๋ฌธ์ , ์๋ํ์ง ์์ ์ ์ญ ์คํ์ผ ๋ณ๊ฒฝ ๋๋ ํตํฉ ๋ฌธ์ ๋ฅผ ํฌ์ฐฉํ๋ ๋ฐ ์ข์ต๋๋ค. ์ต์ข ๊ทธ๋ฆผ์ ํ์ธํ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
- CSS ๋จ์ ํ ์คํธ: ๊ฐ๋ณ CSS ์ ์ธ, ๊ท์น ๋๋ ์ปดํฌ๋ํธ ์คํ์ผ์ ๊ฒฉ๋ฆฌํ์ฌ ์ค์ ์ ๋ก๋๋ค. ํน์ ์์ฑ(์: `background-color`, `font-size`, `display: flex`)์ด ์ ์๋ ์กฐ๊ฑด์์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฉ๋๋์ง ํ์ธํฉ๋๋ค. ์์ฑ๋๊ธฐ ์ ์ ๊ฐ ๋ถ๋๋ฆผ์ด ์๋ํ ๋๋ก ์๋์ง ํ์ธํ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
๊ธ๋ก๋ฒ ๊ฐ๋ฐ ํ์ด ์๊ฐ์ ํ๊ท์๋ง ์์กดํ๋ ๊ฒ์ ๋ถ์ถฉ๋ถํ ์ ์์ต๋๋ค. ํ ์ง์ญ์ ๋ ์ผ๋ฐ์ ์ธ ๋ธ๋ผ์ฐ์ ์์ ๊ธ๊ผด ๋ ๋๋ง์ ๋ฏธ๋ฌํ ์ฐจ์ด๊ฐ ๋์น๊ฑฐ๋ ํน์ `flex-wrap` ๋์์ด ๋งค์ฐ ํน์ ์ฝํ ์ธ ๊ธธ์ด์์๋ง ๋ํ๋ ์ ์์ผ๋ฉฐ, ์๊ฐ์ ํ ์คํธ๊ฐ ๋ชจ๋ ์กฐํฉ์์ ์ด๋ฅผ ํฌ์ฐฉํ์ง ๋ชปํ ์ ์์ต๋๋ค. ๋จ์ ํ ์คํธ๋ ๊ฐ ๊ธฐ๋ณธ ์คํ์ผ ๊ท์น์ด ์ฌ์์ ์ค์ํ๋ค๋ ์ธ๋ถํ๋ ๋ณด์ฆ์ ์ ๊ณตํฉ๋๋ค.
์น์ ์ ๋์ ํน์ฑ๊ณผ ์บ์ค์ผ์ด๋ ๋ณต์ก์ฑ
CSS๋ ์ ๋์ ์ด๊ณ ๋ฐ์ํ๋๋ก ์ค๊ณ๋์์ต๋๋ค. ์คํ์ผ์ ๋ทฐํฌํธ ํฌ๊ธฐ, ์ฌ์ฉ์ ์ํธ ์์ฉ(ํธ๋ฒ, ํฌ์ปค์ค, ํ์ฑ ์ํ) ๋ฐ ๋์ ์ฝํ ์ธ ์ ๋ฐ๋ผ ๋ณ๊ฒฝ๋ฉ๋๋ค. ๋ํ CSS์ ์บ์ค์ผ์ด๋, ํน์ด์ฑ ๋ฐ ์์ ๊ท์น์ ํ ๊ณณ์์ ์ ์ธ๋ ์คํ์ผ์ด ๋ค๋ฅธ ๋ง์ ์คํ์ผ๋ก ์ธํด ์ฌ์ ์๋๊ฑฐ๋ ์ํฅ์ ๋ฐ์ ์ ์์์ ์๋ฏธํฉ๋๋ค. ์ด๋ฌํ ๋ด์ฌ์ ์ํธ ์ฐ๊ฒฐ์ฑ์ ํ ์คํธ๋ฅผ ์ํด CSS์ ๋จ์ผ "๋จ์"๋ฅผ ๊ฒฉ๋ฆฌํ๋ ๊ฒ์ ๋ฏธ๋ฌํ ์์ ์ผ๋ก ๋ง๋ญ๋๋ค.
- ์บ์ค์ผ์ด๋ ๋ฐ ํน์ด์ฑ: ์์์ `font-size`๋ ์ ์ญ ์คํ์ผ, ์ปดํฌ๋ํธ ์คํ์ผ ๋ฐ ์ธ๋ผ์ธ ์คํ์ผ์ ์ํด ์ํฅ์ ๋ฐ์ ์ ์์ต๋๋ค. ์ด๋ค ๊ท์น์ด ์ฐ์ ํ๋์ง ์ดํดํ๊ณ ํด๋น ๋์์ ํ ์คํธํ๋ ๊ฒ์ ์ด๋ ต์ต๋๋ค.
- ๋์ ์ํ: `::hover`, `:focus`, `:active` ๋๋ JavaScript ํด๋์ค(์: `.is-active`)์ ์ํด ์ ์ด๋๋ ์คํ์ผ์ ํ ์คํธํ๋ ค๋ฉด ํ ์คํธ ํ๊ฒฝ์์ ์ด๋ฌํ ์ํธ ์์ฉ์ ์๋ฎฌ๋ ์ด์ ํด์ผ ํฉ๋๋ค.
- ๋ฐ์ํ ๋์์ธ: `min-width` ๋๋ `max-width` ๋ฏธ๋์ด ์ฟผ๋ฆฌ์ ๋ฐ๋ผ ๋ณ๊ฒฝ๋๋ ์คํ์ผ์ ๋ค์ํ ์๋ฎฌ๋ ์ด์ ๋ ๋ทฐํฌํธ ์น์์์ ํ ์คํธํด์ผ ํฉ๋๋ค.
ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ๋ฐ ์ฅ์น ํธํ์ฑ
๊ธ๋ก๋ฒ ์น์ ๋๋ผ์ด ๋ฒ์์ ๋ธ๋ผ์ฐ์ , ์ด์ ์ฒด์ ๋ฐ ์ฅ์น ์ ํ์ ํตํด ์ก์ธ์ค๋ฉ๋๋ค. ๋จ์ ํ ์คํธ๋ ์ฃผ๋ก CSS ๊ท์น์ ๋ ผ๋ฆฌ์ ์ ์ฉ์ ์ค์ ์ ๋์ง๋ง ํธํ์ฑ์ ๊ฐ์ ์ ์ผ๋ก ๊ธฐ์ฌํ ์ ์์ต๋๋ค. ์์๋๋ ์คํ์ผ ๊ฐ์ ์ฃผ์ฅํจ์ผ๋ก์จ ํธ์ฐจ๋ฅผ ์กฐ๊ธฐ์ ํฌ์ฐฉํ ์ ์์ต๋๋ค. ์ง์ ํ ํฌ๊ด์ ์ธ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํด ๋ธ๋ผ์ฐ์ ์๋ฎฌ๋ ์ด์ ๋๊ตฌ ๋ฐ ์ ์ฉ ๋ธ๋ผ์ฐ์ ํ ์คํธ ์๋น์ค์์ ํตํฉ์ ์ฌ์ ํ ์ค์ํ์ง๋ง, ๋จ์ ํ ์คํธ๋ ์ฒซ ๋ฒ์งธ ๋ฐฉ์ด์ ์ ๋๋ค.
"CSS ํ ์คํธ ๊ท์น" ๊ฐ๋ ์ดํดํ๊ธฐ
"CSS ํ ์คํธ ๊ท์น"์ ํน์ ๋๊ตฌ๋ ๋จ์ผ ํ๋ ์์ํฌ๊ฐ ์๋๋ผ ๊ฐ๋ ์ ํ๋ ์์ํฌ์ด์ ๋ฐฉ๋ฒ๋ก ์ ๋๋ค. ์ด๋ ๊ฐ๋ณ CSS ์ ์ธ, ์์ ์คํ์ผ ๋ธ๋ก ๋๋ ๋จ์ผ ์ปดํฌ๋ํธ์ ์ ์ฉ๋๋ ์คํ์ผ์ ๋ณ๋์ ํ ์คํธ ๊ฐ๋ฅํ ๋จ์๋ก ์ทจ๊ธํ๋ ์์ด๋์ด๋ฅผ ๋ํ๋ ๋๋ค. ๋ชฉํ๋ ์ด๋ฌํ ๋จ์๊ฐ ๊ฒฉ๋ฆฌ๋ ์ปจํ ์คํธ์์ ์ ์ฉ๋ ๋ ๋์์ธ ์ฌ์์ ๋ฐ๋ผ ์์๋๋ก ์ ํํ๊ฒ ์๋ํ๋์ง ์ฃผ์ฅํ๋ ๊ฒ์ ๋๋ค.
"CSS ํ ์คํธ ๊ท์น"์ด๋ ๋ฌด์์ธ๊ฐ?
ํต์ฌ์ ์ผ๋ก "CSS ํ ์คํธ ๊ท์น"์ ํน์ ์กฐ๊ฑด ํ์์ ์์์ ์ ์ฉ๋๋ ํน์ ์คํ์ผ ์์ฑ ๋๋ ์์ฑ ์งํฉ์ ๋ํ ์ฃผ์ฅ์ ๋๋ค. ๋ ๋๋ง๋ ํ์ด์ง๋ง ๋ณด๋ ๋์ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ์ง๋ฌธ์ ํฉ๋๋ค.
- "์ด ๋ฒํผ์ ๊ธฐ๋ณธ ์ํ์ผ ๋ `background-color`๊ฐ `#007bff`์ธ๊ฐ?"
- "์ด ์ ๋ ฅ ํ๋์๋ `.is-invalid` ํด๋์ค๊ฐ ์์ ๋ `border-color`๊ฐ `#dc3545`์ธ๊ฐ?"
- "๋ทฐํฌํธ๊ฐ 768px ๋ฏธ๋ง์ผ ๋ ์ด ๋ค๋น๊ฒ์ด์ ๋ฉ๋ด์ `display` ์์ฑ์ `flex`๋ก, `flex-direction`์ `column`์ผ๋ก ๋ณ๊ฒฝ๋๋๊ฐ?"
- "์ด `heading` ์์๋ ๋ชจ๋ ๋ฐ์ํ ์ค๋จ์ ์์ `line-height`๋ฅผ 1.2๋ก ์ ์งํ๋๊ฐ?"
์ด๋ฌํ ๊ฐ ์ง๋ฌธ์ "CSS ํ ์คํธ ๊ท์น"์ ๋ํ๋ ๋๋ค. ์ฆ, ์คํ์ผ๋ง์ ํน์ ์ธก๋ฉด์ ๋ํ ์ง์ค์ ์ธ ํ์ธ์ ๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ข ์ข ์์ธก ๋ถ๊ฐ๋ฅํ CSS ์์ญ์ ์ ํต์ ์ธ ๋จ์ ํ ์คํธ์ ์๊ฒฉํจ์ ๊ฐ์ ธ์ต๋๋ค.
CSS ๋จ์ ํ ์คํธ ์ฒ ํ
CSS ๋จ์ ํ ์คํธ์ ์ฒ ํ์ ๊ฒฌ๊ณ ํ ์ํํธ์จ์ด ๊ณตํ์ ์์น๊ณผ ์๋ฒฝํ๊ฒ ์ผ์นํฉ๋๋ค.
- ์กฐ๊ธฐ ๋ฒ๊ทธ ๊ฐ์ง: ์ฝ๋ฉํ๋ ์ฆ์ ์คํ์ผ๋ง ์ค๋ฅ๋ฅผ ๋ฐ๊ฒฌํ์ธ์. ๋ช ์๊ฐ ๋๋ ๋ฉฐ์น ํ์ ์๊ฐ์ ๊ฒํ ์ค์ด๋, ๋ ๋์๊ฒ๋ ํ๋ก๋์ ๋ฐฐํฌ ํ์ ๋ฐ๊ฒฌํ๋ ๊ฒ์ด ์๋๋๋ค. ์ด๋ ํนํ ์๊ฐ๋ ์ฐจ์ด๋ก ์ธํด ํผ๋๋ฐฑ ์ฃผ๊ธฐ๊ฐ ์ง์ฐ๋ ์ ์๋ ์ ์ธ๊ณ์ ์ผ๋ก ๋ถ์ฐ๋ ํ์๊ฒ ๋งค์ฐ ์ค์ํฉ๋๋ค.
- ๊ฐ์ ๋ ์ ์ง ๊ด๋ฆฌ์ฑ ๋ฐ ๋ฆฌํฉํ ๋ง ์์ ๊ฐ: ํฌ๊ด์ ์ธ CSS ๋จ์ ํ ์คํธ ์ ํ๊ตฐ์ ํตํด ๊ฐ๋ฐ์๋ ์๋ํ์ง ์์ ํ๊ท๊ฐ ์ฆ์ ํฌ์ฐฉ๋๋ค๋ ๊ฒ์ ์๋ฉด์ ํจ์ฌ ๋ ํฐ ์์ ๊ฐ์ผ๋ก ์คํ์ผ์ ๋ฆฌํฉํฐ๋งํ๊ณ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ๊ทธ๋ ์ด๋ํ๊ฑฐ๋ ๋์์ธ ํ ํฐ์ ์กฐ์ ํ ์ ์์ต๋๋ค.
- ๋ช ํํ ๊ธฐ๋์น ๋ฐ ๋ฌธ์: ํ ์คํธ๋ ๋ค์ํ ์กฐ๊ฑด์์ ์ปดํฌ๋ํธ๊ฐ ์ด๋ป๊ฒ ์คํ์ผ๋ง๋์ด์ผ ํ๋์ง์ ๋ํ ์ด์์๋ ๋ฌธ์ ์ญํ ์ ํฉ๋๋ค. ๊ตญ์ ํ์ ๊ฒฝ์ฐ ์ด ๋ช ์์ ๋ฌธ์๋ ๋ชจํธ์ฑ์ ์ค์ด๊ณ ๋์์ธ ์ฌ์์ ๋ํ ๊ณต์ ๋ ์ดํด๋ฅผ ๋ณด์ฅํฉ๋๋ค.
- ํฅ์๋ ํ์ : ๋์์ด๋, ๊ฐ๋ฐ์ ๋ฐ ํ์ง ๋ณด์ฆ ์ ๋ฌธ๊ฐ๋ ํ ์คํธ๋ฅผ ์ฐธ์กฐํ์ฌ ์์๋๋ ๋์์ ์ดํดํ ์ ์์ต๋๋ค. ์ด๋ ๋์์ธ ๊ตฌํ ์ธ๋ถ ์ ๋ณด์ ๋ํ ๊ณตํต ์ธ์ด๋ฅผ ์ก์ฑํฉ๋๋ค.
- ์ ๊ทผ์ฑ์ ์ํ ๊ธฐ์ด: ์๋ ์ ๊ทผ์ฑ ํ ์คํธ๋ฅผ ๋์ฒดํ ์๋ ์์ง๋ง, CSS ๋จ์ ํ ์คํธ๋ ์ถฉ๋ถํ ์์ ๋๋น ๊ฐ, ๋ณด์ด๋ ํฌ์ปค์ค ํ์๊ธฐ ๋๋ ๋ค์ํ ๋์คํ๋ ์ด ๋ชจ๋์ ๋ํ ์ฌ๋ฐ๋ฅธ ํ ์คํธ ํฌ๊ธฐ ์กฐ์ ๊ณผ ๊ฐ์ ์ค์ํ ์ ๊ทผ์ฑ ๊ด๋ จ ์คํ์ผ ์์ฑ์ ๊ฐ์ ํ ์ ์์ต๋๋ค.
CSS ํ ์คํธ ๊ท์น ๋ฐฉ๋ฒ๋ก ์ ์ฑํํจ์ผ๋ก์จ ์กฐ์ง์ ์ฃผ๊ด์ ์ธ ์๊ฐ์ ํ์ธ์ ๋์ด ๊ฐ๊ด์ ์ด๊ณ ์๋ํ๋ ๊ฒ์ฆ์ผ๋ก ๋์๊ฐ ๋ ์์ ์ ์ด๊ณ ๊ณ ํ์ง์ด๋ฉฐ ์ ์ธ๊ณ์ ์ผ๋ก ์ผ๊ด๋ ์น ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
CSS ๋จ์ ํ ์คํธ ํ๊ฒฝ ์ค์
CSS ๋จ์ ํ ์คํธ๋ฅผ ๊ตฌํํ๋ ค๋ฉด ์ฌ๋ฐ๋ฅธ ๋๊ตฌ ์กฐํฉ๊ณผ ์ ๊ตฌ์ฑ๋ ํ๋ก์ ํธ๊ฐ ํ์ํฉ๋๋ค. ์ํ๊ณ๊ฐ ํฌ๊ฒ ์ฑ์ํ์ฌ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์คํ์ผ์ ์ฃผ์ฅํ ์ ์๋ ๊ฐ๋ ฅํ ์ต์ ์ ์ ๊ณตํฉ๋๋ค.
์ฌ๋ฐ๋ฅธ ๋๊ตฌ ์ ํ: Jest, React Testing Library, Cypress, Playwright ๋ฑ
ํ๋ฐํธ์๋ ํ ์คํธ ๋๊ตฌ ํ๊ฒฝ์ ํ๋ถํ๊ณ ์งํํ๊ณ ์์ต๋๋ค. CSS ๋จ์ ํ ์คํธ์ ๊ฒฝ์ฐ, JavaScript ์ปดํฌ๋ํธ ํ ์คํธ๋ฅผ ์ํด ์ฃผ๋ก ์ค๊ณ๋ ๋๊ตฌ๋ฅผ ํ์ฉํ์ฌ ์คํ์ผ์ ์ฃผ์ฅํ๋๋ก ๊ธฐ๋ฅ์ ํ์ฅํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
- Jest & React Testing Library (๋๋ Vue Test Utils, Angular Testing Library): ์ด๋ ํด๋น ํ๋ ์์ํฌ์์ ์ปดํฌ๋ํธ ๋จ์ ํ ์คํธ๋ฅผ ์ํ ์ต๊ณ ์ ์ ํ์ธ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ด๋ฅผ ํตํด ์๋ฎฌ๋ ์ด์ ๋ DOM ํ๊ฒฝ(JSDOM๊ณผ ๊ฐ์)์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ณ , ์์๋ฅผ ์ฟผ๋ฆฌํ ๋ค์, ํด๋น ์คํ์ผ์ ๊ฒ์ฌํ ์ ์์ต๋๋ค.
- Cypress ์ปดํฌ๋ํธ ํ ์คํธ: ์ ํต์ ์ผ๋ก ์๋ํฌ์๋ ํ ์คํธ ๋๊ตฌ์๋ Cypress๋ ์ด์ ํ๋ฅญํ ์ปดํฌ๋ํธ ํ ์คํธ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ค์ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ(JSDOM์ด ์๋)์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ์ฌ ๋ณต์กํ ์ํธ ์์ฉ, ์์ฌ ํด๋์ค(`:hover`, `:focus`) ๋ฐ ๋ฏธ๋์ด ์ฟผ๋ฆฌ์ ๋ํ ์คํ์ผ ์ฃผ์ฅ์ด ๋ ์์ ์ ์ ๋๋ค.
- Playwright ์ปดํฌ๋ํธ ํ ์คํธ: Cypress์ ์ ์ฌํ๊ฒ Playwright๋ ์ค์ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ(Chromium, Firefox, WebKit)์ผ๋ก ์ปดํฌ๋ํธ ํ ์คํธ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ธ๋ผ์ฐ์ ์ํธ ์์ฉ ๋ฐ ์ฃผ์ฅ์ ๋ํ ํ์ํ ์ ์ด๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ๋์์ด ๋๋ ๋ฐฉ๋ฒ: ๋ฐ์ํ ๋ฐ ์์ฌ ์ํ๋ฅผ ํฌํจํ ํฌ๊ด์ ์ธ ์คํ์ผ ํ ์คํธ์ ์ด์์ ์ด๋ฉฐ ์ฌ๋ฌ ๋ธ๋ผ์ฐ์ ์์ง์ ์ง์ํฉ๋๋ค.
- Storybook Test Runner: Storybook์ UI ์ปดํฌ๋ํธ ํ์๊ธฐ์ด์ง๋ง, ํ ์คํธ ๋ฌ๋(Jest ๋ฐ Playwright/Cypress ๊ธฐ๋ฐ)๋ฅผ ์ฌ์ฉํ๋ฉด ์คํ ๋ฆฌ์ ๋ํ ์ํธ ์์ฉ ํ ์คํธ ๋ฐ ์๊ฐ์ ํ๊ท ํ ์คํธ๋ฅผ ์คํํ ์ ์์ต๋๋ค. Storybook์ ํ์๋ ์ปดํฌ๋ํธ์ ๋ํ ๊ณ์ฐ๋ ์คํ์ผ์ ์ฃผ์ฅํ๋ ๋จ์ ํ ์คํธ๋ฅผ ํตํฉํ ์๋ ์์ต๋๋ค.
- Stylelint: ์ฃผ์ฅ ์ธก๋ฉด์์ ๋จ์ ํ ์คํธ ๋๊ตฌ๋ ์๋์ง๋ง, Stylelint๋ ์ฝ๋ฉ ๊ท์น์ ๊ฐ์ ํ๊ณ ์ผ๋ฐ์ ์ธ CSS ์ค๋ฅ(์: ์๋ชป๋ ๊ฐ, ์ถฉ๋ํ๋ ์์ฑ, ์ฌ๋ฐ๋ฅธ ์์)๋ฅผ ๋ฐฉ์งํ๋ ๋ฐ ํ์์ ์ ๋๋ค. ํ ์คํธ ์ ์ CSS๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ํ์ฑ๋์๋์ง ํ์ธํ๋ ์ ์ ๋ถ์ ๋๊ตฌ์ ๋๋ค.
๋์์ด ๋๋ ๋ฐฉ๋ฒ: ์ปดํฌ๋ํธ(์: ๋ฒํผ)๋ฅผ ๋ ๋๋งํ๊ณ , ์๋ฎฌ๋ ์ด์ ๋ ์ด๋ฒคํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ ๋ค์(์: `hover`), ์ด์ค์ ์ ์ฌ์ฉํ์ฌ ์คํ์ผ ์์ฑ์ ํ์ธํ ์ ์์ต๋๋ค. `@testing-library/jest-dom`๊ณผ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ CSS ์์ฑ์ ์ง๊ด์ ์ผ๋ก ์ฃผ์ฅํ๋ ๋ฐ ๋์์ด ๋๋ ์ฌ์ฉ์ ์ง์ ์ผ์น์(์: `toHaveStyle`)๋ฅผ ์ ๊ณตํฉ๋๋ค.
// Jest ๋ฐ React Testing Library์ ํจ๊ปํ๋ ์์
import { render, screen } from '@testing-library/react';
import Button from './Button';
import '@testing-library/jest-dom';
test('Button์ด ๊ธฐ๋ณธ ์คํ์ผ๋ก ๋ ๋๋ง๋ฉ๋๋ค', () => {
render();
const button = screen.getByText('Click Me');
expect(button).toHaveStyle(`
background-color: #007bff;
color: #ffffff;
padding: 10px 15px;
`);
});
test('Button์ด ํธ๋ฒ ์ ๋ฐฐ๊ฒฝ์์ด ๋ณ๊ฒฝ๋ฉ๋๋ค', async () => {
render();
const button = screen.getByText('Hover Me');
// ํธ๋ฒ ์๋ฎฌ๋ ์ด์
. ์ด๋ ์ข
์ข
ํน์ ์ ํธ๋ฆฌํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋๋ ํ๋ ์์ํฌ ๋ฉ์ปค๋์ฆ์ด ํ์ํฉ๋๋ค.
// ์ง์ ์ ์ธ CSS ํ
์คํธ์ ๊ฒฝ์ฐ, ํธ๋ฒ ์คํ์ผ์ ์ ์ฉํ๋ ํด๋์ค์ ์กด์ฌ๋ฅผ ํ
์คํธํ๋ ๊ฒ์ด ๋ ์ฌ์ด ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
// ๋๋ Playwright/Cypress ์ปดํฌ๋ํธ ํ
์คํธ์ ๊ฐ์ ์ค์ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์ ์์กดํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
// jest-dom ๋ฐ JSDOM์ ์ฌ์ฉํ๋ฉด :hover์ ๋ํ ๊ณ์ฐ๋ ์คํ์ผ์ด ์ข
์ข
๊ธฐ๋ณธ์ ์ผ๋ก ์์ ํ ์ง์๋์ง ์์ต๋๋ค.
// ์ผ๋ฐ์ ์ธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ํธ๋ฒ ์คํ์ผ์ ์ ์ฉํ className์ ์กด์ฌ๋ฅผ ํ
์คํธํ๋ ๊ฒ์
๋๋ค.
expect(button).not.toHaveClass('hovered');
// CSS-in-JS์ ๊ฒฝ์ฐ, ์ปดํฌ๋ํธ์ ๋ด๋ถ ํธ๋ฒ ์คํ์ผ์ ์ง์ ์ฃผ์ฅํ ์ ์์ต๋๋ค.
// ์์ CSS์ ๊ฒฝ์ฐ ์ด๊ฒ์ด ์ ํ ์ฌํญ์ด ๋ ์ ์์ผ๋ฉฐ, ํตํฉ ํ
์คํธ๋ฅผ ํธ๋ฒ์ ๋ ์ ํฉํ๊ฒ ๋ง๋ญ๋๋ค.
});
๋์์ด ๋๋ ๋ฐฉ๋ฒ: ์ค์ ๋ธ๋ผ์ฐ์ ๋ ๋๋ง ์์ง์ ์ฌ์ฉํ์ฌ CSS๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง ์ ํํ๊ฒ ํ ์คํธํ๋ ๋ฐ ํ์ํฉ๋๋ค. ์ปดํฌ๋ํธ์ ์ํธ ์์ฉํ๊ณ , ๋ทฐํฌํธ๋ฅผ ์กฐ์ ํ๊ณ , `cy.should('have.css', 'property', 'value')`๋ก ๊ณ์ฐ๋ ์คํ์ผ์ ์ฃผ์ฅํ ์ ์์ต๋๋ค.
// Cypress ์ปดํฌ๋ํธ ํ
์คํธ ์์
import Button from './Button';
import { mount } from 'cypress/react'; // ๋๋ vue, angular
describe('Button Component Styles', () => {
it('๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ์์ผ๋ก ๋ ๋๋ง๋ฉ๋๋ค', () => {
mount();
cy.get('button').should('have.css', 'background-color', 'rgb(0, 123, 255)'); // ์ฐธ๊ณ : ๊ณ์ฐ๋ ์์์ RGB์
๋๋ค
});
it('ํธ๋ฒ ์ ๋ฐฐ๊ฒฝ์์ด ๋ณ๊ฒฝ๋ฉ๋๋ค', () => {
mount();
cy.get('button')
.should('have.css', 'background-color', 'rgb(0, 123, 255)')
.realHover() // ํธ๋ฒ ์๋ฎฌ๋ ์ด์
.should('have.css', 'background-color', 'rgb(0, 86, 179)'); // ํธ๋ฒ ์ ๋ ์ด๋์ด ํ๋์
});
it('์์ ํ๋ฉด์์ ๋ฐ์ํ์
๋๋ค', () => {
cy.viewport(375, 667); // ๋ชจ๋ฐ์ผ ๋ทฐํฌํธ ์๋ฎฌ๋ ์ด์
mount();
cy.get('button').should('have.css', 'font-size', '14px'); // ์: ๋ชจ๋ฐ์ผ์์ ๋ ์์ ๊ธ๊ผด
cy.viewport(1200, 800); // ๋ฐ์คํฌํฑ์ผ๋ก ์ฌ์ค์
cy.get('button').should('have.css', 'font-size', '16px'); // ์: ๋ฐ์คํฌํฑ์์ ๋ ํฐ ๊ธ๊ผด
});
});
๋น๋ ์์คํ (Webpack, Vite)๊ณผ์ ํตํฉ
CSS ๋จ์ ํ ์คํธ๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์ฒ๋ฆฌ๋ CSS์ ์ก์ธ์คํ ์ ์์ด์ผ ํฉ๋๋ค. ์ด๋ ํ ์คํธ ํ๊ฒฝ์ด ๋น๋ ์์คํ (Webpack, Vite, Rollup, Parcel)๊ณผ ์ฌ๋ฐ๋ฅด๊ฒ ํตํฉ๋์ด์ผ ํจ์ ์๋ฏธํฉ๋๋ค. CSS Modules, Sass/Less ์ฌ์ ์ฒ๋ฆฌ๊ธฐ, PostCSS ๋๋ TailwindCSS์ ๊ฒฝ์ฐ ํ ์คํธ ์ค์ ์ ์ด๋ฌํ ๋ณํ์ด ์์ ์คํ์ผ์ ๋ธ๋ผ์ฐ์ ์์ ํด์ ๊ฐ๋ฅํ CSS๋ก ์ด๋ป๊ฒ ๋ณํํ๋์ง ์ดํดํด์ผ ํฉ๋๋ค.
- CSS Modules: CSS Modules๋ฅผ ์ฌ์ฉํ ๋ ํด๋์ค๋ ํด์๋ฉ๋๋ค(์: `button_module__abc12`). ํ ์คํธ์์๋ CSS ๋ชจ๋์ ๊ฐ์ ธ์ ์์ฑ๋ ํด๋์ค ์ด๋ฆ์ ์ฌ์ฉํ์ฌ ํ ์คํธ DOM์ ์์์ ์ ์ฉํด์ผ ํฉ๋๋ค.
- ์ฌ์ ์ฒ๋ฆฌ๊ธฐ(Sass, Less): ์ปดํฌ๋ํธ์์ Sass ๋๋ Less๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ Jest์๋ ์ด๋ฌํ ์คํ์ผ์ ์ปดํ์ผํ๊ธฐ ์ํ ์ฌ์ ์ฒ๋ฆฌ๊ธฐ(์: `jest-scss-transform` ๋๋ ์ฌ์ฉ์ ์ง์ ์ค์ )๊ฐ ํ์ํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ณ์, ๋ฏน์ค์ธ ๋ฐ ์ค์ฒฉ ๊ท์น์ด ์ฌ๋ฐ๋ฅด๊ฒ ํด๊ฒฐ๋ฉ๋๋ค.
- PostCSS: ์๋ ์ ๋์ฌ, ์ถ์ ๋๋ ์ฌ์ฉ์ ์ง์ ๋ณํ์ ์ํด PostCSS๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํ ์คํธ ํ๊ฒฝ์์ ์ด๋ฌํ ๋ณํ์ ์คํํ๊ฑฐ๋ ๊ฐ๋ฅํ ๊ฒฝ์ฐ ์ต์ข ๋ณํ๋ CSS๋ฅผ ํ ์คํธํด์ผ ํฉ๋๋ค.
๋๋ถ๋ถ์ ์ต์ ํ๋ฐํธ์๋ ํ๋ ์์ํฌ์ ํ ์คํธ ์ค์ (์: Create React App, Vue CLI, Next.js)์ ์ด๋ฌํ ๊ตฌ์ฑ์ ์๋น ๋ถ๋ถ์ ์ฆ์ ์ฒ๋ฆฌํ๊ฑฐ๋ ํ์ฅ ๋ฐฉ๋ฒ์ ๋ํ ๋ช ํํ ๋ฌธ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
ํ ์คํธ ๊ฐ๋ฅ์ฑ์ ์ํ ํ๋ก์ ํธ ๊ตฌ์กฐ
์ ๊ตฌ์ฑ๋ ํ๋ก์ ํธ ๊ตฌ์กฐ๋ CSS ํ ์คํธ ์ฉ์ด์ฑ์ ํฌ๊ฒ ํฅ์์ํต๋๋ค.
- ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ์ํคํ ์ฒ: ์คํ์ผ์ ํด๋น ์ปดํฌ๋ํธ์ ํจ๊ป ๊ตฌ์ฑํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ด๋ค ์คํ์ผ์ด ์ด๋ค ์ปดํฌ๋ํธ์ ์ํ๋์ง ๋ช ํํด์ง๋ฏ๋ก ์ด๋ค ํ ์คํธ๊ฐ ์ด๋ฅผ ์ปค๋ฒํด์ผ ํ๋์ง๋ ๋ช ํํด์ง๋๋ค.
- Atomic CSS/์ ํธ๋ฆฌํฐ ํด๋์ค: Atomic CSS(์: TailwindCSS) ๋๋ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ผ๊ด๋๊ฒ ์ ์ฉ๋๊ณ ์ ๋ฌธ์ํ๋์๋์ง ํ์ธํฉ๋๋ค. ์ด๋ฌํ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ ์ฌ๋ฐ๋ฅธ ๋จ์ผ ์์ฑ์ ์ ์ฉํ๋์ง ํ ๋ฒ ํ ์คํธํ๊ณ ์ฌ์ฉ์ ์ ๋ขฐํ ์ ์์ต๋๋ค.
- ๋์์ธ ํ ํฐ: ๋์์ธ ๋ณ์(์์, ๊ฐ๊ฒฉ, ํ์ดํฌ๊ทธ๋ํผ ๋ฑ)๋ฅผ ๋์์ธ ํ ํฐ์ผ๋ก ์ค์ ์ง์คํํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ์ด๋ฌํ ํ ํฐ์ ์ฌ๋ฐ๋ฅด๊ฒ ์๋นํ๋์ง ํ ์คํธํ๊ธฐ๊ฐ ๋ ์ฌ์์ง๋๋ค.
- `__tests__` ๋๋ `*.test.js` ํ์ผ: ํ ์คํธ ํ์ผ์ ํ ์คํธํ๋ ์ปดํฌ๋ํธ์ ํจ๊ป ๋๋ ์ผ๋ฐ์ ์ธ ํ ์คํธ ํจํด์ ๋ฐ๋ฅด๋ ์ ์ฉ `__tests__` ๋๋ ํฐ๋ฆฌ์ ๋ฐฐ์นํฉ๋๋ค.
CSS ๋จ์ ํ ์คํธ ๊ตฌํ: ์ค์ง์ ์ธ ์ ๊ทผ ๋ฐฉ์
์ด์ ์ด๋ก ์ ๋์ด ์ค์ ์ฝ๋๋ฅผ ํตํด CSS ๋จ์ ํ ์คํธ๋ฅผ ๊ตฌํํ๋ ๊ตฌ์ฒด์ ์ธ ๋ฐฉ๋ฒ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ปดํฌ๋ํธ๋ณ ์คํ์ผ ํ ์คํธ(์: Button, Card)
๋๋ถ๋ถ์ ๊ฒฝ์ฐ CSS ๋จ์ ํ ์คํธ๋ ๊ฐ๋ณ UI ์ปดํฌ๋ํธ์ ์คํ์ผ์ด ์ ์ฉ๋๋ ๋ฐฉ์์ ์ค์ ์ ๋ก๋๋ค. ์ฌ๊ธฐ์ CSS ํ ์คํธ ๊ท์น์ด ๋น์ ๋ฐํ์ฌ ๊ฐ ์ปดํฌ๋ํธ๊ฐ ๋์์ธ ์ฌ์์ ์ค์ํ๋๋ก ํฉ๋๋ค.
์ ๊ทผ์ฑ(์์ ๋๋น, ํฌ์ปค์ค ์ํ, ๊ฐ๋ ์ฑ์ ์ํ ๋ฐ์ํ)
์ ์ฒด ์ ๊ทผ์ฑ ๊ฐ์ฌ์๋ ๋ณต์ก์ฑ์ด ์์ง๋ง, ๋จ์ ํ ์คํธ๋ ์ค์ํ ์ ๊ทผ์ฑ ๊ด๋ จ CSS ์์ฑ์ ๊ฐ์ ํ ์ ์์ต๋๋ค.
- ์์ ๋๋น: ๊ฐ๋จํ ์คํ์ผ ์ฃผ์ฅ์ผ๋ก WCAG ๋๋น์จ์ ์ง์ ํ์ธํ ์๋ ์์ง๋ง, ๊ตฌ์ฑ ์์๊ฐ ๋๋น ์๊ตฌ ์ฌํญ์ ์ถฉ์กฑํ๋ ๊ฒ์ผ๋ก ์๋ ค์ง ํน์ ์ฌ์ ์น์ธ๋ ์์ ํ ํฐ์ ํญ์ ์ฌ์ฉํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
- ํฌ์ปค์ค ์ํ: ๋ํํ ์์์ ๋ช ํํ๊ณ ๋ณด์ด๋ ํฌ์ปค์ค ํ์๊ธฐ๊ฐ ์๋์ง ํ์ธํ๋ ๊ฒ์ ํค๋ณด๋ ํ์ ์ฌ์ฉ์์ ๊ฒฝ์ฐ ๊ฐ์ฅ ์ค์ํฉ๋๋ค.
test('Button์ด ์น์ธ๋ ํ
์คํธ ๋ฐ ๋ฐฐ๊ฒฝ์์ ์ฌ์ฉํฉ๋๋ค', () => {
render();
const button = screen.getByText('Accessible');
expect(button).toHaveStyle('background-color: rgb(0, 123, 255)');
expect(button).toHaveStyle('color: rgb(255, 255, 255)');
// ๊ทธ ์ด์์ ๋ณ๋์ ์ ๊ทผ์ฑ ๋๊ตฌ๊ฐ ๋๋น์จ์ ํ์ธํฉ๋๋ค.
});
test('Button์ ๋ณด์ด๋ ํฌ์ปค์ค ์ค๊ณฝ์ ์ด ์์ต๋๋ค', async () => {
// ์ค์ ํฌ์ปค์ค ์ํ ์๋ฎฌ๋ ์ด์
์ ์ํด Cypress ๋๋ Playwright ์ฌ์ฉ์ด ์ด์์ ์
๋๋ค.
// JSDOM์ ๊ฒฝ์ฐ, ํฌ์ปค์ค ์ ์ ์ฉ๋๋ ํน์ ํด๋์ค ๋๋ ์คํ์ผ์ ์กด์ฌ๋ฅผ ํ
์คํธํ ์ ์์ต๋๋ค.
mount();
cy.get('button').focus();
cy.get('button').should('have.css', 'outline-style', 'solid');
cy.get('button').should('have.css', 'outline-color', 'rgb(0, 86, 179)'); // ์์ ํฌ์ปค์ค ์์
});
๋ฐ์ํ(๋ฏธ๋์ด ์ฟผ๋ฆฌ)
๋ฐ์ํ ์คํ์ผ ํ ์คํธ๋ ๋ค์ํ ์ฅ์น๋ฅผ ์ฌ์ฉํ๋ ์ ์ธ๊ณ ์ฌ์ฉ์๋ฅผ ์ํด ์ค์ํฉ๋๋ค. Cypress ๋๋ Playwright์ ๊ฐ์ ๋๊ตฌ๋ ๋ทฐํฌํธ ์กฐ์์ ํ์ฉํ๋ฏ๋ก ์ฌ๊ธฐ์ ๋ฐ์ด๋ฉ๋๋ค.
๋ชจ๋ฐ์ผ์์ ๋ ์ด์์์ด ๋ณ๊ฒฝ๋๋ `Header` ์ปดํฌ๋ํธ๋ฅผ ๊ณ ๋ คํด ๋ณด๊ฒ ์ต๋๋ค.
CSS(๊ฐ๋ตํ๋จ):
.header {
display: flex;
flex-direction: row;
}
@media (max-width: 768px) {
.header {
flex-direction: column;
align-items: center;
}
}
ํ ์คํธ(Cypress):
import Header from './Header';
import { mount } from 'cypress/react';
describe('Header Responsiveness', () => {
it('๋ฐ์คํฌํฑ์์ row-flex์
๋๋ค', () => {
cy.viewport(1024, 768); // ๋ฐ์คํฌํฑ ํฌ๊ธฐ
mount( );
cy.get('.header').should('have.css', 'flex-direction', 'row');
});
it('๋ชจ๋ฐ์ผ์์ column-flex์
๋๋ค', () => {
cy.viewport(375, 667); // ๋ชจ๋ฐ์ผ ํฌ๊ธฐ
mount( );
cy.get('.header').should('have.css', 'flex-direction', 'column');
cy.get('.header').should('have.css', 'align-items', 'center');
});
});
์ํ ๋ณ๊ฒฝ(ํธ๋ฒ, ํ์ฑ, ๋นํ์ฑํ)
๋ํํ ์ํ๋ ์ผ๋ฐ์ ์ธ ์คํจ ์ง์ ์ ๋๋ค. ์ด๋ฅผ ํ ์คํธํ๋ฉด ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
CSS( `PrimaryButton`์ ๋ํ ๊ฐ๋ตํ๋จ):
.primary-button {
background-color: var(--color-primary);
}
.primary-button:hover {
background-color: var(--color-primary-dark);
}
.primary-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
ํ ์คํธ(Cypress/Playwright):
import PrimaryButton from './PrimaryButton';
import { mount } from 'cypress/react';
describe('PrimaryButton State Styles', () => {
it('๊ธฐ๋ณธ ์ํ์์ ๊ธฐ๋ณธ ์์์ ๊ฐ์ง๋๋ค', () => {
mount(Submit );
cy.get('button').should('have.css', 'background-color', 'rgb(0, 123, 255)');
});
it('ํธ๋ฒ ์ ๊ธฐ๋ณธ ์์์ด ์ด๋์์ง๋๋ค', () => {
mount(Submit );
cy.get('button')
.realHover()
.should('have.css', 'background-color', 'rgb(0, 86, 179)');
});
it('๋นํ์ฑํ ์ ๋นํ์ฑํ ์คํ์ผ์ ๊ฐ์ง๋๋ค', () => {
mount(Submit );
cy.get('button')
.should('have.css', 'opacity', '0.6')
.and('have.css', 'cursor', 'not-allowed');
});
});
๋์ ์คํ์ผ(Props ๊ธฐ๋ฐ, JS ์ ์ด)
์ปดํฌ๋ํธ๋ ์ข ์ข JavaScript props(์: `size="small"`, `variant="outline"`)์ ๋ฐ๋ผ ์คํ์ผ์ด ๋ณ๊ฒฝ๋ฉ๋๋ค.
ํ ์คํธ(Jest + React Testing Library for `Badge` ์ปดํฌ๋ํธ with `variant` prop):
// Badge.js (๊ฐ๋ตํ๋ CSS-in-JS ๋๋ CSS Modules ์ ๊ทผ ๋ฐฉ์)
import React from 'react';
import styled from 'styled-components'; // styled-components๋ฅผ ์ฌ์ฉํ ์์
const StyledBadge = styled.span`
display: inline-flex;
padding: 4px 8px;
border-radius: 4px;
${props => props.variant === 'info' && `
background-color: #e0f2f7;
color: #01579b;
`}
${props => props.variant === 'success' && `
background-color: #e8f5e9;
color: #2e7d32;
`}
`;
const Badge = ({ children, variant }) => (
{children}
);
export default Badge;
// Badge.test.js
import { render, screen } from '@testing-library/react';
import Badge from './Badge';
import 'jest-styled-components'; // styled-components ํน์ ์ผ์น์๋ฅผ ์ํด
test('Badge๊ฐ info variant ์คํ์ผ๋ก ๋ ๋๋ง๋ฉ๋๋ค', () => {
render(New );
const badge = screen.getByText('New');
expect(badge).toHaveStyleRule('background-color', '#e0f2f7');
expect(badge).toHaveStyleRule('color', '#01579b');
});
test('Badge๊ฐ success variant ์คํ์ผ๋ก ๋ ๋๋ง๋ฉ๋๋ค', () => {
render(Success );
const badge = screen.getByText('Success');
expect(badge).toHaveStyleRule('background-color', '#e8f5e9');
expect(badge).toHaveStyleRule('color', '#2e7d32');
});
๋ ์ด์์ ๋ฌด๊ฒฐ์ฑ(Flexbox, Grid ๋์
๋ณต์กํ ๋ ์ด์์ ํ ์คํธ๋ ์ข ์ข ์๊ฐ์ ํ๊ท์ ๋์์ด ๋์ง๋ง, ๋จ์ ํ ์คํธ๋ ๋ ์ด์์์ ์ ์ํ๋ ํน์ CSS ์์ฑ์ ์ฃผ์ฅํ ์ ์์ต๋๋ค.
์์: CSS Grid๋ฅผ ์ฌ์ฉํ๋ `GridContainer` ์ปดํฌ๋ํธ.
// GridContainer.js
import React from 'react';
import './GridContainer.css';
const GridContainer = ({ children }) => (
{children}
);
export default GridContainer;
// GridContainer.css
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
@media (max-width: 768px) {
.grid-container {
grid-template-columns: 1fr; // ๋ชจ๋ฐ์ผ์์ ๋จ์ผ ์ด
}
}
// GridContainer.test.js (Cypress ์ฌ์ฉ)
import GridContainer from './GridContainer';
import { mount } from 'cypress/react';
describe('GridContainer Layout', () => {
it('๋ฐ์คํฌํฑ์์ 3์ด ๊ทธ๋ฆฌ๋๋ก ํ์๋ฉ๋๋ค', () => {
cy.viewport(1200, 800);
mount(Item 1Item 2Item 3 );
cy.get('.grid-container')
.should('have.css', 'display', 'grid')
.and('have.css', 'grid-template-columns', '1fr 1fr 1fr'); // ๊ณ์ฐ๋ ๊ฐ
cy.get('.grid-container').should('have.css', 'gap', '16px');
});
it('๋ชจ๋ฐ์ผ์์ ๋จ์ผ ์ด๋ก ํ์๋ฉ๋๋ค', () => {
cy.viewport(375, 667);
mount(Item 1Item 2 );
cy.get('.grid-container')
.should('have.css', 'grid-template-columns', '1fr');
});
});
๊ด์ฌ์ฌ ๋ถ๋ฆฌ: ์์ CSS ํจ์/๋ฏน์ค์ธ ํ ์คํธ
CSS ์ฌ์ ์ฒ๋ฆฌ๊ธฐ(Sass, Less, Stylus)๋ฅผ ์ฌ์ฉํ๋ ํ๋ก์ ํธ์ ๊ฒฝ์ฐ, ์ข ์ข ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฏน์ค์ธ์ด๋ ํจ์๋ฅผ ์์ฑํฉ๋๋ค. ๋ค์ํ ์ ๋ ฅ์ผ๋ก ์ปดํ์ผํ๊ณ ๊ฒฐ๊ณผ CSS ์ถ๋ ฅ์ ์ฃผ์ฅํ์ฌ ์ด๋ฅผ ๋จ์ ํ ์คํธํ ์ ์์ต๋๋ค.
์์: ๋ฐ์ํ ํจ๋ฉ์ ์ํ Sass ๋ฏน์ค์ธ.
// _mixins.scss
@mixin responsive-padding($desktop-padding, $mobile-padding) {
padding: $desktop-padding;
@media (max-width: 768px) {
padding: $mobile-padding;
}
}
// Sass ์ปดํ์ผ๋ฌ๋ฅผ ์ฌ์ฉํ Node.js์์ ํ
์คํธ
const sass = require('sass');
describe('responsive-padding mixin', () => {
it('๋ฐ์คํฌํฑ ๋ฐ ๋ชจ๋ฐ์ผ์ฉ ์ฌ๋ฐ๋ฅธ ํจ๋ฉ์ ์์ฑํฉ๋๋ค', () => {
const result = sass.renderSync({
data: `@use 'sass:math'; @import '_mixins.scss'; .test { @include responsive-padding(20px, 10px); }`,
includePaths: [__dirname] // _mixins.scss๊ฐ ์๋ ์์น
}).css.toString();
expect(result).toContain('padding: 20px;');
expect(result).toContain('@media (max-width: 768px) {
.test {
padding: 10px;
}
}');
});
});
์ด ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์คํ์ผ ๋ธ๋ก์ ํต์ฌ ๋ก์ง์ ํ ์คํธํ์ฌ ์ปดํฌ๋ํธ์ ์ ์ฉ๋๊ธฐ ์ ์ ์๋ํ CSS ๊ท์น์ ์์ฑํ๋์ง ํ์ธํฉ๋๋ค.
CSS-in-JS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ ์ฉ์ด์ฑ ํฅ์
Styled Components, Emotion ๋๋ Stitches์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ CSS๋ฅผ JavaScript๋ก ์ง์ ๊ฐ์ ธ์ ๋จ์ ํ ์คํธ๋ฅผ ํฌ๊ฒ ๋จ์ํํฉ๋๋ค. ์คํ์ผ์ JS ๋ด์ ์ ์๋๋ฏ๋ก ์ง์ ๊ฐ์ ธ์ ์์ฑ๋ CSS๋ฅผ ์ฃผ์ฅํ ์ ์์ต๋๋ค.
`jest-styled-components`์ ๊ฐ์ ๋๊ตฌ๋ ์์ฑ๋ CSS์ ํจ๊ป ์๋ํ๋ ์ฌ์ฉ์ ์ง์ ์ผ์น์(`toHaveStyleRule`)๋ฅผ ์ ๊ณตํ์ฌ ์ฃผ์ฅ์ ๋จ์ํํฉ๋๋ค.
์์(Styled Components + Jest):
// Button.js
import styled from 'styled-components';
const Button = styled.button`
background-color: blue;
color: white;
font-size: 16px;
&:hover {
background-color: darkblue;
}
&.disabled {
opacity: 0.5;
}
`;
export default Button;
// Button.test.js
import React from 'react';
import { render } from '@testing-library/react';
import Button from './Button';
import 'jest-styled-components';
describe('Button Styled Component', () => {
it('๊ธฐ๋ณธ ์คํ์ผ๋ก ๋ ๋๋ง๋ฉ๋๋ค', () => {
const { container } = render();
expect(container.firstChild).toHaveStyleRule('background-color', 'blue');
expect(container.firstChild).toHaveStyleRule('color', 'white');
expect(container.firstChild).toHaveStyleRule('font-size', '16px');
});
it('ํธ๋ฒ ์คํ์ผ์ ์ ์ฉํฉ๋๋ค', () => {
const { container } = render();
// toHaveStyleRule ์ผ์น์๋ ์์ฌ ์ํ๋ฅผ ์ง์ ํ
์คํธํ ์ ์์ต๋๋ค.
expect(container.firstChild).toHaveStyleRule('background-color', 'darkblue', {
modifier: ':hover'
});
});
it('className์ด ์กด์ฌํ ๋ ๋นํ์ฑํ ์คํ์ผ์ ์ ์ฉํฉ๋๋ค', () => {
const { container } = render();
expect(container.firstChild).toHaveStyleRule('opacity', '0.5');
});
});
์ ํธ๋ฆฌํฐ ํด๋์ค ๋ฐ ๋์์ธ ํ ํฐ ํ ์คํธ
Tailwind CSS์ ๊ฐ์ ์ ํธ๋ฆฌํฐ ์ฐ์ CSS ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์์ฒด Atomic ์ ํธ๋ฆฌํฐ ํด๋์ค ์ธํธ๊ฐ ์๋ ๊ฒฝ์ฐ, ์ด๋ฌํ ํด๋์ค๊ฐ ์๋ํ ์คํ์ผ๋ง ์ ์ฉํ๋์ง ํ์ธํ๊ธฐ ์ํด ๋จ์ ํ ์คํธํ ์ ์์ต๋๋ค. ์ด๋ ๊ฐ๋จํ ์์๋ฅผ ๋ ๋๋งํ๊ณ ๊ณ์ฐ๋ ์คํ์ผ์ ์ฃผ์ฅํ์ฌ ์ํํ ์ ์์ต๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก, ๋์์ธ ํ ํฐ(CSS ์ฌ์ฉ์ ์ ์ ์์ฑ)์ ๊ฒฝ์ฐ ํ ๋ง ์์คํ ์ด ์ด๋ฌํ ๋ณ์๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ถ๋ ฅํ๊ณ ์ปดํฌ๋ํธ๊ฐ ์์๋๋ก ์๋นํ๋์ง ํ ์คํธํ ์ ์์ต๋๋ค.
์์: `text-bold` ์ ํธ๋ฆฌํฐ ํด๋์ค ํ ์คํธ.
// utility.css
.text-bold {
font-weight: 700;
}
// utility.test.js (Jest ๋ฐ JSDOM ์ฌ์ฉ)
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import './utility.css'; // JSDOM์ ๋ํ CSS ๊ฐ์ ธ์ค๊ธฐ/๋ชจํน์ ์ฌ๋ฐ๋ฅด๊ฒ ํ์ธ
test('text-bold ์ ํธ๋ฆฌํฐ ํด๋์ค๊ฐ font-weight 700์ ์ ์ฉํฉ๋๋ค', () => {
render(Bold Text);
const element = screen.getByText('Bold Text');
expect(element).toHaveStyle('font-weight: 700;');
});
๋ชจํน ๋ฐ ์์ ๋ ๋๋ง์ ์ํ CSS ์์ฑ
์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ ๋ ์์ ์ปดํฌ๋ํธ์ ์คํ์ผ์ ๊ฒฉ๋ฆฌํ๊ธฐ ์ํด ํ์ ์ปดํฌ๋ํธ๋ฅผ ์๊ฒ ๋ ๋๋งํ๊ฑฐ๋ ๋ชจํนํ๋ ๊ฒ์ด ์ข ์ข ์ ์ตํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด CSS ๋จ์ ํ ์คํธ๊ฐ ์ง์ค๋ ฅ์ ์ ์งํ๊ณ ์ค์ฒฉ๋ ์์์ ๋ณ๊ฒฝ์ผ๋ก ์ธํด ๋ถ์์ ํด์ง์ง ์์ต๋๋ค.
CSS์ ๊ฒฝ์ฐ, ์ปดํฌ๋ํธ ์คํ์ผ์ ๊ฒฉ๋ฆฌ๊ฐ ์ ์ญ ์คํ์ผ์ด๋ ์ธ๋ถ ์คํ์ผ์ํธ์ ๊ฐ์ญํ๋ ๊ฒฝ์ฐ ์ด๋ฅผ ๋ชจํนํด์ผ ํ ์๋ ์์ต๋๋ค. Jest์ `moduleNameMapper`์ ๊ฐ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ CSS ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ๋ชจํนํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ณ ๊ธ CSS ๋จ์ ํ ์คํธ ์ ๋ต
๊ธฐ๋ณธ ์์ฑ ์ฃผ์ฅ ์ธ์๋ ์ฌ๋ฌ ๊ณ ๊ธ ์ ๋ต์ ํตํด CSS ํ ์คํธ ๋ ธ๋ ฅ์ ๋์ฑ ํฅ์์ํฌ ์ ์์ต๋๋ค.
์ค๋ ์ท ํ ์คํธ๋ฅผ ์ฌ์ฉํ ์๊ฐ์ ์ฃผ์ฅ ์๋ํ(์คํ์ผ์ฉ)
์๊ฐ์ ํ๊ท๋ ์ด๋ฏธ์ง๋ฅผ ๋น๊ตํ์ง๋ง, ์คํ์ผ์ฉ ์ค๋ ์ท ํ ์คํธ๋ ์ปดํฌ๋ํธ์ ๋ ๋๋ง๋ HTML ๊ตฌ์กฐ์ ํด๋น CSS๋ฅผ ๊ธฐ๋กํฉ๋๋ค. Jest์ ์ค๋ ์ท ํ ์คํธ ๊ธฐ๋ฅ์ด ์ด๋ฅผ ์ํด ์ธ๊ธฐ๊ฐ ์์ต๋๋ค.
์ฒ์ ์ค๋ ์ท ํ ์คํธ๋ฅผ ์คํํ๋ฉด ์ปดํฌ๋ํธ ๋ ๋๋ง์ ์ง๋ ฌํ๋ ์ถ๋ ฅ(HTML ๋ฐ ์ข ์ข CSS-in-JS์ ๋ํ ์์ฑ๋ ์คํ์ผ)์ ํฌํจํ๋ `.snap` ํ์ผ์ ์์ฑํฉ๋๋ค. ํ์ ์คํ์ ํ์ฌ ์ถ๋ ฅ์ ์ค๋ ์ท๊ณผ ๋น๊ตํฉ๋๋ค. ๋ถ์ผ์น๊ฐ ์์ผ๋ฉด ํ ์คํธ๊ฐ ์คํจํ๋ฉฐ, ๋ณ๊ฒฝ์ด ์๋๋ ๊ฒ์ด๋ผ๋ฉด ์ฝ๋๋ฅผ ์์ ํ๊ฑฐ๋ ์ค๋ ์ท์ ์ ๋ฐ์ดํธํด์ผ ํฉ๋๋ค.
์ฅ์ : ์์์น ๋ชปํ ๊ตฌ์กฐ ๋๋ ์คํ์ผ ๋ณ๊ฒฝ์ ํฌ์ฐฉํ๊ณ ๊ตฌํ์ด ๋น ๋ฅด๋ฉฐ ๋ณต์กํ ์ปดํฌ๋ํธ์ ์ผ๊ด์ฑ์ ๋ณด์ฅํ๋ ๋ฐ ์ข์ต๋๋ค.
๋จ์ : ์ปดํฌ๋ํธ ๊ตฌ์กฐ ๋๋ ์์ฑ๋ ํด๋์ค ์ด๋ฆ์ด ์์ฃผ ๋ณ๊ฒฝ๋๋ฉด ๋ถ์์ ํด์ง ์ ์์ต๋๋ค. ์ค๋ ์ท์ด ์ปค์ ธ ๊ฒํ ํ๊ธฐ ์ด๋ ค์์ง ์ ์์ต๋๋ค. ํฝ์ ๋จ์๋ก ์๋ฒฝํ ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ธ์ ์์ ํ ๋์ฒดํ์ง๋ ๋ชปํฉ๋๋ค.
์์(Jest + Styled Components ์ค๋ ์ท):
// Button.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import Button from './Button'; // ์คํ์ผ์ด ์ ์ฉ๋ ์ปดํฌ๋ํธ ๋ฒํผ
test('Button ์ปดํฌ๋ํธ๊ฐ ์ค๋
์ท๊ณผ ์ผ์นํฉ๋๋ค', () => {
const tree = renderer.create().toJSON();
expect(tree).toMatchSnapshot();
});
// .snap ํ์ผ์๋ ๋ค์๊ณผ ๊ฐ์ ๋ด์ฉ์ด ํฌํจ๋ฉ๋๋ค:
// exports[`Button ์ปดํฌ๋ํธ๊ฐ ์ค๋
์ท๊ณผ ์ผ์นํฉ๋๋ค 1`] = `
// .c0 {
// background-color: blue;
// color: white;
// font-size: 16px;
// }
// .c0:hover {
// background-color: darkblue;
// }
//
// `;
CSS ์ฑ๋ฅ ํ ์คํธ(Critical CSS, FOUC)
์ด๋ ์ข ์ข ํตํฉ ๋๋ E2E ๋ฌธ์ ์ด์ง๋ง CSS ์ฑ๋ฅ์ ์ธก๋ฉด์ ๋จ์ ํ ์คํธํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ด๊ธฐ ํ์ด์ง ๋ก๋๋ฅผ ๋ ๋น ๋ฅด๊ฒ ํ๊ธฐ ์ํด Critical CSS๋ฅผ ์์ฑํ๋ ๋น๋ ๋จ๊ณ๊ฐ ์๋ ๊ฒฝ์ฐ, Critical CSS๊ฐ ์๋จ ๋ทฐํฌํธ ์ฝํ ์ธ ์ ๋ํ ์์ ๊ท์น์ ํฌํจํ๋์ง ํ์ธํ๊ธฐ ์ํด ํด๋น ํ๋ก์ธ์ค์ ์ถ๋ ฅ์ ๋จ์ ํ ์คํธํ ์ ์์ต๋๋ค.
์์ฑ๋ Critical CSS ๋ฒ๋ค ๋ด์ ํน์ ํต์ฌ ์คํ์ผ(์: ํค๋, ๋ค๋น๊ฒ์ด์ ๋๋ ๊ธฐ๋ณธ ์ฝํ ์ธ ์์ญ)์ด ์๋์ง ์ฃผ์ฅํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์คํ์ผ์ด ์ ์ฉ๋์ง ์์ ์ฝํ ์ธ ์ ๊น๋ฐ์(FOUC)์ ๋ฐฉ์งํ๊ณ ๋คํธ์ํฌ ์กฐ๊ฑด์ ๊ด๊ณ์์ด ์ ์ธ๊ณ ์ฌ์ฉ์๋ฅผ ์ํ ์ํํ ๋ก๋ฉ ๊ฒฝํ์ ๋ณด์ฅํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
CI/CD ํ์ดํ๋ผ์ธ๊ณผ์ ํตํฉ
CSS ๋จ์ ํ ์คํธ์ ์ง์ ํ ํ์ CI/CD(์ง์์ ํตํฉ/์ง์์ ์ ๊ณต) ํ์ดํ๋ผ์ธ์ ํตํฉ๋ ๋ ์คํ๋ฉ๋๋ค. ๋ชจ๋ ์ฝ๋ ์ปค๋ฐ์ CSS ๋จ์ ํ ์คํธ๋ฅผ ํฌํจํ ํ ์คํธ ์ ํ๊ตฐ์ ํธ๋ฆฌ๊ฑฐํด์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์คํ์ผ ํ๊ท๊ฐ ์ฝ๋๋ฒ ์ด์ค์ ์ฃผ์ ๋ถ๋ถ์ผ๋ก ๋ณํฉ๋๊ธฐ ์ ์ ์ฆ์ ํฌ์ฐฉ๋ฉ๋๋ค.
- ์๋ํ๋ ํ์ธ: GitHub Actions, GitLab CI, Jenkins, Azure DevOps ๋๋ ์ ํํ CI ํ๋ซํผ์ ๊ตฌ์ฑํ์ฌ ๋ชจ๋ ํธ์ ๋๋ ํ ์์ฒญ์์ `npm test`(๋๋ ํด๋น ๊ฐ)๋ฅผ ์คํํฉ๋๋ค.
- ๋น ๋ฅธ ํผ๋๋ฐฑ: ๊ฐ๋ฐ์๋ ์คํ์ผ ๋ณ๊ฒฝ์ ๋ํ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ๋ฐ์ ์ ์ํ ์์ ์ ์ํํ ์ ์์ต๋๋ค.
- ํ์ง ๊ฒ์ดํธ: CSS ๋จ์ ํ ์คํธ๊ฐ ์คํจํ๋ฉด ๋ณํฉ์ ๋ฐฉ์งํ๋๋ก ํ์ดํ๋ผ์ธ์ ์ค์ ํ์ฌ ๊ฐ๋ ฅํ ํ์ง ๊ฒ์ดํธ๋ฅผ ์ค์ ํฉ๋๋ค.
๊ธ๋ก๋ฒ ํ์ ๊ฒฝ์ฐ ์ด๋ฌํ ์๋ํ๋ ํผ๋๋ฐฑ ๋ฃจํ๋ ๋งค์ฐ ์ค์ํ๋ฉฐ, ์ง๋ฆฌ์ ๊ฑฐ๋ฆฌ๋ฅผ ์ฐ๊ฒฐํ๊ณ ๋ชจ๋ ๊ธฐ์ฌ๊ฐ ๋์ผํ ๊ณ ํ์ง ํ์ค์ ์ถฉ์กฑํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
๋์์ธ ์์คํ ์ ์ํ ๊ณ์ฝ ํ ์คํธ
์กฐ์ง์์ ๋์์ธ ์์คํ ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ CSS ๋จ์ ํ ์คํธ๋ ๊ณ์ฝ ์ค์๋ฅผ ๋ณด์ฅํ๋ ๋ฐ ์ค์ํฉ๋๋ค. ๋์์ธ ์์คํ ์ปดํฌ๋ํธ(์: `Button`, `Input`, `Card`)์๋ ์ ์๋ ์์ฑ ๋ฐ ์์ ๋์ ์งํฉ์ด ์์ต๋๋ค. ๋จ์ ํ ์คํธ๋ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ฅํ ๊ณ์ฝ ์ญํ ์ ํ ์ ์์ต๋๋ค.
- `Button size="large"`๊ฐ ํญ์ ํน์ `padding` ๋ฐ `font-size`๋ฅผ ์์ฑํ๋์ง ํ์ธํฉ๋๋ค.
- `Input state="error"`๊ฐ ์ผ๊ด๋๊ฒ ์ฌ๋ฐ๋ฅธ `border-color` ๋ฐ `background-color`๋ฅผ ์ ์ฉํ๋์ง ํ์ธํฉ๋๋ค.
- ๋์์ธ ํ ํฐ(์: `var(--spacing-md)`)์ด ์ต์ข ๊ณ์ฐ๋ CSS์ ํฝ์ ๋๋ rem ๊ฐ์ผ๋ก ์ฌ๋ฐ๋ฅด๊ฒ ๋ณํ๋๋์ง ํ์ธํฉ๋๋ค.
์ด ์ ๊ทผ ๋ฐฉ์์ ๋์์ธ ์์คํ ์ผ๋ก ๊ตฌ์ถ๋ ๋ชจ๋ ์ ํ ์ ๋ฐ์ ๊ฑธ์ณ ์ผ๊ด์ฑ์ ๊ฐ์ ํ๋ฉฐ, ์ด๋ ๋ค์ํ ์์ฅ ์ ๋ฐ์ ๊ฑธ์ณ ๋ธ๋๋ ํตํฉ ๋ฐ ์ฌ์ฉ์ ์ธ์์ ๋งค์ฐ ์ค์ํฉ๋๋ค.
ํจ๊ณผ์ ์ธ CSS ๋จ์ ํ ์คํธ ๋ชจ๋ฒ ์ฌ๋ก
CSS ๋จ์ ํ ์คํธ ๋ ธ๋ ฅ์ ๊ฐ์น๋ฅผ ๊ทน๋ํํ๋ ค๋ฉด ๋ค์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๊ณ ๋ คํ์ญ์์ค.
์๊ณ ์ง์ค๋ ํ ์คํธ ์์ฑ
๊ฐ ํ ์คํธ๋ ์ด์์ ์ผ๋ก CSS ๊ท์น ๋๋ ์์ฑ์ ํน์ ์ธก๋ฉด์ ์ค์ ์ ๋ก๋๋ค. ํ ๋ฒ์ ๊ฑฐ๋ํ ํ ์คํธ์์ ์ปดํฌ๋ํธ์ ๋ชจ๋ ์คํ์ผ์ ์ฃผ์ฅํ๋ ๋์ ๋ถํ ํฉ๋๋ค.
- ๊ธฐ๋ณธ `background-color` ํ ์คํธ.
- ๊ธฐ๋ณธ `font-size` ํ ์คํธ.
- `hover` ์ `background-color` ํ ์คํธ.
- `size="small"`์ผ ๋ `padding` ํ ์คํธ.
์ด๋ ๊ฒ ํ๋ฉด ํ ์คํธ๋ฅผ ์ฝ๊ณ ๋๋ฒ๊ทธํ๊ณ ์ ์ง ๊ด๋ฆฌํ๊ธฐ๊ฐ ์ฌ์์ง๋๋ค. ํ ์คํธ๊ฐ ์คํจํ๋ฉด ์ด๋ค CSS ๊ท์น์ด ์๋ชป๋์๋์ง ์ ํํ ์ ์ ์์ต๋๋ค.
๊ตฌํ ์ธ๋ถ ์ฌํญ์ด ์๋ ๋์ ํ ์คํธ
ํ ์คํธ๋ ๋ด๋ถ ๊ตฌํ์ด ์๋ ์คํ์ผ์ ๊ด์ฐฐ ๊ฐ๋ฅํ ์ถ๋ ฅ ๋ฐ ๋์์ ์ค์ ์ ๋ก๋๋ค. ์๋ฅผ ๋ค์ด, ํน์ CSS ํด๋์ค ์ด๋ฆ์ด ์กด์ฌํ๋์ง ํ ์คํธํ๋ ๋์ (๋ฆฌํฉํฐ๋ง ์ค์ ๋ณ๊ฒฝ๋ ์ ์์), ์์๊ฐ ํด๋น ํด๋์ค์ ์ํด ์ ์ฉ๋ ์คํ์ผ์ ๊ฐ์ง๊ณ ์๋์ง ํ ์คํธํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ํ ์คํธ๊ฐ ๋ ๊ฐ๋ ฅํ๊ณ ๋ฆฌํฉํฐ๋ง์ ๋ ๋ถ์์ ํด์ง๋๋ค.
์ข์: expect(button).toHaveStyle('background-color: blue;')
๋ ์ข์: expect(button).toHaveClass('primary-button-background') (ํด๋์ค ์์ฒด๊ฐ ๊ณต๊ฐ API๊ฐ ์๋ ํ).
์ ์ง ๊ด๋ฆฌ ๊ฐ๋ฅํ ํ ์คํธ ์ ํ๊ตฐ
ํ๋ก์ ํธ๊ฐ ์ฑ์ฅํจ์ ๋ฐ๋ผ ํ ์คํธ ์ ํ๊ตฐ๋ ์ฑ์ฅํฉ๋๋ค. ํ ์คํธ๊ฐ ๋ค์์ ๋ณด์ฅํ๋๋ก ํฉ๋๋ค.
- ๊ฐ๋ ์ฑ: ๋ช ํํ๊ณ ์ค๋ช ์ ์ธ ํ ์คํธ ์ด๋ฆ ์ฌ์ฉ (์: "Test 1" ๋์ "Button์ด ๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ์์ผ๋ก ๋ ๋๋ง๋ฉ๋๋ค").
- ๊ตฌ์ฑ: `describe` ๋ธ๋ก์ ์ฌ์ฉํ์ฌ ๊ด๋ จ ํ ์คํธ ๊ทธ๋ฃนํ.
- DRY(๋ฐ๋ณต ๊ธ์ง): `beforeEach` ๋ฐ `afterEach` ํํฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ผ๋ฐ์ ์ธ ํ ์คํธ ์กฐ๊ฑด ์ค์ ๋ฐ ํด์ .
์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก ํ ์คํธ ์ฝ๋๋ ์ ๊ธฐ์ ์ผ๋ก ๊ฒํ ํ๊ณ ๋ฆฌํฉํฐ๋งํฉ๋๋ค. ์ค๋๋๊ฑฐ๋ ๋ถ์์ ํ ํ ์คํธ๋ ์ ๋ขฐ๋ฅผ ๊ฐ์์ํค๊ณ ๊ฐ๋ฐ์ ๋ฆ์ถฅ๋๋ค.
ํ ๊ฐ ํ์ (๋์์ด๋, ๊ฐ๋ฐ์, QA)
CSS ๋จ์ ํ ์คํธ๋ ๊ฐ๋ฐ์๋ฅผ ์ํ ๊ฒ๋ง์ ์๋๋๋ค. ๋ชจ๋ ์ดํด ๊ด๊ณ์๋ฅผ ์ํ ๊ณตํต ์ฐธ์กฐ ์ง์ ์ญํ ์ ํ ์ ์์ต๋๋ค.
- ๋์์ด๋: ํ ์คํธ ์ค๋ช ์ ๊ฒํ ํ์ฌ ๋์์ธ ์ฌ์๊ณผ ์ผ์นํ๋์ง ํ์ธํ๊ฑฐ๋ ํ ์คํธ ์ฌ๋ก์ ๊ธฐ์ฌํ ์ ์์ต๋๋ค.
- QA ์์ง๋์ด: ํ ์คํธ๋ฅผ ์ฌ์ฉํ์ฌ ์์๋๋ ๋์์ ์ดํดํ๊ณ ์๋ ํ ์คํธ๋ฅผ ๋ ๋ณต์กํ ํตํฉ ์๋๋ฆฌ์ค์ ์ง์คํ ์ ์์ต๋๋ค.
- ๊ฐ๋ฐ์: ๋ณ๊ฒฝ์ ๋ํ ์์ ๊ฐ์ ์ป๊ณ ์ ํํ ์คํ์ผ๋ง ์๊ตฌ ์ฌํญ์ ์ดํดํฉ๋๋ค.
์ด ํ์ ์ ์ ๊ทผ ๋ฐฉ์์ ํ์ง ๋ฌธํ์ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ํ ๊ณต์ ์ฑ ์์ ์ก์ฑํ๋ฉฐ, ์ด๋ ๋ถ์ฐ๋ ๊ธ๋ก๋ฒ ํ์๊ฒ ํนํ ์ ์ตํฉ๋๋ค.
์ง์์ ์ธ ๊ฐ์ ๋ฐ ๊ฐ์
์น์ ๋์์์ด ์งํํ๊ณ ์์ผ๋ฉฐ, ํ ์คํธ ์ ๋ต๋ ๋ง์ฐฌ๊ฐ์ง์ ๋๋ค. CSS ๋จ์ ํ ์คํธ๋ฅผ ์ ๊ธฐ์ ์ผ๋ก ๊ฒํ ํฉ๋๋ค.
- ๊ด๋ จ์ฑ์ด ์์ต๋๊น?
- ์ค์ ๋ฒ๊ทธ๋ฅผ ํฌ์ฐฉํ๊ณ ์์ต๋๊น?
- ํ ์คํธํด์ผ ํ๋ ์๋ก์ด ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฅ ๋๋ CSS ์์ฑ์ด ์์ต๋๊น?
- ์๋ก์ด ๋๊ตฌ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ ์คํธ ํจ์จ์ฑ์ ํฅ์์ํฌ ์ ์์ต๋๊น?
ํจ๊ณผ์ ์ผ๋ก ์ ์งํ๊ธฐ ์ํด ๊ด๋ฆฌ์ ์ฃผ์๊ฐ ํ์ํ ์ฝ๋๋ฒ ์ด์ค์ ์ด์์๋ ๋ถ๋ถ์ผ๋ก ํ ์คํธ ์ ํ๊ตฐ์ ๋ํ์ญ์์ค.
๊ฒฌ๊ณ ํ CSS ํ ์คํธ์ ๊ธ๋ก๋ฒ ์ํฅ
์ธ์ฌํ CSS ๋จ์ ํ ์คํธ ์ ๊ทผ ๋ฐฉ์์ ์ฑํํ๋ฉด ํนํ ๊ธ๋ก๋ฒ ๊ท๋ชจ๋ก ์ด์๋๋ ์กฐ์ง์ ๊ด๋ฒ์ํ ๊ธ์ ์ ์ธ ์ํฅ์ด ์์ต๋๋ค.
์ ์ธ๊ณ์ ์ผ๋ก ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ ๋ณด์ฅ
๊ตญ์ ๋ธ๋๋์ ๊ฒฝ์ฐ ์ผ๊ด์ฑ์ด ํต์ฌ์ ๋๋ค. ํ ๊ตญ๊ฐ์ ์ฌ์ฉ์๋ ๋ค๋ฅธ ๊ตญ๊ฐ์ ์ฌ์ฉ์์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ฅ์น, ๋ธ๋ผ์ฐ์ ๋๋ ์ง์ญ ์ค์ ์ ๊ด๊ณ์์ด ๋์ผํ ๊ณ ํ์ง ์ธํฐํ์ด์ค๋ฅผ ๊ฒฝํํด์ผ ํฉ๋๋ค. CSS ๋จ์ ํ ์คํธ๋ ํต์ฌ UI ์์๊ฐ ์ด๋ฌํ ๋ณ์ ์ ๋ฐ์ ๊ฑธ์ณ ์๋ํ ๋ชจ์๊ณผ ๋์์ ์ ์งํ๋๋ก ๋ณด์ฅํ๋ ๊ธฐ๋ณธ ์์ค์ ๋ณด์ฆ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ธ๋๋ ํฌ์์ด ์ค์ด๋ค๊ณ ์ ์ธ๊ณ์ ์ผ๋ก ์ ๋ขฐ๊ฐ ์กฐ์ฑ๋ฉ๋๋ค.
๊ธฐ์ ๋ถ์ฑ ๋ฐ ์ ์ง ๊ด๋ฆฌ ๋น์ฉ ์ ๊ฐ
๋ฒ๊ทธ, ํนํ ์๊ฐ์ ๋ฒ๊ทธ๋ ๊ฐ๋ฐ ์ฃผ๊ธฐ ํ๋ฐ์ด๋ ๋ฐฐํฌ ํ์ ๋ฐ๊ฒฌ๋ ๊ฒฝ์ฐ ์์ ํ๋ ๋ฐ ๋น์ฉ์ด ๋ง์ด ๋ค ์ ์์ต๋๋ค. ๊ธ๋ก๋ฒ ํ๋ก์ ํธ์ ๊ฒฝ์ฐ ์ฌ๋ฌ ์ง์ญ, ํ ์คํธ ํ๊ฒฝ ๋ฐ ๋ฆด๋ฆฌ์ค ์ฃผ๊ธฐ์ ๊ฑธ์ณ ๋ฒ๊ทธ๋ฅผ ์์ ํ๋ ๋น์ฉ์ ๋น ๋ฅด๊ฒ ์ฆ๊ฐํ ์ ์์ต๋๋ค. ๋จ์ ํ ์คํธ๋ฅผ ํตํด CSS ํ๊ท๋ฅผ ์กฐ๊ธฐ์ ํฌ์ฐฉํจ์ผ๋ก์จ ํ์ ๊ธฐ์ ๋ถ์ฑ๋ฅผ ํฌ๊ฒ ์ค์ด๊ณ ์ฌ์์ ์ ์ต์ํํ๋ฉฐ ์ ์ฒด ์ ์ง ๊ด๋ฆฌ ๋น์ฉ์ ์ ๊ฐํ ์ ์์ต๋๋ค. ์ด๋ฌํ ํจ์จ์ฑ ํฅ์์ ๋๊ท๋ชจ์ ๋ค์ํ ์ฝ๋๋ฒ ์ด์ค์ ์๋ง์ ์ ํ ์ ๊ณต ์ ๋ฐ์ ๊ฑธ์ณ ๋ฐฐ๊ฐ๋ฉ๋๋ค.
๊ฐ๋ฐ ํ์ ๋ฐ ์์ ๊ฐ ์กฐ์ฑ
๊ฐ๋ฐ์๊ฐ ๊ฐ๋ ฅํ ์๋ํ ํ ์คํธ ์์ ๋ง์ ๊ฐ์ถ๋ฉด ๋๋ดํ ๋ณ๊ฒฝ์ ํ๊ฑฐ๋ ์ ๊ธฐ๋ฅ์ ์คํํ๊ฑฐ๋ ๊ธฐ์กด ์ฝ๋๋ฅผ ๋ฆฌํฉํฐ๋งํ๋ ๋ฐ ๋ ์์ ๊ฐ์ ๊ฐ๊ฒ ๋ฉ๋๋ค. ํ๋ฐํธ์๋ ๊ฐ๋ฐ์์ ์ข ์ข ํ์ ์ ์ ํดํ๋ ์๋ํ์ง ์์ ์๊ฐ์ ํ๊ท๋ฅผ ๋์ ํ๋ ๊ฒ์ ๋ํ ๋๋ ค์์ด ํฌ๊ฒ ์ค์ด๋ญ๋๋ค. ์ด๋ฌํ ์์ ๊ฐ์ ํ์ด ๋ ๋น ๋ฅด๊ฒ ๋ฐ๋ณตํ๊ณ ์ฐฝ์์ ์ธ ์๋ฃจ์ ์ ํ์ํ๋ฉฐ ํ์ง์ ์์์ํค์ง ์๊ณ ํ์ ์ ์ธ ๊ธฐ๋ฅ์ ์ ๊ณตํ ์ ์๋๋ก ํ์ฌ ๊ธ๋ก๋ฒ ์์ฅ์์ ์ ํ ๊ฒฝ์๋ ฅ์ ์ ์งํฉ๋๋ค.
๋ชจ๋ ์ฌ์ฉ์๋ฅผ ์ํ ์ ๊ทผ์ฑ
์ง์ ์ผ๋ก ๊ธ๋ก๋ฒํ ์ ํ์ ์ ๊ทผ ๊ฐ๋ฅํ ์ ํ์ ๋๋ค. CSS๋ ์๊ฐ ์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์๋ฅผ ์ํ ์ถฉ๋ถํ ์์ ๋๋น ๋ณด์ฅ, ํค๋ณด๋ ํ์๊ฐ๋ฅผ ์ํ ๋ช ํํ ํฌ์ปค์ค ํ์๊ธฐ ์ ๊ณต, ๋ค์ํ ํ๋ฉด ํฌ๊ธฐ ๋ฐ ํ ์คํธ ํฌ๊ธฐ ๊ธฐ๋ณธ ์ค์ ์ ๊ฑธ์ณ ๊ฐ๋ ์ฑ ์๋ ๋ ์ด์์ ์ ์ง์ ์ด๋ฅด๊ธฐ๊น์ง ์ ๊ทผ์ฑ์ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ์ด๋ฌํ ์ค์ํ CSS ์์ฑ์ ๋จ์ ํ ์คํธํจ์ผ๋ก์จ ์กฐ์ง์ ์ ๊ทผ์ฑ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๊ฐ๋ฐ ์ํฌํ๋ก์ ์ฒด๊ณ์ ์ผ๋ก ๋ด์ฅํ์ฌ ์น ์ ํ์ด ์ด๋์๋ ๋ชจ๋ ์ฌ๋์ด ์ฌ์ฉํ ์ ์๊ณ ํฌ์ฉ์ ์์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
๊ฒฐ๋ก : CSS ๋จ์ ํ ์คํธ๋ก ํ๋ฐํธ์๋ ํ์ง ํฅ์
์๋ ์๊ฐ์ ํ์ธ์์ ์ ๊ตํ๊ณ ์๋ํ๋ CSS ๋จ์ ํ ์คํธ๋ก์ ์ ํ์ ํ๋ฐํธ์๋ ๊ฐ๋ฐ์ ์ค์ํ ๋ฐ์ ์ ๋ํ๋ ๋๋ค. "CSS ํ ์คํธ ๊ท์น" ํจ๋ฌ๋ค์, ์ฆ ๊ฐ๋ณ CSS ์์ฑ ๋ฐ ์ปดํฌ๋ํธ ์คํ์ผ์ ๊ฒฉ๋ฆฌํ๊ณ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์ฃผ์ฅํ๋ ์๋์ ์ธ ์ค์ฒ์ ๋ ์ด์ ํ์ ๊ฐ๋ ์ด ์๋๋ผ ๊ฒฌ๊ณ ํ๊ณ ์ ์ง ๊ด๋ฆฌ ๊ฐ๋ฅํ๋ฉฐ ์ ์ธ๊ณ์ ์ผ๋ก ์ผ๊ด๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๊ธฐ ์ํ ํ์์ ์ธ ์ ๋ต์ ๋๋ค.
๊ฐ๋ ฅํ ํ ์คํธ ํ๋ ์์ํฌ๋ฅผ ํ์ฉํ๊ณ , ์ต์ ๋น๋ ์์คํ ๊ณผ ํตํฉํ๊ณ , ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ค์ํจ์ผ๋ก์จ ๊ฐ๋ฐ ํ์ ์คํ์ผ๋ง์ ์ ๊ทผํ๋ ๋ฐฉ์์ ๋ณํ์ํฌ ์ ์์ต๋๋ค. ๊ทธ๋ค์ ๋ํ๋๋ ๋๋ก ์๊ฐ์ ๋ฒ๊ทธ๋ฅผ ์์ ํ๋ ์๋์ ์ธ ์ ์ฅ์์ ๊ทธ๊ฒ๋ค์ด ๋ฐ์ํ๋ ๊ฒ์ ๋ฐฉ์งํ๋ ๋ฅ๋์ ์ธ ์ ์ฅ์์ ๋์๊ฐ๋๋ค.
CSS ํ ์คํธ์ ๋ฏธ๋
CSS๊ฐ ์ปจํ ์ด๋ ์ฟผ๋ฆฌ, `has()` ์ ํ๊ธฐ ๋ฐ ๊ณ ๊ธ ๋ ์ด์์ ๋ชจ๋๊ณผ ๊ฐ์ ์๋ก์ด ๊ธฐ๋ฅ์ผ๋ก ๊ณ์ ๋ฐ์ ํจ์ ๋ฐ๋ผ ๊ฐ๋ ฅํ ํ ์คํธ์ ๋ํ ํ์์ฑ์ ๋์ฑ ์ปค์ง ๊ฒ์ ๋๋ค. ๋ฏธ๋์ ๋๊ตฌ์ ๋ฐฉ๋ฒ๋ก ์ ์ด๋ฌํ ๋ณต์กํ ์ํธ ์์ฉ ๋ฐ ๋ฐ์ํ ๋์์ ํ ์คํธํ๋ ํจ์ฌ ๋ ์ํํ ๋ฐฉ๋ฒ์ ์ ๊ณตํ์ฌ CSS ๋จ์ ํ ์คํธ๋ฅผ ํ๋ฐํธ์๋ ๊ฐ๋ฐ ๋ผ์ดํ์ฌ์ดํด์ ํ์์ ์ธ ๋ถ๋ถ์ผ๋ก ๋์ฑ ๊น์ด ํตํฉํ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
CSS ๋จ์ ํ ์คํธ๋ฅผ ์ฑํํ๋ ๊ฒ์ ํ์ง, ํจ์จ์ฑ ๋ฐ ์์ ๊ฐ์ ๋ํ ํฌ์์ ๋๋ค. ๊ธ๋ก๋ฒ ํ์๊ฒ ์ด๋ ์ผ๊ด๋๊ฒ ๋ฐ์ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๊ณ , ๊ฐ๋ฐ ๋ง์ฐฐ์ ์ค์ด๋ฉฐ, ๋ชจ๋ ํฝ์ ๊ณผ ๋ชจ๋ ์คํ์ผ ๊ท์น์ด ์ ํ์ ์ ๋ฐ์ ์ธ ์ฑ๊ณต์ ๊ธ์ ์ ์ผ๋ก ๊ธฐ์ฌํ๋๋ก ๋ณด์ฅํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์ง๊ธ CSS ๋จ์ ํ ์คํธ๋ฅผ ๊ตฌํํ์ฌ ํ๋ฐํธ์๋ ํ์ง์ ๋์ด๊ณ ํ ์คํธ๊ฐ ํ๋ก์ ํธ์ ๊ฐ์ ธ์ค๋ ํ์ง ๋ฐ ์์ ๊ฐ์ ์ฐจ์ด๋ฅผ ๊ฒฝํํ์ญ์์ค.