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

UI Code Lab

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

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

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

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

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

 

 

์›น ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค๋‹ค ๋ณด๋ฉด ๋ฉ”๋‰ด๋‚˜ ํ•„ํ„ฐ, ์„ค์ • ์ฐฝ ๋“ฑ ๋“œ๋กญ๋‹ค์šด(dropdown) ๊ธฐ๋Šฅ์ด ๊ผญ ํ•„์š”ํ•  ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” jQuery ์—†์ด๋„ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•œ ์ˆœ์ˆ˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ(dropdown)๋ฅผ ํ™œ์šฉํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ฆญํ•˜๋ฉด ์—ด๋ฆฌ๊ณ , ๋‹ค์‹œ ํด๋ฆญํ•˜๋ฉด ๋‹ซํžˆ๋Š” ๊ธฐ๋ณธ ๋“œ๋กญ๋‹ค์šด UI๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

 

โœ… ๋“œ๋กญ๋‹ค์šด์ด๋ž€?

๋“œ๋กญ๋‹ค์šด(dropdown)์€ ์‚ฌ์šฉ์ž์˜ ํด๋ฆญ์— ๋”ฐ๋ผ ์„ ํƒ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” UI ์š”์†Œ์ž…๋‹ˆ๋‹ค. ๋Œ€ํ‘œ์ ์ธ ์˜ˆ๋กœ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฉ”๋‰ด, ์–ธ์–ด ์„ ํƒ ์ฐฝ, ์•Œ๋ฆผ ๋ชฉ๋ก ๋“ฑ์—์„œ ์ž์ฃผ ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

 

๐Ÿ› ๏ธ HTML ๊ตฌ์กฐ

 

๋จผ์ € dropdown์„ ๊ตฌ์„ฑํ•  ๊ธฐ๋ณธ HTML์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค:

<div class="dropdown">
  <button id="dropdownButton">๋ฉ”๋‰ด ์—ด๊ธฐ โ–พ</button>
  <ul id="dropdownMenu" class="dropdown-menu">
    <li><a href="#">ํ™ˆ</a></li>
    <li><a href="#">์†Œ๊ฐœ</a></li>
    <li><a href="#">๋ฌธ์˜</a></li>
  </ul>
</div>

 

๐ŸŽจ CSS ์Šคํƒ€์ผ

๊ธฐ๋ณธ์ ์œผ๋กœ ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด๋Š” ์ˆจ๊ธฐ๊ณ , ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋ณด์ด๋„๋ก ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

.dropdown-menu {
  display: none;
  position: absolute;
  background-color: #fff;
  border: 1px solid #ddd;
  list-style: none;
  padding: 0;
  margin-top: 8px;
  width: 160px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}

.dropdown-menu li {
  padding: 10px;
}

.dropdown-menu li:hover {
  background-color: #f4f4f4;
}

 

โš™๏ธ JavaScript๋กœ ํ† ๊ธ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„

๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ul.dropdown-menu์˜ display ์†์„ฑ์„ ํ† ๊ธ€ ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

document.addEventListener("DOMContentLoaded", function () {
  const button = document.getElementById("dropdownButton");
  const menu = document.getElementById("dropdownMenu");

  button.addEventListener("click", function () {
    const isVisible = menu.style.display === "block";
    menu.style.display = isVisible ? "none" : "block";
  });

  // ์™ธ๋ถ€ ํด๋ฆญ ์‹œ ๋“œ๋กญ๋‹ค์šด ๋‹ซ๊ธฐ
  document.addEventListener("click", function (event) {
    if (!button.contains(event.target) && !menu.contains(event.target)) {
      menu.style.display = "none";
    }
  });
});

 

์ด ์Šคํฌ๋ฆฝํŠธ๋Š”:

  • ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋ฉ”๋‰ด๋ฅผ ์—ด๊ณ ,
  • ๋‹ค์‹œ ํด๋ฆญํ•˜๋ฉด ๋‹ซ์œผ๋ฉฐ,
  • ๋ฉ”๋‰ด ์™ธ๋ถ€๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋ฉ”๋‰ด๊ฐ€ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

 

์•„๋ž˜๋Š” ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด ํ•ญ๋ชฉ์„ ์„ ํƒํ•˜๋ฉด ๋ฒ„ํŠผ ํ…์ŠคํŠธ๊ฐ€ ํ•ด๋‹น ํ•ญ๋ชฉ์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋Š” ๊ธฐ๋Šฅ๊นŒ์ง€ ํฌํ•จํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. ๋“œ๋กญ๋‹ค์šด ๊ธฐ๋Šฅ์— ์žˆ์–ด UX์ ์œผ๋กœ๋„ ๋งค์šฐ ์œ ์šฉํ•œ ๊ฐœ์„ ์ด์—์š”. ๐Ÿ˜„

 

โš™๏ธ ๋ณด์™„๋œ JavaScript

document.addEventListener("DOMContentLoaded", function () {
  const button = document.getElementById("dropdownButton");
  const menu = document.getElementById("dropdownMenu");

  // ๋ฉ”๋‰ด ํ† ๊ธ€
  button.addEventListener("click", function (e) {
    e.stopPropagation(); // ๋ฉ”๋‰ด ์™ธ๋ถ€ ํด๋ฆญ ์‹œ ๋‹ซํž˜ ๋ฐฉ์ง€
    menu.style.display = menu.style.display === "block" ? "none" : "block";
  });

  // ๋ฉ”๋‰ด ํ•ญ๋ชฉ ํด๋ฆญ ์‹œ ๋ฒ„ํŠผ ํ…์ŠคํŠธ ๋ณ€๊ฒฝ
  menu.querySelectorAll("li a").forEach(item => {
    item.addEventListener("click", function (e) {
      e.preventDefault(); // ๊ธฐ๋ณธ ๋งํฌ ๋™์ž‘ ๋ฐฉ์ง€
      button.textContent = this.textContent + " โ–พ"; // ๋ฒ„ํŠผ ํ…์ŠคํŠธ ๋ณ€๊ฒฝ
      menu.style.display = "none"; // ๋ฉ”๋‰ด ๋‹ซ๊ธฐ
    });
  });

  // ์™ธ๋ถ€ ํด๋ฆญ ์‹œ ๋ฉ”๋‰ด ๋‹ซ๊ธฐ
  document.addEventListener("click", function (e) {
    if (!menu.contains(e.target) && !button.contains(e.target)) {
      menu.style.display = "none";
    }
  });
});

 

์ด ์ฝ”๋“œ๋Š” ๋‹ค์Œ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค:

  • ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด๊ฐ€ ์—ด๋ฆฌ๊ณ ,
  • ๋ฉ”๋‰ด ํ•ญ๋ชฉ์„ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น ํ…์ŠคํŠธ๋กœ ๋ฒ„ํŠผ์ด ๋ณ€๊ฒฝ๋˜๋ฉฐ,
  • ์™ธ๋ถ€๋ฅผ ํด๋ฆญํ•˜๋ฉด ๋“œ๋กญ๋‹ค์šด์ด ๋‹ซํž™๋‹ˆ๋‹ค.

 

 

๋“œ๋กญ๋‹ค์šด UI๋Š” ์‹œ๊ฐ์  ํšจ๊ณผ๋ฟ ์•„๋‹ˆ๋ผ ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ด์š”. ์ด์ œ ์ ‘๊ทผ์„ฑ(Accessibility, A11y)์„ ๊ณ ๋ คํ•œ ๊ตฌ์กฐ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•ด๋ณผ๊ฒŒ์š”.

 

โœ… ์ ‘๊ทผ์„ฑ์„ ๊ฐ•ํ™”ํ•œ HTML ๊ตฌ์กฐ

<div class="dropdown">
  <button
    id="dropdownButton"
    aria-haspopup="true"
    aria-expanded="false"
    aria-controls="dropdownMenu"
  >
    ๋ฉ”๋‰ด ์—ด๊ธฐ โ–พ
  </button>
  <ul
    id="dropdownMenu"
    class="dropdown-menu"
    role="menu"
    aria-labelledby="dropdownButton"
  >
    <li role="menuitem"><a href="#">ํ™ˆ</a></li>
    <li role="menuitem"><a href="#">์†Œ๊ฐœ</a></li>
    <li role="menuitem"><a href="#">๋ฌธ์˜</a></li>
  </ul>
</div>

 

๐Ÿ”Ž ์ ‘๊ทผ์„ฑ ํฌ์ธํŠธ ์š”์•ฝ:

  • aria-haspopup="true": ์ด ๋ฒ„ํŠผ์ด ํŒ์—… ๋ฉ”๋‰ด๋ฅผ ์—ฐ๋‹ค๋Š” ์˜๋ฏธ
  • aria-expanded: ์—ด๋ฆผ ์ƒํƒœ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ˜์˜
  • aria-controls + aria-labelledby: ๋ฒ„ํŠผ๊ณผ ๋ฉ”๋‰ด๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์—ฐ๊ฒฐ
  • role="menu" + role="menuitem": ํ™”๋ฉด๋‚ญ๋…๊ธฐ์—์„œ ๋“œ๋กญ๋‹ค์šด์ด๋ผ๋Š” ์˜๋ฏธ๋ฅผ ์ „๋‹ฌ

 

โš™๏ธ ์ ‘๊ทผ์„ฑ ๊ฐ•ํ™” + ํ‚ค๋ณด๋“œ ์ง€์› JavaScript

document.addEventListener("DOMContentLoaded", () => {
  const button = document.getElementById("dropdownButton");
  const menu = document.getElementById("dropdownMenu");

  // ๋ฉ”๋‰ด ์—ด๊ธฐ/๋‹ซ๊ธฐ
  function toggleMenu(show) {
    menu.style.display = show ? "block" : "none";
    button.setAttribute("aria-expanded", show ? "true" : "false");
  }

  button.addEventListener("click", (e) => {
    e.stopPropagation();
    const isOpen = button.getAttribute("aria-expanded") === "true";
    toggleMenu(!isOpen);
  });

  // ๋ฉ”๋‰ด ์•„์ดํ…œ ํด๋ฆญ → ๋ฒ„ํŠผ ํ…์ŠคํŠธ ๋ณ€๊ฒฝ
  menu.querySelectorAll("a").forEach(item => {
    item.addEventListener("click", (e) => {
      e.preventDefault();
      button.textContent = item.textContent + " โ–พ";
      toggleMenu(false);
    });
  });

  // ์™ธ๋ถ€ ํด๋ฆญ ์‹œ ๋ฉ”๋‰ด ๋‹ซ๊ธฐ
  document.addEventListener("click", (e) => {
    if (!menu.contains(e.target) && !button.contains(e.target)) {
      toggleMenu(false);
    }
  });

  // ESC ๋ˆŒ๋ €์„ ๋•Œ ๋‹ซ๊ธฐ
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape") {
      toggleMenu(false);
      button.focus();
    }
  });

  // ์•„๋ž˜ ๋ฐฉํ–ฅํ‚ค๋กœ ํฌ์ปค์Šค ์ด๋™
  button.addEventListener("keydown", (e) => {
    if (e.key === "ArrowDown") {
      e.preventDefault();
      const firstItem = menu.querySelector("a");
      if (firstItem) firstItem.focus();
    }
  });

  // ๋ฉ”๋‰ด ํ•ญ๋ชฉ ๊ฐ„ ๋ฐฉํ–ฅํ‚ค ์ด๋™
  menu.addEventListener("keydown", (e) => {
    const items = Array.from(menu.querySelectorAll("a"));
    const currentIndex = items.indexOf(document.activeElement);

    if (e.key === "ArrowDown") {
      e.preventDefault();
      const next = items[(currentIndex + 1) % items.length];
      next.focus();
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      const prev = items[(currentIndex - 1 + items.length) % items.length];
      prev.focus();
    }
  });
});

 

๐ŸŒŸ ์ด ์ฝ”๋“œ์˜ ์ ‘๊ทผ์„ฑ ์š”์•ฝ

  • โœ… ๋ฒ„ํŠผ ์—ญํ•  ๋ช…ํ™•ํ™” (aria-* ์†์„ฑ)
  • โœ… ํ‚ค๋ณด๋“œ ํƒ์ƒ‰ ๊ฐ€๋Šฅ (๋ฐฉํ–ฅํ‚ค, ESC ์ง€์›)
  • โœ… ํฌ์ปค์Šค ์ด๋™ ์ฒ˜๋ฆฌ
  • โœ… ํ™”๋ฉด ๋‚ญ๋…๊ธฐ์—์„œ ๋ฉ”๋‰ด ์ธ์‹ ์ง€์›

์ด์ œ ์‹œ๊ฐ์  ์‚ฌ์šฉ์ž๋ฟ ์•„๋‹ˆ๋ผ ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž, ์Šคํฌ๋ฆฐ๋ฆฌ๋” ์‚ฌ์šฉ์ž ๋ชจ๋‘์—๊ฒŒ ์นœ์ ˆํ•œ ๋“œ๋กญ๋‹ค์šด UI๊ฐ€ ์™„์„ฑ๋์–ด์š”!

 

 

๐Ÿต ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

๋“œ๋กญ๋‹ค์šด UI๋Š” ๋‹จ์ˆœํ•œ ๊ธฐ๋Šฅ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ,

์‚ฌ์šฉ์ž ๊ฒฝํ—˜์˜ ๋””ํ…Œ์ผ์„ ์ขŒ์šฐํ•˜๋Š” ์ค‘์š”ํ•œ ์š”์†Œ์ž…๋‹ˆ๋‹ค.

์ˆœ์ˆ˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋งŒ์œผ๋กœ๋„ ํด๋ฆญ, ํ‚ค๋ณด๋“œ ํƒ์ƒ‰, ์™ธ๋ถ€ ํด๋ฆญ ์ฒ˜๋ฆฌ ๋“ฑ ๋‹ค์–‘ํ•œ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ,

์ ‘๊ทผ์„ฑ์„ ๊ฐ•ํ™”ํ•˜๋ฉด ๋”์šฑ ๋งŽ์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์—ด๋ฆฐ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์–ด์š”.

 

์ž‘์€ ๋“œ๋กญ๋‹ค์šด ํ•˜๋‚˜๋ฅผ ๋งŒ๋“ค๋ฉด์„œ๋„

HTML ๊ตฌ์กฐ, ์Šคํƒ€์ผ๋ง, ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ, ๊ทธ๋ฆฌ๊ณ  ์›น ์ ‘๊ทผ์„ฑ๊นŒ์ง€

๋ชจ๋‘ ๊ณ ๋ฏผํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ ํ”„๋ŸฐํŠธ์—”๋“œ ๊ฐœ๋ฐœ์€ ์ •๋ง ๋งค๋ ฅ์ ์ธ ๋ถ„์•ผ์ฃ .

 

์•ž์œผ๋กœ๋„ ์ด๋Ÿฐ UI ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜ํ•˜๋‚˜๋ฅผ ์„ธ์‹ฌํ•˜๊ฒŒ ๋งŒ๋“ค์–ด๊ฐ€๋ฉฐ,

๋” ๋‚˜์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๋””์ž์ธํ•ด๋ณด์„ธ์š”!

๋‹ค์Œ ํฌ์ŠคํŠธ์—์„œ๋Š” ๋” ๋‹ค์–‘ํ•œ ์ธํ„ฐ๋ž™์…˜์ด๋‚˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ™œ์šฉ ๋ฐฉ๋ฒ•๋„ ์†Œ๊ฐœํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

๊ธฐ๋Œ€ํ•ด์ฃผ์„ธ์š”. ๐Ÿ˜Š