사용자가 무언가를 누르면 제품은 반드시 화답해야 합니다. 됐다고, 혹은 정말 그럴 거냐고 되묻는 그 짧은 대화가 신뢰를 만듭니다. 아래 예제로 화답을 코드로 어떻게 짓는지 하나씩 보여드리겠습니다.

26.1 토스트 알림

작업이 끝났다는 짧은 소식은 화면 아래에서 슬며시 떠올랐다 사라지는 토스트가 알맞습니다. 흐름을 막지 않으면서도 눈에 들어옵니다.


.toast {
position: fixed;
bottom: 24px;
left: 50%;
padding: 12px 20px;
background: #111827;
color: #fff;
border-radius: 8px;
opacity: 0;
transform: translateX(-50%) translateY(20px);
animation: toast-in .3s ease forwards;
}
@keyframes toast-in {
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}


아래에서 20px 밑에 투명하게 두었다가 제자리로 밀어 올리며 서서히 드러냅니다. translateX(-50%) 는 가로 가운데 정렬을 유지하는 부분이라 두 좌표 모두에 그대로 실어 줍니다.

26.2 확인창 dialog

되돌릴 수 없는 일 앞에서는 잠깐 멈춰 세워 되물어야 합니다. HTML 기본 요소인 <dialog> 하나로 배경을 어둡게 덮는 확인창을 만들 수 있습니다.


<dialog id="confirm">
<p>이 글을 삭제할까요? 되돌릴 수 없습니다.</p>
<form method="dialog">
<button value="cancel">그대로 두기</button>
<button value="delete" class="danger">삭제</button>
</form>
</dialog>


자바스크립트에서 showModal() 로 열면 뒤 화면과의 상호작용이 자동으로 잠깁니다. method="dialog" 폼 안의 버튼을 누르면 창이 닫히고 그 value 가 결과로 전해집니다.


dialog {
border: none;
border-radius: 12px;
padding: 24px;
max-width: 320px;
}
dialog::backdrop {
background: rgba(0, 0, 0, .4);
}
.danger {
background: #dc2626;
color: #fff;
}


::backdrop 는 dialog 뒤를 덮는 층을 스타일링하는 전용 가상 요소입니다. 여기에 반투명 검정을 깔면 뒤 화면이 어두워지며 시선이 확인창으로 모입니다. 확인 버튼은 붉게 칠해 무게를 실었습니다.

26.3 성공 체크마크 그리기

됐다는 신호로 체크 표시가 손으로 그리듯 나타나면 작은 만족감을 줍니다. SVG 선을 stroke-dasharray 로 다뤄 이 효과를 냅니다.


<svg class="check" viewBox="0 0 52 52">
<circle cx="26" cy="26" r="24" fill="none" stroke="#22c55e"/>
<path fill="none" stroke="#22c55e" d="M14 27 l8 8 l16 -16"/>
</svg>


.check path {
stroke-width: 3;
stroke-dasharray: 48;
stroke-dashoffset: 48;
animation: draw .4s ease forwards;
}
@keyframes draw {
to { stroke-dashoffset: 0; }
}


stroke-dasharray 로 선 전체를 한 칸짜리 점선으로 만들고 stroke-dashoffset 으로 그만큼 밀어내면 선이 통째로 숨습니다. offset 을 0으로 되돌리는 애니메이션을 주면, 감춰졌던 선이 시작점부터 끝점까지 그어지듯 나타납니다.

26.4 aria-live 로 소리 없이 알리기

토스트가 눈에는 보여도 스크린 리더 사용자에게는 닿지 않을 수 있습니다. aria-live 영역에 문구를 넣으면 그 변화가 소리로 읽힙니다.


<div aria-live="polite" class="sr-only">저장되었습니다</div>
<div aria-live="assertive" class="sr-only">저장에 실패했습니다</div>


polite 는 사용자가 하던 일을 마칠 때까지 기다렸다 알리고, assertive 는 하던 낭독을 끊고 곧바로 알립니다. 저장 완료 같은 일상적 소식은 polite 로, 실패나 경고처럼 즉시 알아야 할 것은 assertive 로 나눕니다.


.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}


이 클래스는 요소를 화면에서만 지우고 접근성 트리에는 남깁니다. 눈으로 보는 사람에게는 안 보이지만 낭독기는 그 문구를 읽어 주니, 시각 토스트와 짝지어 두면 두 통로로 화답이 전해집니다.

26.5 실행취소 패턴

삭제 전에 매번 되묻기보다, 일단 지우되 되돌릴 손길을 잠깐 남겨 두는 편이 더 다정합니다. 토스트 안에 실행취소 버튼을 함께 둡니다.


<div class="toast" role="status">
<span>항목을 삭제했습니다</span>
<button class="undo">실행취소</button>
</div>


.toast {
display: flex;
align-items: center;
gap: 16px;
}
.undo {
background: none;
border: none;
color: #60a5fa;
font-weight: 600;
cursor: pointer;
}


role="status" 는 이 영역을 부드러운 알림으로 표시해 낭독기에도 전해집니다. 사용자가 몇 초 안에 실행취소를 누르면 지운 것을 되살리고, 그냥 두면 토스트가 사라지며 삭제가 확정됩니다. 앞을 막아서는 확인창보다 걸음이 가볍습니다.

26.6 클릭 후 상태 전환

버튼은 눌린 뒤 스스로 결과를 보여 주면 좋습니다. 팔로우 버튼이 눌리자마자 팔로잉으로 바뀌는 식입니다.


<button class="follow" aria-pressed="false">팔로우</button>


.follow {
background: #2563eb;
color: #fff;
padding: 8px 16px;
border-radius: 8px;
transition: background .15s;
}
.follow[aria-pressed="true"] {
background: #e5e7eb;
color: #374151;
}
.follow[aria-pressed="true"]::before {
content: "\2713 ";
}


aria-pressed 값만 뒤집으면 색이 조용한 회색으로 바뀌고 앞에 작은 체크가 붙습니다. 이 속성은 눌림 상태를 낭독기에도 그대로 전하니, 보이는 변화와 들리는 변화가 한 속성 하나로 함께 움직입니다.

26.7 조각을 하나로 모으기

삭제 흐름 하나에 지금까지의 조각이 다 들어갑니다. 위험한 행동은 확인창으로 한 번 세우고, 실행 뒤에는 토스트로 화답하며 실행취소를 곁들이는 짜임입니다.


<button class="danger">삭제</button>
<dialog id="confirm">...</dialog>
<div class="toast" role="status" hidden>
<span>삭제했습니다</span>
<button class="undo">실행취소</button>
</div>


삭제를 누르면 확인창이 뜨고, 확정하면 항목이 사라지며 토스트가 hidden 을 벗고 떠오릅니다. 사용자는 자기 행동의 결과를 눈으로 보고, 마음이 바뀌면 되돌릴 수 있습니다. 좋은 화답은 큰 소리로 외치기보다 곁에서 가만히 고개를 끄덕이는 것에 가깝습니다.