๊ด€๋ฆฌ ๋ฉ”๋‰ด

UI Code Lab

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ํƒญ ๊ธฐ๋Šฅ ๋งŒ๋“ค๊ธฐ (tab) ๋ณธ๋ฌธ

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธUI

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ํƒญ ๊ธฐ๋Šฅ ๋งŒ๋“ค๊ธฐ (tab)

๐Ÿฏ๊ฟ€์ƒ์ด๐Ÿ 2025. 6. 20. 09:45

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ํƒญ ๊ธฐ๋Šฅ ๋งŒ๋“ค๊ธฐ

 

 

๐Ÿงญ ์›น UI๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ธฐ๋ณธ๊ธฐ – ํƒญ ๊ธฐ๋Šฅ๋ถ€ํ„ฐ ํƒ„ํƒ„ํ•˜๊ฒŒ

์›น์‚ฌ์ดํŠธ๋ฅผ ์ฒ˜์Œ ๋ฐฉ๋ฌธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์žฅ ๋จผ์ € ์ ‘ํ•˜๋Š” UI ์š”์†Œ ์ค‘ ํ•˜๋‚˜๊ฐ€ ํƒญ ๋ฉ”๋‰ด์ž…๋‹ˆ๋‹ค.

์ •๋ณด๊ฐ€ ๋งŽ์„์ˆ˜๋ก ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณต์žกํ•˜๊ฒŒ ๋А๊ปด์ง€๊ธฐ ์‰ฌ์šด๋ฐ,

ํƒญ์€ ์ด๋Ÿฐ ๋‚ด์šฉ์„ ์ฃผ์ œ๋ณ„๋กœ ๋‚˜๋ˆ  ๊ฐ€๋…์„ฑ๊ณผ ๊ฒฝํ—˜์„ ๋™์‹œ์— ์žก์„ ์ˆ˜ ์žˆ๋Š” ํ›Œ๋ฅญํ•œ ๋„๊ตฌ์˜ˆ์š”.

 

์˜ค๋Š˜์€ ์ˆœ์ˆ˜ ๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ํƒญ ๊ธฐ๋Šฅ์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•๊ณผ ํ•จ๊ป˜

์ ‘๊ทผ์„ฑ(Accessibility)๊นŒ์ง€ ์ฑ™๊ธฐ๋Š” ํŒ์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

 

 

๐ŸŽฏ ํƒญ UI๋ž€?

ํƒญ ๋ฉ”๋‰ด๋Š” ์—ฌ๋Ÿฌ ์ •๋ณด ๋ธ”๋ก์„ ๊ฒน์ณ๋‘๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ฆญํ•˜๋Š” ํƒญ์— ๋”ฐ๋ผ ํ•ด๋‹น ์ฝ˜ํ…์ธ ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

FAQ, ์ œํ’ˆ ์‚ฌ์–‘, ๋ฉค๋ฒ„ ํ”„๋กœํ•„, ์นดํ…Œ๊ณ ๋ฆฌ ๊ตฌ๋ถ„ ๋“ฑ ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์— ํ™œ์šฉ๋˜์ฃ .

 

 

๐Ÿงฑ HTML ๊ตฌ์กฐ๋ถ€ํ„ฐ ์ค€๋น„ํ•˜๊ธฐ

๋จผ์ € ํƒญ ๋ฒ„ํŠผ๊ณผ ์ฝ˜ํ…์ธ  ์˜์—ญ์„ HTML๋กœ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

<div class="tabs">
  <ul class="tab-buttons">
    <li class="active">ํƒญ 1</li>
    <li>ํƒญ 2</li>
    <li>ํƒญ 3</li>
  </ul>
  <div class="tab-contents">
    <div class="tab-content active">๋‚ด์šฉ 1</div>
    <div class="tab-content">๋‚ด์šฉ 2</div>
    <div class="tab-content">๋‚ด์šฉ 3</div>
  </div>
</div>

 

๐ŸŽจ CSS ์Šคํƒ€์ผ๋ง์œผ๋กœ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ ์ฃผ๊ธฐ

.tab-content { display: none; }
.tab-content.active { display: block; }
.tab-buttons li.active { font-weight: bold; }

 

๐Ÿง  ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ํƒญ ๋™์ž‘ ๊ตฌํ˜„

const tabButtons = document.querySelectorAll('.tab-buttons li');
const tabContents = document.querySelectorAll('.tab-content');

tabButtons.forEach((btn, index) => {
  btn.addEventListener('click', () => {
    tabButtons.forEach(b => b.classList.remove('active'));
    tabContents.forEach(c => c.classList.remove('active'));

    btn.classList.add('active');
    tabContents[index].classList.add('active');
  });
});

 

๐Ÿ›  ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํƒญ์—๋„ ์˜๋ฏธ ์žˆ๋Š” HTML ๊ตฌ์กฐ ์“ฐ๊ธฐ

  • <button> ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ ‘๊ทผ์„ฑ์— ๋” ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ ์ฝ˜ํ…์ธ ์— aria-labelledby, role="tabpanel" ๊ฐ™์€ ์†์„ฑ์„ ์‚ฌ์šฉํ•ด ๊ฒ€์ƒ‰์—”์ง„๊ณผ ์Šคํฌ๋ฆฐ๋ฆฌ๋”๋„ ๋‚ด์šฉ์„ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ์„ธ์š”.
  • ํƒญ ์ด๋ฆ„์— ํ‚ค์›Œ๋“œ๋ฅผ ๋„ฃ์–ด ์ฝ˜ํ…์ธ  ๊ฐ€์‹œ์„ฑ์„ ๋†’์ด๋Š” ๊ฒƒ๋„ ํŒ์ž…๋‹ˆ๋‹ค.

 

์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•˜๋ฉด ๋” ๋งŽ์€ ์‚ฌ์šฉ์ž,

ํŠนํžˆ ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ์‚ฌ์šฉ์ž๋‚˜ ํ‚ค๋ณด๋“œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์—๊ฒŒ๋„

ํƒญ ๊ธฐ๋Šฅ์„ ํŽธ๋ฆฌํ•˜๊ฒŒ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์–ด์š”.

 

 

โ™ฟ ์ ‘๊ทผ์„ฑ๋„ ์ฑ™๊ธฐ์ž – ๋ˆ„๊ตฌ๋‚˜ ์“ฐ๊ธฐ ์‰ฌ์šด UI ๋งŒ๋“ค๊ธฐ

๊ธฐ๋Šฅ ๊ตฌํ˜„๋งŒํผ ์ค‘์š”ํ•œ ๊ฒƒ์ด ๋ชจ๋‘๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค.

ํ‚ค๋ณด๋“œ๋งŒ ์“ฐ๋Š” ์‚ฌ์šฉ์ž, ํ™”๋ฉด ๋‚ญ๋…๊ธฐ ์‚ฌ์šฉ์ž์—๊ฒŒ๋„ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋ ค๋ฉด ์•„๋ž˜ ์š”์†Œ๋“ค์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • ์‹œ๋งจํ‹ฑํ•œ HTML ์‚ฌ์šฉ (<button> ์š”์†Œ, role="tablist" ๋“ฑ)
  • ARIA ์†์„ฑ ์ถ”๊ฐ€ (aria-selected, aria-controls, aria-labelledby ๋“ฑ)
  • ํ‚ค๋ณด๋“œ ํƒ์ƒ‰ ์ง€์› (keydown ์ด๋ฒคํŠธ, ๋ฐฉํ–ฅํ‚ค ์ด๋™)
  • ํฌ์ปค์Šค ์Šคํƒ€์ผ ์ถ”๊ฐ€ ๋ฐ tabindex="0" ์ง€์ •

 

โœ… ์™œ ์ ‘๊ทผ์„ฑ์ด ์ค‘์š”ํ• ๊นŒ์š”?

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

 

๐Ÿงฑ HTML ๊ตฌ์กฐ: ์‹œ๋งจํ‹ฑ ์š”์†Œ & ARIA ์†์„ฑ ์ถ”๊ฐ€

<div class="tabs" role="tablist" aria-label="์ •๋ณด ํƒญ">
  <button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">ํƒญ 1</button>
  <button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">ํƒญ 2</button>
  <button role="tab" aria-selected="false" aria-controls="panel-3" id="tab-3">ํƒญ 3</button>

  <div id="panel-1" role="tabpanel" aria-labelledby="tab-1">๋‚ด์šฉ 1</div>
  <div id="panel-2" role="tabpanel" aria-labelledby="tab-2" hidden>๋‚ด์šฉ 2</div>
  <div id="panel-3" role="tabpanel" aria-labelledby="tab-3" hidden>๋‚ด์šฉ 3</div>
</div>

 

๐ŸŽฏ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ ‘๊ทผ์„ฑ ๋Œ€์‘: aria-selected์™€ hidden ์ฒ˜๋ฆฌ

const tabs = document.querySelectorAll('[role="tab"]');
const panels = document.querySelectorAll('[role="tabpanel"]');

tabs.forEach((tab, index) => {
  tab.addEventListener('click', () => {
    tabs.forEach(t => {
      t.setAttribute('aria-selected', false);
      t.classList.remove('active');
    });
    panels.forEach(p => p.hidden = true);

    tab.setAttribute('aria-selected', true);
    tab.classList.add('active');
    panels[index].hidden = false;
  });
});

 

๐Ÿ’ก ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž๋„ ๊ณ ๋ คํ•˜๋ฉด ์™„์„ฑ๋„ ์—…!

  • ๋ฐฉํ–ฅํ‚ค(←, →)๋กœ ํƒญ ์ด๋™ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด keydown ์ด๋ฒคํŠธ๋„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ข‹์•„์š”.
  • focus() ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•ด ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค๋ฅผ ๋‹ค์Œ ํƒญ์œผ๋กœ ์ด๋™์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด์š”.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ ‘๊ทผ์„ฑ ํ‘œ์ค€(WCAG)๋„ ์ถฉ์กฑํ•ด์š”. ์›น์‚ฌ์ดํŠธ ์‹ ๋ขฐ๋„์™€ ์‚ฌ์šฉ์ž ๋งŒ์กฑ๋„๋ฅผ ๋ชจ๋‘ ๋†’์ผ ์ˆ˜ ์žˆ์ฃ .

 

 

 

๐Ÿ“Œ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ํƒญ UI๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ณผ์ •์€ ๋‹จ์ˆœํžˆ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋‚ด์šฉ์ด ๋ฐ”๋€Œ๋Š” ์ˆ˜์ค€์„ ๋„˜์–ด,

์‚ฌ์šฉ์ž ์ค‘์‹ฌ ์„ค๊ณ„์™€ ์›น ํ‘œ์ค€์— ๋Œ€ํ•œ ์ดํ•ด๋ฅผ ๋„“ํž ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๊ธฐํšŒ์ž…๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์— ์ ‘๊ทผ์„ฑ๊นŒ์ง€ ์ฑ™๊ธฐ๋ฉด ์ง„์งœ ์‹ค๋ฌด์— ๊ฐ•ํ•œ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”.

 

๋‹ค์Œ ํฌ์ŠคํŠธ์—์„œ๋Š” ๋“œ๋กญ๋‹ค์šด, ๋ชจ๋‹ฌ ์ฐฝ, ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋“ฑ ๋” ๋‹ค์–‘ํ•œ UI ๊ตฌ์„ฑ ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

UI ํ•˜๋‚˜ํ•˜๋‚˜์— ์ˆจ์€ ์„ค๊ณ„์˜ ์žฌ๋ฏธ๋ฅผ ํ•จ๊ป˜ ๋ฐฐ์›Œ๊ฐ€์š”! ๐Ÿ˜Š

๊ตฌ๋…๊ณผ ์ข‹์•„์š”๋Š” ๊ฐœ๋ฐœ์ž์—๊ฒŒ ํฐ ํž˜์ด ๋ฉ๋‹ˆ๋‹ค. ๐Ÿ™Œ