๐Ÿ–ฑ๏ธ Tailwind 6์žฅ: ์ƒํƒœ ๊ธฐ๋ฐ˜ ์Šคํƒ€์ผ๋ง

2026๋…„ 3์›” 5์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

Tailwind variant ์‹œ์Šคํ…œ ์™„์ „ ์ •๋ณต โ€” ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜์— ๋ฐ˜์‘ํ•˜๋Š” UI ๋งŒ๋“ค๊ธฐ

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 18๋ถ„

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • hover:, focus:, active:, disabled: ๊ฐ™์€ ๊ธฐ๋ณธ ์ƒํƒœ variant ๋ฅผ ์ž์œ ์ž์žฌ๋กœ ์“ธ ์ˆ˜ ์žˆ๋‹ค
  • group / group-hover: ๋กœ ๋ถ€๋ชจ hover ์‹œ ์ž์‹ ์Šคํƒ€์ผ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค
  • peer / peer-invalid: ๋กœ ํ˜•์ œ ์š”์†Œ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์Šคํƒ€์ผ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ๐ŸŽจ ์˜์ˆ™ (๋””์ž์ด๋„ˆ): "์˜์ฒ  ๋‹˜, ์Šคํ„ฐ๋”” ์นด๋“œ์— hover ํ•˜๋ฉด ์นด๋“œ ์ „์ฒด๊ฐ€ ์‚ด์ง ์˜ฌ๋ผ์˜ค๊ณ , ์นด๋“œ ์ œ๋ชฉ ์ƒ‰์ด ํŒŒ๋ž—๊ฒŒ ๋ณ€ํ•˜๋ฉด ์ข‹๊ฒ ์–ด์š”. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์•ˆ์— ๋ฒ„ํŠผ๋„ hover ์‹œ ์ƒ‰์ด ๋‹ฌ๋ผ์ ธ์•ผ ํ•˜๊ณ ์š”."
  • ๐Ÿฃ ์˜์ฒ : "์–ด... JS ๋กœ ์ƒํƒœ ๊ด€๋ฆฌํ•ด์„œ ํด๋ž˜์Šค๋ฅผ ๋™์ ์œผ๋กœ ๊ต์ฒดํ•ด์•ผ ํ•˜๋‚˜์š”?"
  • ๐Ÿฆ ์˜ํ˜ธ (๋ฆฌ๋“œ): "๊ทธ๋Ÿด ํ•„์š” ์—†์–ด์š”. Tailwind ์˜ group ๊ณผ group-hover: ๋ฅผ ์“ฐ๋ฉด JS ์—†์ด CSS ๋งŒ์œผ๋กœ ํ•ด๊ฒฐ๋ผ์š”."
  • ๐Ÿฃ ์˜์ฒ : "JS ์—†์ด์š”?!"

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•œ UI ๋Š” ๋‹จ์ˆœํžˆ "์ด์œ UI" ๊ฐ€ ์•„๋‹ˆ์•ผ. ์‚ฌ์šฉ์ž์—๊ฒŒ "์ด ์š”์†Œ๋ฅผ ํด๋ฆญํ•  ์ˆ˜ ์žˆ๋‹ค", "์ง€๊ธˆ ํฌ์ปค์Šค๊ฐ€ ์—ฌ๊ธฐ ์žˆ๋‹ค" ๋Š” ํ”ผ๋“œ๋ฐฑ์„ ์ค˜์„œ ์ ‘๊ทผ์„ฑ๊ณผ UX ๋ฅผ ๋†’์ด๋Š” ํ•ต์‹ฌ ์š”์†Œ์•ผ.

์ „ํ†ต CSS ์—์„œ๋Š” ์ด๊ฑธ ์œ„ํ•ด ๋ณ„๋„ CSS ํŒŒ์ผ์— :hover, :focus ๊ทœ์น™์„ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜, JS ๋กœ ํด๋ž˜์Šค๋ฅผ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€/์ œ๊ฑฐํ–ˆ์–ด. ํ•˜์ง€๋งŒ Tailwind ์˜ variant ์‹œ์Šคํ…œ์„ ์“ฐ๋ฉด HTML ํด๋ž˜์Šค๋งŒ์œผ๋กœ ์™„์„ฑ๋ผ.


๐Ÿงฉ Variant ์‹œ์Šคํ…œ ๊ฐœ์š”

Tailwind ์˜ ๋ชจ๋“  ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋Š” ์•ž์— variant ๋ฅผ ๋ถ™์—ฌ์„œ ์กฐ๊ฑด๋ถ€๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

{variant}:{utility}

์˜ˆ:
hover:bg-blue-700      โ†’ :hover ์ƒํƒœ์—์„œ bg-blue-700 ์ ์šฉ
focus:ring-2           โ†’ :focus ์ƒํƒœ์—์„œ ring-2 ์ ์šฉ
md:flex                โ†’ 768px ์ด์ƒ์—์„œ flex ์ ์šฉ
dark:bg-gray-800       โ†’ ๋‹คํฌ ๋ชจ๋“œ์—์„œ bg-gray-800 ์ ์šฉ

๐Ÿ–ฑ๏ธ ๊ธฐ๋ณธ ์ƒํƒœ Variant (hover, focus, active)

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • hover:, focus:, active:, disabled: ๊ฐ๊ฐ์˜ ์ •ํ™•ํ•œ ํŠธ๋ฆฌ๊ฑฐ ํƒ€์ด๋ฐ์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ์ ‘๊ทผ์„ฑ์„ ํ•ด์น˜์ง€ ์•Š๋Š” focus ์Šคํƒ€์ผ๋ง ํŒจํ„ด์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค
  • first:, last:, odd:, even: ๊ฐ™์€ ๊ตฌ์กฐ์  Variant ๋กœ ๋ฐ˜๋ณต UI ๋ฅผ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿ–ฑ๏ธ Hover โ€” ๋งˆ์šฐ์Šค๊ฐ€ ์˜ฌ๋ผ์™”์„ ๋•Œ

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์Šคํ„ฐ๋”” ๋ชฉ๋ก์„ ๋งŒ๋“ค๋˜ ์˜์ฒ ์ด๊ฐ€ ์ฒ˜์Œ ๋ถ€๋”ชํžŒ ์ƒํƒœ ์Šคํƒ€์ผ๋ง์ด ๋ฐ”๋กœ hover ์•ผ. ๋ฒ„ํŠผ์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ธ์„ ๋•Œ ์ƒ‰์ด ๋ฐ”๋€Œ๊ณ , ์นด๋“œ์— ์˜ฌ๋ ธ์„ ๋•Œ ๊ทธ๋ฆผ์ž๊ฐ€ ์ง„ํ•ด์ง€๋Š” ๊ทธ ํšจ๊ณผ ๋ง์ด์•ผ.

๐Ÿฆ ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์˜ ๋ง: "hover ๋Š” :hover ์˜์‚ฌ ํด๋ž˜์Šค๋ฅผ Tailwind ๊ฐ€ ํด๋ž˜์Šค๋กœ ์ถ”์ƒํ™”ํ•œ ๊ฒƒ๋ฟ์ด์•ผ. hover:bg-blue-700 ์€ CSS ๋กœ .hover\:bg-blue-700:hover { background-color: ... } ๋ฅผ ์ƒ์„ฑํ•ด์š”. ์›๋ฆฌ๋ฅผ ์•Œ๋ฉด ์‘์šฉ์ด ์‰ฌ์›Œ์ ธ์š”."

<!-- ๐Ÿฃ ์˜์ฒ  1์•ˆ: JS ๋กœ ํด๋ž˜์Šค ํ† ๊ธ€ํ•ด์„œ hover ๊ตฌํ˜„ โ€” ๋ณต์žกํ•˜๊ณ  ๋ถˆํ•„์š” -->
<button id="btn">ํด๋ฆญ</button>
<script>
  btn.addEventListener('mouseover', () => btn.classList.add('bg-blue-700'))
  btn.addEventListener('mouseout', () => btn.classList.remove('bg-blue-700'))
</script>
 
<!-- ๐Ÿฆ ์˜ํ˜ธ ๋ฆฌํŒฉํ† ๋ง: Tailwind variant ํ•˜๋‚˜๋กœ ๋ -->
<button class="bg-blue-600 hover:bg-blue-700 transition-colors">ํด๋ฆญ</button>

hover: ๋Š” ๋‹จ์ˆœํ•œ ์ƒ‰์ƒ ๋ณ€๊ฒฝ์—๋งŒ ์“ฐ์ด๋Š” ๊ฒŒ ์•„๋‹ˆ์•ผ. transform, shadow, opacity ๋“ฑ ์–ด๋–ค ์œ ํ‹ธ๋ฆฌํ‹ฐ๋“  ์•ž์— hover: ๋ฅผ ๋ถ™์ด๋ฉด hover ์‹œ์—๋งŒ ์ ์šฉ๋ผ.

<!-- ์นด๋“œ hover ์‹œ ๊ทธ๋ฆผ์ž ๊ฐ•์กฐ + ์‚ด์ง ์œ„๋กœ ์ด๋™ -->
<div class="shadow-md transition-all hover:-translate-y-1 hover:shadow-xl">์นด๋“œ</div>
 
<!-- ๋งํฌ hover ์‹œ ๋ฐ‘์ค„ (๊ธฐ๋ณธ ๋ฐ‘์ค„ ์ œ๊ฑฐ ํ›„ hover ์‹œ๋งŒ ํ‘œ์‹œ) -->
<a class="no-underline hover:underline">๋งํฌ</a>

๐ŸŽฏ Focus โ€” ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค์™€ ์ ‘๊ทผ์„ฑ์˜ ๊ท ํ˜•

ํฌ์ปค์Šค ์Šคํƒ€์ผ์€ ๋‹จ์ˆœํ•œ ๋ฏธ์  ์š”์†Œ๊ฐ€ ์•„๋‹ˆ์•ผ. Tab ํ‚ค๋กœ ํŽ˜์ด์ง€๋ฅผ ํƒ์ƒ‰ํ•˜๋Š” ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž ์™€ ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์‚ฌ์šฉ์ž ์—๊ฒŒ "์ง€๊ธˆ ํฌ์ปค์Šค๊ฐ€ ์—ฌ๊ธฐ ์žˆ์–ด์š”" ๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ์ ‘๊ทผ์„ฑ์˜ ํ•ต์‹ฌ ์ด์•ผ.

์˜์ˆ™์ด ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ค‘์— ์ด๋Ÿฐ ๋ง์„ ํ–ˆ์–ด:

๐ŸŽจ ์˜์ˆ™ ๋‹˜: "์˜์ฒ  ๋‹˜, ๋กœ๊ทธ์ธ ํผ์—์„œ Tab ํ‚ค๋กœ ์ด๋™ํ•˜๋ฉด ์–ด๋””์— ํฌ์ปค์Šค๊ฐ€ ์žˆ๋Š”์ง€๊ฐ€ ์•ˆ ๋ณด์—ฌ์š”. ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์“ฐ๋Š” ๋ถ„๋“ค์€ ์–ด๋–ป๊ฒŒ ํ•˜๋ผ๊ณ ์š”?"

๐Ÿฃ ์˜์ฒ : "์•„, outline: none ์ผ๋”๋‹ˆ ํŒŒ๋ž€ ํ…Œ๋‘๋ฆฌ๊ฐ€ ๋„ˆ๋ฌด ๋ชป์ƒ๊ฒจ์„œ..."

๐Ÿฆ ์˜ํ˜ธ: "๊ทธ๊ฑฐ ์ ‘๊ทผ์„ฑ ์œ„๋ฐ˜์ด์—์š”. focus:outline-none ๋งŒ ์“ฐ๋ฉด WCAG 2.1 ๊ธฐ์ค€ ๋ฏธ๋‹ฌ์ด์•ผ. ๋Œ€์‹  focus-visible: ์„ ์จ์š”."

<!-- โŒ ์ˆœ์ง„ํ•œ ์ฝ”๋“œ: outline ์ œ๊ฑฐ๋งŒ ํ•˜๊ณ  ๋Œ€์•ˆ ์—†์Œ โ†’ ์ ‘๊ทผ์„ฑ ์œ„๋ฐ˜ -->
<button class="focus:outline-none">๋ฒ„ํŠผ</button>
 
<!-- โœ… ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ: outline ์ œ๊ฑฐ + ring ์œผ๋กœ ๋Œ€์•ˆ ํฌ์ปค์Šค ์Šคํƒ€์ผ ์ œ๊ณต -->
<input class="
  rounded-lg border border-gray-300 px-4 py-2
  focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20
" />
 
<!-- ๐Ÿ’ก ๋” ์ •๊ตํ•˜๊ฒŒ: focus-visible ๋กœ ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค๋งŒ ์Šคํƒ€์ผ๋ง -->
<!-- focus-visible: = ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค์ผ ๋•Œ๋งŒ (๋งˆ์šฐ์Šค ํด๋ฆญ ์‹œ์—๋Š” ์ ์šฉ ์•ˆ ๋จ) -->
<button class="focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2">
  ๋ฒ„ํŠผ
</button>

โš ๏ธ ์ ‘๊ทผ์„ฑ ํ™ฉ๊ธˆ ๊ณต์‹: focus:outline-none ์€ ๋ฐ˜๋“œ์‹œ focus-visible:ring-* ์™€ ์ง์„ ์ด๋ค„์•ผ ํ•œ๋‹ค. ํ˜ผ์ž ์“ฐ๋ฉด ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž๊ฐ€ ํฌ์ปค์Šค๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํŒŒ์•…ํ•  ์ˆ˜ ์—†์–ด WCAG 2.1 2.4.7 ์œ„๋ฐ˜ ์ด์•ผ.


๐Ÿ‘† Active โ€” ํด๋ฆญํ•˜๋Š” ๊ทธ ์ฐฐ๋‚˜์˜ ์ˆœ๊ฐ„

active: ๋Š” ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ์žˆ๋Š” ๋™์•ˆ ๋งŒ ์ ์šฉ๋ผ. ํด๋ฆญ ์• ๋‹ˆ๋ฉ”์ด์…˜์— ์“ฐ๋ฉด ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ๋А๋‚Œ์„ ์ค„ ์ˆ˜ ์žˆ์–ด.

๐ŸŽจ ์˜์ˆ™ ๋‹˜: "๋ฒ„ํŠผ ํด๋ฆญํ•  ๋•Œ ์‚ด์ง ๋ˆŒ๋ฆฌ๋Š” ๋А๋‚Œ ์žˆ์œผ๋ฉด ์ข‹๊ฒ ์–ด์š”. ์š”์ฆ˜ ์•ฑ๋“ค์ฒ˜๋Ÿผ์š”."

<!-- ํด๋ฆญ ์‹œ ์‚ด์ง ์ถ•์†Œ + ์ƒ‰ ์ง„ํ•ด์ง€๋Š” ํ”ผ์ง€์ปฌ ๋А๋‚Œ -->
<button class="
  transform bg-blue-600 text-white px-6 py-3 rounded-xl
  transition-transform duration-75
  active:scale-95 active:bg-blue-800
">
  ์Šคํ„ฐ๋”” ์ฐธ์—ฌํ•˜๊ธฐ
</button>

transition-transform duration-75 ๋Š” 75ms ์งง์€ ์ „ํ™˜ ์‹œ๊ฐ„ โ€” ๋„ˆ๋ฌด ๊ธธ๋ฉด ํด๋ฆญ ๋ฐ˜์‘์ด ๋А๋ ค ๋ณด์—ฌ์„œ duration-75 ~ duration-150 ์ •๋„๊ฐ€ ์ ๋‹นํ•ด.


๐Ÿšซ Disabled โ€” ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ์˜ UX

๋น„ํ™œ์„ฑํ™”๋œ ๋ฒ„ํŠผ์€ "์ง€๊ธˆ์€ ๋ชป ๋ˆŒ๋Ÿฌ" ๋ผ๋Š” ์‹ ํ˜ธ์•ผ. ์˜์ฒ ์ด๊ฐ€ ์Šคํ„ฐ๋”” ๋งˆ๊ฐ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค ๋•Œ ์ด ํŒจํ„ด์„ ์ผ์–ด.

<!-- โœ… disabled ์†์„ฑ + Tailwind disabled: variant ์กฐํ•ฉ -->
<button class="
  bg-blue-600 text-white px-6 py-3 rounded-xl
  disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-400
  transition-colors
" disabled>
  ๋งˆ๊ฐ๋œ ์Šคํ„ฐ๋””
</button>

๐Ÿ’ก disabled: ๋Š” HTML disabled ์†์„ฑ์ด ์žˆ์„ ๋•Œ๋งŒ ๋™์ž‘ํ•ด. CSS ์—์„œ :disabled ์˜์‚ฌ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•ด. React ์—์„œ๋Š” <button disabled={isDisabled}> ์ฒ˜๋Ÿผ prop ์œผ๋กœ ์ œ์–ดํ•˜๋ฉด ๋ผ.


๐Ÿ”ข ๊ตฌ์กฐ์  Variant โ€” first, last, odd, even

์Šคํ„ฐ๋”” ๋ชฉ๋ก์ฒ˜๋Ÿผ ๋™์ผํ•œ ํ•ญ๋ชฉ์ด ๋ฐ˜๋ณต๋  ๋•Œ, ๊ฐ ํ•ญ๋ชฉ์— ๊ฐœ๋ณ„ ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ  ๊ตฌ์กฐ์  ์œ„์น˜ ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์Šคํƒ€์ผ๋งํ•  ์ˆ˜ ์žˆ์–ด.

์˜์ฒ ์ด๊ฐ€ ์ฒ˜์Œ์— ์ด๋Ÿฐ ์ฝ”๋“œ๋ฅผ ์ผ์–ด:

// ๐Ÿฃ ์˜์ฒ  1์•ˆ: ์ฒซ ๋ฒˆ์งธ ํ•ญ๋ชฉ์€ pt-0, ๋งˆ์ง€๋ง‰์€ border-b ์—†์• ์•ผ ํ•ด์„œ index ๋กœ ์กฐ๊ฑด ์ฒ˜๋ฆฌ
{studies.map((study, index) => (
  <li
    key={study.id}
    className={`py-3 border-b border-gray-200 ${index === 0 ? 'pt-0' : ''} ${index === studies.length - 1 ? 'border-b-0' : ''}`}
  >
    {study.title}
  </li>
))}

๐Ÿฆ ์˜ํ˜ธ: "์˜์ฒ  ๋‹˜, first: ๋ž‘ last: ์“ฐ๋ฉด index ๊ณ„์‚ฐ ํ•„์š” ์—†์–ด์š”. Tailwind ๊ฐ€ CSS ์˜ :first-child, :last-child ๋ฅผ variant ๋กœ ์ถ”์ƒํ™”ํ•œ ๊ฑฐ๊ฑฐ๋“ ์š”."

// ๐Ÿฆ ์˜ํ˜ธ ๋ฆฌํŒฉํ† ๋ง: ๊ตฌ์กฐ์  Variant ๋กœ ๊น”๋”ํ•˜๊ฒŒ
{studies.map((study) => (
  <li
    key={study.id}
    className="border-b border-gray-200 py-3 first:pt-0 last:border-b-0"
  >
    {study.title}
  </li>
))}

odd: / even: ์€ ํ…Œ์ด๋ธ”์ด๋‚˜ ๋ชฉ๋ก์—์„œ ์ค„๋ฌด๋Šฌ(zebra stripe) ํŒจํ„ด์„ ๋งŒ๋“ค ๋•Œ ์œ ์šฉํ•ด:

<!-- ํ…Œ์ด๋ธ” ํ–‰ ์ค„๋ฌด๋Šฌ: ํ™€์ˆ˜ ํ–‰์€ ํฐ์ƒ‰, ์ง์ˆ˜ ํ–‰์€ ํšŒ์ƒ‰ -->
<tr class="odd:bg-white even:bg-gray-50">ํ–‰</tr>
 
<!-- placeholder ์ƒ‰์ƒ ์ œ์–ด -->
<input class="placeholder:text-gray-400 focus:placeholder:text-gray-300" />
 
<!-- ์ฒดํฌ๋œ ์ƒํƒœ -->
<input type="checkbox" class="checked:bg-blue-600 checked:border-blue-600" />

๐Ÿ‘ช Group Variant: ๋ถ€๋ชจ ์ƒํƒœ๋กœ ์ž์‹ ์ œ์–ด

group ์„ ๋ถ€๋ชจ์— ๋ถ™์ด๋ฉด, ๊ทธ ์ž์‹๋“ค์ด ๋ถ€๋ชจ์˜ ์ƒํƒœ๋ฅผ ๊ฐ์ง€ํ•ด์„œ group-hover:, group-focus: ๋“ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

<!-- ๋ถ€๋ชจ์— group ๋ถ™์ด๊ธฐ -->
<div class="group">
  <h3 class="text-gray-900 group-hover:text-blue-600">
    ์นด๋“œ ์ œ๋ชฉ (๋ถ€๋ชจ hover ์‹œ ํŒŒ๋ž€์ƒ‰์œผ๋กœ)
  </h3>
  <p class="text-gray-500 group-hover:text-gray-700">
    ์„ค๋ช… (๋ถ€๋ชจ hover ์‹œ ์ข€ ๋” ์ง„ํ•˜๊ฒŒ)
  </p>
  <button class="opacity-0 group-hover:opacity-100 transition-opacity">
    ๋ฒ„ํŠผ (๋ถ€๋ชจ hover ์‹œ ๋‚˜ํƒ€๋‚จ)
  </button>
</div>

์‹ค์ „: ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์Šคํ„ฐ๋”” ์นด๋“œ

// ๐Ÿฆ ์˜ํ˜ธ: "์นด๋“œ ์ „์ฒด์— hover ๋ฅผ ๊ฑธ๊ณ , ๋‚ด๋ถ€ ์š”์†Œ๋“ค์ด ๋”ฐ๋กœ ๋ฐ˜์‘ํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด
//           group ์ด ์ •๋‹ต์ด์—์š”. JS ์—†์ด CSS ๋งŒ์œผ๋กœ!"
 
function StudyCard({ title, description, memberCount, status }: Props) {
  return (
    // group: ์ด div ๊ฐ€ group ์˜ ๊ธฐ์ค€์ 
    // hover:-translate-y-1: hover ์‹œ 1px ์œ„๋กœ ์ด๋™
    // hover:shadow-lg: hover ์‹œ ๊ทธ๋ฆผ์ž ๊ฐ•์กฐ
    <div className="group relative rounded-xl border border-gray-200 bg-white p-5 shadow-md transition-all hover:-translate-y-1 hover:shadow-lg">
 
      {/* ์ƒํƒœ ๋ฑƒ์ง€: ํ•ญ์ƒ ์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ์— */}
      <div className="absolute right-4 top-4">
        <StatusBadge status={status} />
      </div>
 
      {/* ์•„์ด์ฝ˜: hover ์‹œ ํŒŒ๋ž€์ƒ‰ ๋ฐฐ๊ฒฝ์œผ๋กœ */}
      <div className="mb-4 flex h-10 w-10 items-center justify-center rounded-full bg-gray-100 text-xl transition-colors group-hover:bg-blue-100">
        ๐Ÿ“š
      </div>
 
      {/* ์ œ๋ชฉ: hover ์‹œ ํŒŒ๋ž€์ƒ‰์œผ๋กœ */}
      <h3 className="mb-2 text-lg font-bold text-gray-900 transition-colors group-hover:text-blue-600">
        {title}
      </h3>
 
      {/* ์„ค๋ช…: hover ์‹œ ์•ฝ๊ฐ„ ์ง„ํ•˜๊ฒŒ */}
      <p className="mb-4 line-clamp-2 text-sm text-gray-500 transition-colors group-hover:text-gray-700">
        {description}
      </p>
 
      {/* ํ•˜๋‹จ ์ •๋ณด */}
      <div className="flex items-center justify-between">
        <span className="text-xs text-gray-400">๐Ÿ‘ฅ {memberCount}๋ช…</span>
        {/* ๋ฒ„ํŠผ: ๊ธฐ๋ณธ ์ˆจ๊น€ โ†’ hover ์‹œ ๋‚˜ํƒ€๋‚จ */}
        <button className="rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-medium text-white opacity-0 transition-opacity group-hover:opacity-100">
          ์ž์„ธํžˆ ๋ณด๊ธฐ โ†’
        </button>
      </div>
    </div>
  );
}

์ค‘์ฒฉ Group (๊ทธ๋ฃน ์ด๋ฆ„ ์ง€์ •)

์—ฌ๋Ÿฌ ๊ฐœ์˜ group ์ด ์ค‘์ฒฉ๋  ๋•Œ ์ด๋ฆ„์„ ์ง€์ •ํ•ด์„œ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์–ด.

<!-- ์ค‘์ฒฉ group ์—์„œ ํŠน์ • group ๋งŒ ๋ฐ˜์‘ํ•˜๊ฒŒ ํ•˜๊ธฐ -->
<div class="group/card">
  <div class="group/btn">
    <button class="group-hover/card:bg-blue-50 group-hover/btn:bg-blue-100">
      ์นด๋“œ hover ์‹œ ํŒŒ๋ž€ ๋ฐฐ๊ฒฝ, ๋ฒ„ํŠผ ์ž์ฒด hover ์‹œ ๋” ์ง„ํ•œ ํŒŒ๋ž€ ๋ฐฐ๊ฒฝ
    </button>
  </div>
</div>

๐Ÿค Peer Variant: ํ˜•์ œ ์ƒํƒœ๋กœ ์ œ์–ด

peer ๋ฅผ ํ˜•์ œ ์š”์†Œ์— ๋ถ™์ด๋ฉด, ๊ทธ ๋’ค์— ์˜ค๋Š” ํ˜•์ œ๋“ค์ด peer-hover:, peer-invalid:, peer-checked: ๋“ฑ์„ ์‚ฌ์šฉํ•ด์„œ peer ์˜ ์ƒํƒœ์— ๋ฐ˜์‘ํ•  ์ˆ˜ ์žˆ์–ด.

โš ๏ธ ์ค‘์š”: peer ์™€ ๊ทธ๊ฑธ ๊ฐ์ง€ํ•˜๋Š” ์š”์†Œ๋Š” ํ˜•์ œ ๊ด€๊ณ„์—ฌ์•ผ ํ•˜๊ณ , peer ๊ฐ€ ์•ž์— ์™€์•ผ ํ•ด (CSS ์˜ ์ผ๋ฐ˜ ํ˜•์ œ ์„ ํƒ์ž ~ ๋ฐฉํ–ฅ ๋•Œ๋ฌธ).

<!-- ์ž…๋ ฅ ํ•„๋“œ๊ฐ€ peer, ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ peer-invalid ์— ๋ฐ˜์‘ -->
<div>
  <input
    type="email"
    class="peer rounded-lg border border-gray-300 px-4 py-2
           invalid:border-red-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
    placeholder="์ด๋ฉ”์ผ ์ž…๋ ฅ"
    required
  />
  <!-- peer-invalid: input ์ด invalid ์ƒํƒœ์ผ ๋•Œ๋งŒ ๋ณด์ž„ -->
  <p class="mt-1 hidden text-sm text-red-500 peer-invalid:block">
    ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”
  </p>
</div>

์‹ค์ „: ์ฒดํฌ๋ฐ•์Šค + ๋ผ๋ฒจ ์Šคํƒ€์ผ๋ง

// ์ปค์Šคํ…€ ์ฒดํฌ๋ฐ•์Šค โ€” peer ๋กœ ์ฒดํฌ ์ƒํƒœ๋ฅผ ๋ผ๋ฒจ์— ๋ฐ˜์˜
function CheckboxItem({ id, label }: { id: string; label: string }) {
  return (
    <label
      htmlFor={id}
      className="flex cursor-pointer items-center gap-3 rounded-lg p-3 hover:bg-gray-50"
    >
      {/* peer: ์ด ์ฒดํฌ๋ฐ•์Šค์˜ ์ƒํƒœ๋ฅผ ํ˜•์ œ๋“ค์ด ๊ฐ์ง€ */}
      <input
        id={id}
        type="checkbox"
        className="peer sr-only"  {/* sr-only: ์‹œ๊ฐ์ ์œผ๋กœ ์ˆจ๊ธฐ๋˜ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์—๋Š” ๋ณด์ž„ */}
      />
      {/* ์ปค์Šคํ…€ ์ฒดํฌ๋ฐ•์Šค UI */}
      <div className="
        flex h-5 w-5 items-center justify-center rounded border-2 border-gray-300
        transition-colors
        peer-checked:border-blue-600 peer-checked:bg-blue-600
      ">
        {/* ์ฒดํฌ ํ‘œ์‹œ: peer-checked ์‹œ ๋ณด์ž„ */}
        <svg className="hidden h-3 w-3 text-white peer-checked:block" ...>โœ“</svg>
      </div>
      {/* ๋ผ๋ฒจ ํ…์ŠคํŠธ: peer-checked ์‹œ ์ƒ‰์ƒ ๋ณ€๊ฒฝ */}
      <span className="text-sm text-gray-700 peer-checked:font-medium peer-checked:text-blue-700">
        {label}
      </span>
    </label>
  );
}

์‹ค์ „: ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ UI

function EmailInput() {
  return (
    <div className="flex flex-col gap-1.5">
      <label htmlFor="email" className="text-sm font-medium text-gray-700">
        ์ด๋ฉ”์ผ
      </label>
      <input
        id="email"
        type="email"
        className="
          peer rounded-lg border border-gray-300 px-4 py-2.5 text-sm
          placeholder:text-gray-400
          focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20
          invalid:[&:not(:placeholder-shown)]:border-red-400
          invalid:[&:not(:placeholder-shown)]:ring-red-400/20
        "
        placeholder="you@example.com"
        required
      />
      {/* ์—๋Ÿฌ ๋ฉ”์‹œ์ง€: placeholder ๊ฐ€ ์•ˆ ๋ณด์ผ ๋•Œ(= ๊ฐ’์ด ์žˆ์„ ๋•Œ) + invalid ์ƒํƒœ์ผ ๋•Œ๋งŒ */}
      <p className="
        hidden text-xs text-red-500
        peer-invalid:[&:not(:placeholder-shown)~&]:block
      ">
        ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค
      </p>
    </div>
  );
}

๐Ÿ’ป ์‹ค์ „: ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ UI

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํ•„ํ„ฐ ๋ฒ„ํŠผ

type FilterButtonProps = {
  label: string;
  active?: boolean;
  onClick: () => void;
};
 
function FilterButton({ label, active, onClick }: FilterButtonProps) {
  return (
    <button
      onClick={onClick}
      className={`
        rounded-full px-4 py-2 text-sm font-medium transition-all
        ${active
          ? 'bg-blue-600 text-white shadow-md'
          : 'bg-white text-gray-600 border border-gray-200 hover:border-blue-300 hover:text-blue-600 hover:bg-blue-50'
        }
        focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500
        active:scale-95
      `}
    >
      {label}
    </button>
  );
}

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์ž…๋ ฅ ํ•„๋“œ (ํฌ์ปค์Šค ๊ฐ•์กฐ)

function SearchInput() {
  return (
    <div className="relative">
      {/* ๊ฒ€์ƒ‰ ์•„์ด์ฝ˜ */}
      <div className="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-gray-400">
        ๐Ÿ”
      </div>
      <input
        type="search"
        placeholder="์Šคํ„ฐ๋”” ๊ฒ€์ƒ‰..."
        className="
          w-full rounded-xl border border-gray-200 bg-white
          py-3 pl-11 pr-4 text-sm text-gray-900
          placeholder:text-gray-400
          transition-all
          focus:border-blue-400 focus:bg-white focus:outline-none
          focus:ring-4 focus:ring-blue-500/10
          hover:border-gray-300
        "
      />
    </div>
  );
}

๐Ÿ”ง ๊ณ ๊ธ‰ Variant ์Šคํƒœํ‚น

Variant ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ์กฐํ•ฉํ•ด์„œ "๋” ๊ตฌ์ฒด์ ์ธ ์กฐ๊ฑด" ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.

<!-- ๋‹คํฌ ๋ชจ๋“œ + md ์ด์ƒ + hover ์‹œ -->
<div class="dark:md:hover:bg-gray-700">
 
<!-- ํฌ์ปค์Šค + disabled ๊ฐ€ ์•„๋‹ ๋•Œ -->
<button class="focus:not-disabled:ring-2">
 
<!-- ๊ทธ๋ฃน hover + md ์ด์ƒ -->
<span class="md:group-hover:text-blue-600">
// ์‹ค์ „: ๋ฐ˜์‘ํ˜• + ๋‹คํฌ ๋ชจ๋“œ + hover ๋ณตํ•ฉ ๋ฒ„ํŠผ
<button className="
  w-full md:w-auto
  bg-blue-600 text-white
  px-6 py-3 rounded-xl
  font-semibold text-sm
  transition-all
  hover:bg-blue-700 hover:shadow-lg
  active:scale-95
  disabled:cursor-not-allowed disabled:opacity-50
  focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2
  dark:bg-blue-500 dark:hover:bg-blue-400
">
  ์Šคํ„ฐ๋”” ์ฐธ์—ฌํ•˜๊ธฐ
</button>

๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

VariantํŠธ๋ฆฌ๊ฑฐ์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€
hover:๋งˆ์šฐ์Šค ์˜ค๋ฒ„๋ฒ„ํŠผ ์ƒ‰์ƒ ๋ณ€๊ฒฝ, ์นด๋“œ ๊ทธ๋ฆผ์ž
focus:ํฌ์ปค์Šค์ž…๋ ฅ ํ•„๋“œ ๋ง ํ‘œ์‹œ
focus-visible:ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค์ ‘๊ทผ์„ฑ ํฌ์ปค์Šค ํ‘œ์‹œ
active:ํด๋ฆญ ์ค‘๋ฒ„ํŠผ ๋ˆŒ๋ฆผ ํšจ๊ณผ
disabled:disabled ์†์„ฑ๋น„ํ™œ์„ฑํ™” ์Šคํƒ€์ผ
checked:์ฒดํฌ๋ฐ•์Šค ์ฒดํฌ์ปค์Šคํ…€ ์ฒดํฌ๋ฐ•์Šค
first: / last:์ฒซ/๋งˆ์ง€๋ง‰ ์ž์‹๋ชฉ๋ก ๊ตฌ๋ถ„์„ 
odd: / even:ํ™€์ˆ˜/์ง์ˆ˜ ์ž์‹ํ…Œ์ด๋ธ” ์ค„๋ฌด๋Šฌ
group-hover:๋ถ€๋ชจ hover์นด๋“œ ๋‚ด ๋ฒ„ํŠผ ๋…ธ์ถœ
peer-invalid:ํ˜•์ œ invalidํผ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ์˜์ˆ™์ด "์นด๋“œ์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ฆฌ๋ฉด ์นด๋“œ ์•ˆ์˜ ๋ฒ„ํŠผ์ด ๋ณด์ด๊ณ , ์•„๋‹ ๋•Œ๋Š” ์ˆจ๊ฒจ์ค˜์š”" ๋ผ๊ณ  ์š”์ฒญํ–ˆ๋‹ค. JS ์—†์ด Tailwind ๋งŒ์œผ๋กœ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€?

โœ… ์ •๋‹ต: ๋ถ€๋ชจ ์นด๋“œ์— group ํด๋ž˜์Šค, ๋ฒ„ํŠผ์— opacity-0 group-hover:opacity-100 ํด๋ž˜์Šค๋ฅผ ์ ์šฉํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • group ์€ ๋ถ€๋ชจ์— ๋‹ฌ์•„์„œ "์ด ์š”์†Œ๊ฐ€ hover ๋์„ ๋•Œ๋ฅผ ์ž์‹๋“ค์ด ๊ฐ์ง€ํ•œ๋‹ค" ๋Š” ์‹ ํ˜ธ์•ผ.
  • group-hover:opacity-100 ์€ group ์ด ๋ถ™์€ ์กฐ์ƒ์ด hover ๋  ๋•Œ ์ ์šฉ๋ผ.
  • opacity-0 ์ด ๊ธฐ๋ณธ (์ˆจ๊น€), group-hover:opacity-100 ์ด hover ์‹œ (๋‚˜ํƒ€๋‚จ).
  • transition-opacity ๋„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋‚˜ํƒ€๋‚˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๊นŒ์ง€ ์ค„ ์ˆ˜ ์žˆ์–ด.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋ถ€๋ชจ์— group, ์ž์‹์— group-hover:{์Šคํƒ€์ผ}."

Q2. focus:outline-none ๋งŒ ์“ฐ๋Š” ๊ฒƒ์ด ์œ„ํ—˜ํ•œ ์ด์œ ์™€ ์˜ฌ๋ฐ”๋ฅธ ๋Œ€์•ˆ์€?

โœ… ์ •๋‹ต: ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค ์‹œ ์‹œ๊ฐ์  ํ‘œ์‹œ๊ฐ€ ์‚ฌ๋ผ์ ธ ์ ‘๊ทผ์„ฑ ์œ„๋ฐ˜์ด ๋œ๋‹ค. focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ์ฒ˜๋Ÿผ ๋Œ€์•ˆ ํฌ์ปค์Šค ์Šคํƒ€์ผ์„ ๋ฐ˜๋“œ์‹œ ์ œ๊ณตํ•ด์•ผ ํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ outline ์€ ๋งˆ์šฐ์Šค๋กœ ํด๋ฆญํ•  ๋•Œ๋„ ๋‚˜ํƒ€๋‚˜์„œ ๋””์ž์ธ์„ ํ•ด์น˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด. ๊ทธ๋ž˜์„œ focus:outline-none ์„ ์“ฐ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„.
  • ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Tab ํ‚ค๋กœ ์ด๋™ํ•˜๋Š” ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž๋‚˜ ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์‚ฌ์šฉ์ž๊ฐ€ ํ˜„์žฌ ํฌ์ปค์Šค ์œ„์น˜๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์—†์–ด โ€” WCAG 2.1 ์ ‘๊ทผ์„ฑ ๊ฐ€์ด๋“œ๋ผ์ธ ์œ„๋ฐ˜.
  • focus-visible: ์€ ๋งˆ์šฐ์Šค ํด๋ฆญ ์‹œ์—๋Š” ์ ์šฉ ์•ˆ ๋˜๊ณ  ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค ์‹œ์—๋งŒ ์ ์šฉ๋ผ์„œ ๋‘ ๋ฌธ์ œ๋ฅผ ๋™์‹œ์— ํ•ด๊ฒฐํ•ด.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "focus:outline-none ์€ focus-visible:ring-* ์™€ ์ง์„ ์ด๋ค„์•ผ ํ•œ๋‹ค."

Q3. peer ๋ฅผ ์ด์šฉํ•œ ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์—์„œ "์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค"๋ฉด ๊ฐ€์žฅ ๋จผ์ € ํ™•์ธํ•ด์•ผ ํ•  ๊ฒƒ์€?

โœ… ์ •๋‹ต: peer ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ง„ input ์ด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ณด๋‹ค HTML ์—์„œ ์•ž์—(์œ„์—) ์œ„์น˜ํ•ด ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • peer ๋Š” CSS ์˜ ์ผ๋ฐ˜ ํ˜•์ œ ์„ ํƒ์ž(~)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•ด. CSS ์˜ ~ ๋Š” ์ดํ›„์— ์˜ค๋Š” ํ˜•์ œ ์š”์†Œ ์—๋งŒ ์ ์šฉ๋ผ. ์ฆ‰, peer ๊ฐ€ ๋ถ™์€ ์š”์†Œ๊ฐ€ ๊ฐ์ง€ํ•˜๋Š” ์š”์†Œ ์•ž ์— ์žˆ์–ด์•ผ ํ•ด.
  • ์ž˜๋ชป๋œ ๊ตฌ์กฐ: ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ input ๋ณด๋‹ค ์•ž์— ์žˆ์œผ๋ฉด peer-invalid: ๊ฐ€ ์ž‘๋™ ์•ˆ ํ•ด.
  • ์˜ฌ๋ฐ”๋ฅธ ๊ตฌ์กฐ: <input class="peer" /> โ†’ <p class="peer-invalid:block" />
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "peer ๋Š” ์•ž์—, ๊ฐ์ง€ํ•˜๋Š” ์š”์†Œ๋Š” ๋’ค์—."

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

group ๊ณผ peer ๋ฅผ ์˜ค๋Š˜ ์ฒ˜์Œ ์ œ๋Œ€๋กœ ์จ๋ดค๋Š”๋ฐ, ์ด๊ฒŒ ์ง„์งœ ๋งˆ๋ฒ•์ด๋‹ค. JS ๋กœ isHovered ์ƒํƒœ ๊ด€๋ฆฌํ•˜๊ณ  ํด๋ž˜์Šค ๋™์ ์œผ๋กœ ๋ถ™์ด๊ณ  ํ–ˆ๋˜ ๊ฒŒ CSS ๋ ˆ๋ฒจ์—์„œ ๊ทธ๋ƒฅ ํ•ด๊ฒฐ๋˜๋‹ค๋‹ˆ.

์˜ํ˜ธ ๋‹˜์ด "์ƒํƒœ ๊ด€๋ จ UI ๋Š” ๊ฐ€๋Šฅํ•˜๋ฉด CSS ๋กœ ํ•ด๊ฒฐํ•˜๊ณ , JS ๋Š” ์‹ค์ œ ๋ฐ์ดํ„ฐ ์ƒํƒœ ๊ด€๋ฆฌ์— ์ง‘์ค‘ํ•ด์š”" ๋ผ๊ณ  ํ•˜์…จ๋Š”๋ฐ, ์ด๊ฒŒ ๋ฌด์Šจ ๋ง์ธ์ง€ ์ด์ œ ์ฒด๊ฐ๋œ๋‹ค. ์นด๋“œ hover ํšจ๊ณผ ๊ฐ™์€ ๊ฑฐ JS ์—์„œ ๋นผ๋‹ˆ๊นŒ ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•ด์ง€๋”๋ผ.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "group ๊ณผ peer ๋Š” CSS ์— ์กฐ๊ฑด๋ถ€ ๋…ผ๋ฆฌ๋ฅผ ์ฃผ์ž…ํ•˜๋Š” ๋„๊ตฌ๋‹ค. JS ๋กœ ํ•ด์•ผ ํ•  ์ผ์„ CSS ๋กœ ๋Œ๋ฆฌ๋ฉด, ๊ทธ๋งŒํผ JS ๊ฐ€ ๊ฐ€๋ฒผ์›Œ์ง„๋‹ค."

์˜ค๋Š˜ ์˜์ˆ˜ ๋‹˜์ด "ํŽ˜์ด์ง€ ๋กœ๋”ฉ์ด ๋นจ๋ผ์ง„ ๊ฒƒ ๊ฐ™๋‹ค" ๊ณ  ํ•˜์…จ๋Š”๋ฐ, ์•Œ๊ณ  ๋ณด๋ฉด JS ๋ฒˆ๋“ค์ด ์ค„์–ด๋“  ํšจ๊ณผ๋„ ์žˆ์„ ๊ฑฐ๋‹ค. ๋ณด๋žŒ์ฐจ๋‹ค! ์ง‘์— ๊ฐ€์„œ ๋งฅ์ฃผ ํ•œ ์บ” ๋งˆ์…”์•ผ์ง€. ๐Ÿบ


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ