화면 구석에 잠깐 나타났다 사라지는 알림 메시지. 성공·오류·경고 상황을 사용자에게 자연스럽게 전달하는 토스트 알림을 직접 만들어봐요.
Toast(토스트)는 빵이 튀어나오는 토스터에서 이름을 따왔어요. 화면 모서리에서 '툭' 튀어나왔다가 3초쯤 뒤 자연스럽게 사라지는 작은 메시지 박스입니다.
토스트 vs 모달 vs 스낵바: 토스트와 스낵바는 거의 같은 개념이에요. Google Material Design에서는 스낵바, iOS·웹 일반에서는 토스트라고 부릅니다. 둘 다 사용자의 작업을 방해하지 않는 비침투적(non-intrusive) 알림이에요. 모달은 반대로 확인 버튼을 눌러야 사라지는 침투적 알림입니다.
저장 완료, 복사됨, 전송 성공처럼 사용자가 방금 한 행동의 결과를 빠르게 알려줄 때 씁니다. 사용자가 계속 작업하도록 흐름을 끊지 않아요.
삭제 확인처럼 사용자의 결정이 필요한 상황, 긴 오류 메시지, 법적 동의 등은 토스트보다 모달이나 인라인 에러가 적합합니다.
짧은 메시지는 2~3초, 긴 메시지는 4~5초가 일반적이에요. 오류 메시지는 사용자가 읽을 시간을 줘야 하므로 더 길게 설정하거나 수동 닫기만 허용하기도 해요.
가장 일반적인 위치는 오른쪽 하단이에요. 모바일에서는 하단 가운데가 엄지손가락에 가까워 더 자연스러워요. UX 가이드라인에 맞게 선택하세요.
| 구분 | 토스트/스낵바 | 모달 다이얼로그 | 인라인 메시지 |
|---|---|---|---|
| 작업 방해 | 없음 | 차단 | 없음 |
| 자동 사라짐 | O | X | X |
| 사용 상황 | 행동 결과 피드백 | 확인·선택 요구 | 폼 유효성 오류 |
아래 버튼을 누르면 화면 오른쪽 하단에 토스트가 나타나고 3초 뒤 자동으로 사라집니다. × 버튼으로 직접 닫을 수도 있어요.
버튼을 눌러보세요 — 화면 오른쪽 하단에 토스트가 나타납니다
데모 체크리스트: ① 버튼마다 색이 다른 왼쪽 테두리 ② 3초 후 자동 소멸 ③ 하단의 진행 바(프로그레스 바) ④ × 버튼 클릭으로 즉시 닫기 ⑤ 여러 개 누르면 쌓이는 큐(queue) 처리를 확인해보세요.
HTML + CSS + JS 모두 포함한 완성 코드예요. 별도 라이브러리 없이 순수 코드만으로 동작합니다.
<body> 안 어딘가에 컨테이너 한 줄만 추가하면 됩니다.
<!-- body 안에 딱 한 줄 --> <div id="toast-container"></div> <!-- 버튼 예시 --> <button onclick="showToast('저장되었습니다.', 'success')">저장</button>
컨테이너, 토스트 아이템, 슬라이드·페이드 애니메이션, 진행 바를 포함합니다.
/* 토스트 컨테이너: 화면 오른쪽 하단 고정 */ #toast-container { position: fixed; bottom: 24px; right: 24px; z-index: 9999; display: flex; flex-direction: column; gap: 10px; pointer-events: none; } /* 토스트 아이템 기본 */ .toast { pointer-events: all; display: flex; align-items: flex-start; gap: 12px; background: #fff; border-radius: 10px; padding: 14px 16px; box-shadow: 0 4px 20px rgba(0,0,0,.13); border-left: 4px solid #4f46e5; min-width: 280px; max-width: 360px; position: relative; animation: toastIn .3s cubic-bezier(.34,1.56,.64,1) both; } .toast.hiding { animation: toastOut .3s ease forwards; } /* 타입별 색상 */ .toast-success { border-left-color: #16a34a; } .toast-error { border-left-color: #e11d48; } .toast-warning { border-left-color: #d97706; } .toast-info { border-left-color: #4f46e5; } .toast-icon { font-size: 1.15rem; flex-shrink: 0; margin-top: 1px; } .toast-body { flex: 1; } .toast-title { font-size: .88rem; font-weight: 700; color: #111827; margin-bottom: 2px; } .toast-msg { font-size: .82rem; color: #6b7280; line-height: 1.55; } .toast-close { background: none; border: none; cursor: pointer; color: #9ca3af; font-size: 1rem; padding: 0; flex-shrink: 0; transition: color .15s; } .toast-close:hover { color: #374151; } /* 하단 진행 바 */ .toast-progress { position: absolute; bottom: 0; left: 0; height: 2px; border-radius: 0 0 0 6px; animation: toastProgress 3s linear forwards; } .toast-success .toast-progress { background: #16a34a; } .toast-error .toast-progress { background: #e11d48; } .toast-warning .toast-progress { background: #d97706; } .toast-info .toast-progress { background: #4f46e5; } /* 애니메이션 keyframes */ @keyframes toastIn { from { opacity: 0; transform: translateX(60px); } to { opacity: 1; transform: translateX(0); } } @keyframes toastOut { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(60px); } } @keyframes toastProgress { from { width: 100%; } to { width: 0%; } }
showToast(message, type) 함수 하나로 어디서든 호출할 수 있어요.
/** * showToast(message, type, duration) * type: 'success' | 'error' | 'warning' | 'info' * duration: ms (기본값 3000) */ function showToast(message, type = 'info', duration = 3000) { const config = { success: { icon: '✅', title: '성공' }, error: { icon: '❌', title: '오류' }, warning: { icon: '⚠️', title: '경고' }, info: { icon: 'ℹ️', title: '알림' }, }; const { icon, title } = config[type] || config.info; // 토스트 요소 생성 const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = ` <span class="toast-icon">${icon}</span> <div class="toast-body"> <div class="toast-title">${title}</div> <div class="toast-msg">${message}</div> </div> <button class="toast-close" onclick="dismissToast(this.parentElement)">×</button> <div class="toast-progress" style="animation-duration:${duration}ms"></div> `; const container = document.getElementById('toast-container'); container.appendChild(toast); // duration 후 자동 닫기 const timer = setTimeout(() => dismissToast(toast), duration); toast._timer = timer; } function dismissToast(toast) { if (!toast || toast.classList.contains('hiding')) return; clearTimeout(toast._timer); toast.classList.add('hiding'); toast.addEventListener('animationend', () => toast.remove(), { once: true }); }
위치 변경, 지속 시간 조절, 큐 처리 등 실무에서 바로 쓸 수 있는 응용 패턴이에요.
#toast-container의 CSS 속성만 바꾸면 돼요.
버튼을 선택하면 다음 토스트부터 해당 위치에 표시됩니다. 위의 ② 데모 버튼을 눌러 확인해보세요.
/* 오른쪽 하단 (기본) */ #toast-container { bottom: 24px; right: 24px; } /* 왼쪽 하단 */ #toast-container { bottom: 24px; left: 24px; } /* 상단 가운데 */ #toast-container { top: 24px; left: 50%; transform: translateX(-50%); align-items: center; }
showToast()의 세 번째 인수로 밀리초를 전달해요.
// 기본 (3초) showToast('저장되었습니다.', 'success'); // 1.5초 — 짧은 확인 메시지 showToast('링크가 복사되었습니다.', 'success', 1500); // 5초 — 오류처럼 읽을 시간이 필요한 경우 showToast('서버와 연결할 수 없습니다. 네트워크를 확인해주세요.', 'error', 5000); // 0 (또는 Infinity) — 수동으로만 닫히게 하기 showToast('중요: 결제가 실패했습니다.', 'error', 0);
토스트가 너무 많이 쌓이면 사용자가 읽기 어려워요. 최대 갯수를 제한하는 패턴입니다.
const MAX_TOASTS = 3; // 동시에 최대 3개 function showToast(message, type = 'info', duration = 3000) { const container = document.getElementById('toast-container'); const existing = container.querySelectorAll('.toast:not(.hiding)'); // 한도 초과 시 가장 오래된 토스트 제거 if (existing.length >= MAX_TOASTS) { dismissToast(existing[0]); } // ... 이하 기존 showToast 코드와 동일 }
접근성(a11y) 팁: 토스트에 role="alert" 속성을 추가하면 스크린 리더가 내용을 자동으로 읽어줘요. 오류·경고는 aria-live="assertive", 일반 정보는 aria-live="polite"를 사용하면 더욱 좋습니다.
AI에게 토스트 알림을 요청할 때 사용하기 좋은 프롬프트 예시예요. 복사해서 바로 사용해보세요.
아래 프롬프트를 Claude, ChatGPT, Cursor에 붙여넣어 보세요.
내 웹페이지에 토스트 알림 시스템을 추가해줘. 요구사항: - showToast(message, type, duration) 함수 하나로 어디서든 호출 가능 - type: 'success', 'error', 'warning', 'info' 4가지 - 각 타입별로 아이콘과 테두리 색이 달라야 함 - 오른쪽 하단에 fixed 위치로 표시 - 3초 후 자동 소멸 + × 버튼으로 즉시 닫기 가능 - 오른쪽에서 슬라이드인 / 오른쪽으로 슬라이드아웃 애니메이션 - 하단에 남은 시간을 보여주는 progress bar 포함 - 동시에 최대 3개까지만 표시 (초과 시 오래된 것 제거) - 외부 라이브러리 없이 순수 HTML + CSS + JS로 구현 - role="alert" 접근성 속성 포함 기존 코드에 추가 형식으로 작성해줘.
토스트 알림을 구현할 때 헷갈리는 부분들이에요.
mouseenter 이벤트에서 clearTimeout(toast._timer)로 타이머를 멈추고, mouseleave에서 setTimeout을 다시 시작하면 됩니다. CSS의 animation-play-state: paused를 progress bar에 적용하면 진행 바도 함께 멈출 수 있어요.
useState로 toast 배열을 관리하고 useEffect로 자동 제거를 처리해요. Vue는 ref([])로 같은 방식으로 구현합니다. 또는 react-hot-toast, vue-toastification 같은 검증된 라이브러리를 사용하면 더 빠르게 적용할 수 있어요.
fixed 요소의 위치가 밀릴 수 있어요. bottom 값을 env(safe-area-inset-bottom)으로 대체하거나, 키보드가 열렸을 때 토스트를 상단으로 이동시키는 방법이 있습니다. 또는 입력 중에는 토스트를 잠시 숨기는 전략도 실무에서 많이 씁니다.