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

UI Code Lab

์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•œ ์‹ค์ „ ๋ชจ๋‹ฌ ๊ตฌํ˜„๊ธฐ (Modal) ๋ณธ๋ฌธ

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

์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•œ ์‹ค์ „ ๋ชจ๋‹ฌ ๊ตฌํ˜„๊ธฐ (Modal)

๐Ÿฏ๊ฟ€์ƒ์ด๐Ÿ 2025. 6. 26. 11:57

์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•œ ์‹ค์ „ ๋ชจ๋‹ฌ ๊ตฌํ˜„๊ธฐ (Modal)

 

 

 

 

๋ชจ๋‹ฌ์€ UI์—์„œ ์ž์ฃผ ์“ฐ์ด๋Š” ์ปดํฌ๋„ŒํŠธ์ง€๋งŒ,

 

์ ‘๊ทผ์„ฑ๊นŒ์ง€ ์ฑ™๊ธด ๊ตฌํ˜„์€ ์ƒ๊ฐ๋ณด๋‹ค ๋“œ๋ญ…๋‹ˆ๋‹ค.

 

์ด ๊ธ€์—์„œ๋Š” ๊ธฐ๋ณธ์ ์ธ ๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ชจ๋‹ฌ ๊ตฌํ˜„๋ฒ•๋ถ€ํ„ฐ,

 

์Šคํฌ๋ฆฐ๋ฆฌ๋”์™€ ํ‚ค๋ณด๋“œ ์‚ฌ์šฉ์ž๋„ ๊ณ ๋ คํ•œ ์ ‘๊ทผ์„ฑ ๊ฐœ์„  ํฌ์ธํŠธ๊นŒ์ง€ ๋‹ค๋ค„๋ด…๋‹ˆ๋‹ค.

 

์‹ค๋ฌด์— ๋ฐ”๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์ œ ์ฝ”๋“œ๋„ ํ•จ๊ป˜ ์ œ๊ณตํ•˜๋‹ˆ,

 

๋ณต์žกํ•œ ํ”„๋ ˆ์ž„์›Œํฌ ์—†์ด ์ˆœ์ˆ˜ JS๋กœ๋„ ์ถฉ๋ถ„ํžˆ ๋ฉ‹์ง„ ์ ‘๊ทผ์„ฑ ๋ชจ๋‹ฌ์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”!

 

 


โœจ ๊ธฐ๋ณธ ๋ชจ๋‹ฌ ๊ตฌํ˜„

๊ฐ„๋‹จํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ๋ชจ๋‹ฌ์„ ๋„์šฐ๊ณ  ๋‹ซ๋Š” ๊ธฐ๋ณธ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

 

โœ… HTML ๊ตฌ์กฐ

 
<button id="openModal">๋ชจ๋‹ฌ ์—ด๊ธฐ</button>

<div id="modal" class="modal" hidden>
  <div class="modal-content" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
    <h2 id="modalTitle">๋ชจ๋‹ฌ ์ œ๋ชฉ</h2>
    <p>๋ชจ๋‹ฌ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.</p>
    <button id="closeModal">๋‹ซ๊ธฐ</button>
  </div>
</div>

 

โœ… JS ๊ธฐ๋ณธ ๋กœ์ง

 
const openBtn = document.getElementById('openModal');
const closeBtn = document.getElementById('closeModal');
const modal = document.getElementById('modal');

openBtn.addEventListener('click', () => {
  modal.hidden = false;
});

closeBtn.addEventListener('click', () => {
  modal.hidden = true;
});

 

โœ… ๊ธฐ๋ณธ ์Šคํƒ€์ผ (์„ ํƒ)

 
.modal {
  position: fixed;
  inset: 0;
  background-color: rgba(0,0,0,0.4);
  display: flex;
  justify-content: center;
  align-items: center;
}
.modal-content {
  background: white;
  padding: 2em;
  border-radius: 8px;
}

 


โ™ฟ ์ ‘๊ทผ์„ฑ์„ ๋ฐ˜์˜ํ•œ ๋ชจ๋‹ฌ ๊ฐœ์„  ํฌ์ธํŠธ

 

์œ„ ์ฝ”๋“œ์— ์•ฝ๊ฐ„์˜ ๊ธฐ๋Šฅ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ํ›จ์”ฌ ์ ‘๊ทผ์„ฑ ๋†’์€ ๋ชจ๋‹ฌ์ด ๋ฉ๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ๋Š” ์šด์˜ ์„œ๋น„์Šค์— ์‹ค์ œ ์ ์šฉํ–ˆ๋˜ ์ ‘๊ทผ์„ฑ ์‚ฌ๋ก€๋“ค์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

 

1. ํฌ์ปค์Šค ํŠธ๋žฉ

 

๋ชจ๋‹ฌ ์•ˆ์—์„œ Tab ํ‚ค๋กœ ํฌ์ปค์Šค๊ฐ€ ์ˆœํ™˜ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

 

๋ชจ๋‹ฌ์ด ์—ด๋ฆฌ๋ฉด ํฌ์ปค์Šค๊ฐ€ ์ฒซ ๋ฒˆ์งธ ํฌ์ปค์Šค ๊ฐ€๋Šฅํ•œ ์š”์†Œ๋กœ ์ด๋™ํ•˜๊ณ ,

 

Shift + Tab์ด๋‚˜ Tab์œผ๋กœ ํฌ์ปค์Šค๊ฐ€ ๋ฐ”๊นฅ์œผ๋กœ ๋‚˜๊ฐ€์ง€ ์•Š๊ฒŒ ๋ง‰๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

 

const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const modalContent = modal.querySelector('.modal-content');

function trapFocus(e) {
  const focusableElements = modalContent.querySelectorAll(focusableSelectors);
  const firstEl = focusableElements[0];
  const lastEl = focusableElements[focusableElements.length - 1];

  if (e.key === 'Tab') {
    if (e.shiftKey && document.activeElement === firstEl) {
      e.preventDefault();
      lastEl.focus();
    } else if (!e.shiftKey && document.activeElement === lastEl) {
      e.preventDefault();
      firstEl.focus();
    }
  }
}

 

2. ESC ํ‚ค๋กœ ๋‹ซ๊ธฐ

document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && !modal.hidden) {
    modal.hidden = true;
    openBtn.focus();
  }
});

 

3. ARIA ์†์„ฑ ์ ์šฉ

  • role="dialog": ์Šคํฌ๋ฆฐ๋ฆฌ๋”๊ฐ€ ๋Œ€ํ™”์ฐฝ์ž„์„ ์ธ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • aria-modal="true": ๋ฐฐ๊ฒฝ๊ณผ ๋ถ„๋ฆฌ๋œ ๋…๋ฆฝ ๋ ˆ์ด์–ด์ž„์„ ์•Œ๋ฆฝ๋‹ˆ๋‹ค.
  • aria-labelledby="modalTitle": ํƒ€์ดํ‹€์„ ์ฐธ์กฐํ•ด ์ œ๋ชฉ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.

 

4. ํฌ์ปค์Šค ๋ณต๊ท€

๋ชจ๋‹ฌ์„ ๋‹ซ์„ ๋•Œ๋Š” ๋‹ค์‹œ ํŠธ๋ฆฌ๊ฑฐ ๋œ ์š”์†Œ(์˜ˆ: "๋ชจ๋‹ฌ ์—ด๊ธฐ" ๋ฒ„ํŠผ)๋กœ ํฌ์ปค์Šค๋ฅผ ๋˜๋Œ๋ ค ์‚ฌ์šฉ์ž ํ๋ฆ„์„ ๋Š์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 


โœ… ๊ตฌ์กฐ์ ·์‹œ๊ฐ์  ๊ฐœ์„  ์•„์ด๋””์–ด

 

๐ŸŽฏ 1. ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜ ํšจ๊ณผ ์ถ”๊ฐ€

๋ชจ๋‹ฌ์ด ์—ด๋ฆฌ๊ณ  ๋‹ซํž ๋•Œ ๋ถ€๋“œ๋Ÿฌ์šด ํŠธ๋žœ์ง€์…˜์„ ์ฃผ๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ํ›จ์”ฌ ์ž์—ฐ์Šค๋Ÿฌ์›Œ์ง‘๋‹ˆ๋‹ค.

.modal[hidden] {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.modal {
  opacity: 1;
  transition: opacity 0.3s ease;
}

 

JS์—์„œ๋Š” modal.hidden = false; ๋Œ€์‹  modal.classList.add('visible'),

 

๋‹ซ์„ ๋• remove('visible')๋กœ ํ† ๊ธ€ ํ•˜๋ฉด์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ ์šฉํ•˜๋ฉด ์ข‹์•„์š”.

 

๐Ÿ”ง 2. ๋ชจ๋‹ฌ ์—ฌ๋Ÿฌ ๊ฐœ ๋Œ€์‘์„ ์œ„ํ•œ ๊ตฌ์กฐ ์ผ๋ฐ˜ํ™”

 

๋ชจ๋‹ฌ์ด ํ•˜๋‚˜๋ฟ์ด ์•„๋‹ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด,

 

JS ๊ตฌ์กฐ๋ฅผ ํ•จ์ˆ˜ํ˜•์œผ๋กœ ๋งŒ๋“ค์–ด๋‘๋ฉด ์žฌ์‚ฌ์šฉ์„ฑ์ด ์ข‹์•„์ง‘๋‹ˆ๋‹ค.

function openModal(modalId) {
  const modal = document.getElementById(modalId);
  modal.hidden = false;
  trapFocus(modal); // ํฌ์ปค์Šค ํŠธ๋žฉ ํ•จ์ˆ˜ ์žฌํ™œ์šฉ
}

function closeModal(modalId) {
  const modal = document.getElementById(modalId);
  modal.hidden = true;
}

 

์ถ”ํ›„ CAD ๋‹ค์šด๋กœ๋“œ๋‚˜ ๊ณต์ง€์‚ฌํ•ญ ํŒ์—… ๋“ฑ์—๋„ ๋™์ผํ•œ ๊ตฌ์กฐ๋กœ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์–ด์š”.

 

๐Ÿงญ 3. ๋ฐฐ๊ฒฝ ํด๋ฆญ์œผ๋กœ ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ ๊ธฐ๋Šฅ

 

์˜์™ธ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ธฐ๋Œ€ํ•˜๋Š” ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์˜ˆ์š”.

modal.addEventListener('click', (e) => {
  if (e.target === modal) {
    modal.hidden = true;
    openBtn.focus();
  }
});

 

๐Ÿชก 4. CSS ๋ณ€์ˆ˜ ํ™œ์šฉํ•œ ํ…Œ๋งˆ ์Šคํƒ€์ผ

 

๋ชจ๋‹ฌ ๋ฐฐ๊ฒฝ์ƒ‰, ๊ทธ๋ฆผ์ž, ์—ฌ๋ฐฑ ๋“ฑ์„ CSS ๋ณ€์ˆ˜๋กœ ์ถ”์ถœํ•˜๋ฉด

 

์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์— ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์›Œ์š”.

 

:root {
  --modal-bg: #fff;
  --modal-overlay: rgba(0, 0, 0, 0.4);
}

.modal {
  background-color: var(--modal-overlay);
}
.modal-content {
  background-color: var(--modal-bg);
}

 

๐ŸŒ 5. ๋‹ค๊ตญ์–ด ๋Œ€์‘์„ ์œ„ํ•œ ํ…์ŠคํŠธ ๊ด€๋ฆฌ ๊ตฌ์กฐ

 

ํ”„๋กœ์ ํŠธ์—์„œ ๋‹ค๊ตญ์–ด ํŽ˜์ด์ง€๋„ ์žˆ์—ˆ๋˜ ๊ฑธ๋กœ ๋ณด์ด๋Š”๋ฐ์š”,

 

๋ชจ๋‹ฌ ํ…์ŠคํŠธ๋„ ์†์„ฑ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ถ„๋ฆฌํ•ด๋‘๋ฉด ๊ตญ์ œํ™”์— ์œ ๋ฆฌํ•ด์š”.

 
<p data-text="modal.message">๋ชจ๋‹ฌ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.</p>
 
const i18n = {
  ko: {
    'modal.message': '๋ชจ๋‹ฌ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.',
  },
  en: {
    'modal.message': 'This is modal content.',
  },
};

function applyLang(lang) {
  document.querySelectorAll('[data-text]').forEach((el) => {
    const key = el.getAttribute('data-text');
    el.textContent = i18n[lang][key] || '';
  });
}

 

 


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

 

์ ‘๊ทผ์„ฑ์€ ํ•œ ๋ฒˆ์— ์™„๋ฒฝํ•˜๊ฒŒ ํ•˜๊ธฐ๋ณด๋‹ค,

 

๊พธ์ค€ํžˆ ๊ฐœ์„ ํ•ด๊ฐ€๋Š” ์—ฌ์ •์— ๊ฐ€๊นŒ์›Œ์š”.

 

์ด ๊ธ€์ด ์—ฌ๋Ÿฌ๋ถ„์ด ๋งŒ๋“  ๋ชจ๋‹ฌ ํ•˜๋‚˜ํ•˜๋‚˜์—

 

๋” ๋งŽ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ์ž‘์€ ์ถœ๋ฐœ์ ์ด ๋˜์—ˆ์œผ๋ฉด ํ•ฉ๋‹ˆ๋‹ค.