화면이 잘 굴러갈 때보다 뭔가 어긋났을 때 사용자는 그 서비스의 됨됨이를 봅니다. 빨간 글씨 하나, 텅 빈 목록 하나를 어떻게 다루느냐가 인상을 가릅니다. 에러와 빈 상태를 실제 CSS와 마크업으로 하나씩 다뤄 보겠습니다.
24.1 에러 메시지의 기본 스타일
에러는 눈에 띄어야 하지만 화면을 피로 물들일 필요는 없습니다. 색과 아이콘, 여백으로 조용히 그러나 분명히 알립니다.
.error-text {
display: flex;
align-items: center;
gap: 6px;
margin-top: 6px;
color: #b91c1c;
font-size: 13px;
}
.error-text::before {
content: "!";
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 50%;
background: #b91c1c;
color: #fff;
font-weight: 700;
}
글자와 아이콘이 한 줄에 붙어, 무엇이 문제인지 읽기 전에 문제가 있다는 사실부터 전해집니다.
24.2 입력 아래 도움말이 에러로 바뀌기
평소엔 도움말이던 자리가 문제가 생기면 에러로 바뀌는 편이 자연스럽습니다. 같은 자리를 두 가지 상태가 나눠 씁니다.
.hint {
margin-top: 6px;
font-size: 13px;
color: #6b7280;
}
.field.is-error .hint {
color: #b91c1c;
}
.field.is-error input {
border-color: #b91c1c;
background: #fef2f2;
}
필드에 is-error 클래스 하나만 붙이면 테두리와 배경, 안내 문구가 함께 붉어집니다. 위치가 그대로라 눈이 다시 찾을 필요가 없습니다.
24.3 CSS만으로 하는 즉시 검증
자바스크립트 없이도 브라우저의 기본 검증 상태를 그릴 수 있습니다. 값이 규칙에 맞으면 초록, 어긋나면 붉게 물듭니다.
input:invalid:not(:placeholder-shown) {
border-color: #dc2626;
}
input:valid:not(:placeholder-shown) {
border-color: #16a34a;
}
:not(:placeholder-shown) 를 함께 걸어, 아직 아무것도 적지 않은 빈 칸까지 붉게 칠하는 성급함을 막았습니다. 손을 대기 시작한 칸부터 반응합니다.
24.4 스크린 리더에 에러 읽어 주기
색만으로 알리면 화면을 못 보는 사용자는 놓칩니다. aria-live 영역에 담으면 에러가 뜨는 순간 소리로도 전달됩니다.
<label for="email">이메일</label>
<input id="email" type="email"
aria-describedby="email-err">
<p id="email-err" class="error-text"
role="alert" aria-live="assertive">
올바른 이메일 형식이 아닙니다
</p>
role="alert" 가 붙은 문구는 나타나는 즉시 낭독됩니다. 입력칸과 aria-describedby 로 묶여 있어, 그 칸으로 이동하면 무엇이 문제였는지 다시 들려줍니다.
24.5 폼 위쪽의 에러 요약
긴 폼에서 제출을 눌렀는데 오류가 여기저기 흩어져 있으면 사용자는 뭘 고쳐야 할지 헤맵니다. 맨 위에 요약을 모아 링크로 짚어 줍니다.
<div class="error-summary" role="alert">
<strong>3곳을 확인해 주세요</strong>
<ul>
<li><a href="#email">이메일 형식</a></li>
<li><a href="#pw">비밀번호 길이</a></li>
</ul>
</div>
.error-summary {
border: 1px solid #fca5a5;
background: #fef2f2;
border-radius: 8px;
padding: 14px 16px;
margin-bottom: 20px;
}
.error-summary a {
color: #b91c1c;
}
각 항목이 해당 입력칸으로 가는 앵커 링크라, 누르면 곧장 문제의 칸으로 이동합니다. 헤매는 시간을 요약 상자 하나가 줄여 줍니다.
24.6 빈 상태는 실패가 아니다
목록이 비어 있는 화면을 그냥 흰 여백으로 두면 로딩이 덜 된 건지 고장인지 알 수 없습니다. 왜 비었고 무엇을 하면 되는지 안내합니다.
<div class="empty">
<div class="empty-icon" aria-hidden="true">+</div>
<h3>아직 저장한 글이 없어요</h3>
<p>마음에 드는 글을 담아 두면 여기 모입니다.</p>
<button class="btn-primary">글 둘러보기</button>
</div>
.empty {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
text-align: center;
padding: 48px 20px;
color: #6b7280;
}
.empty-icon {
width: 48px;
height: 48px;
display: grid;
place-items: center;
border-radius: 50%;
background: #f3f4f6;
font-size: 24px;
color: #9ca3af;
}
비었다는 사실 대신 다음에 할 일을 보여 주는 순간, 막다른 길이 입구로 바뀝니다. 행동 버튼 하나가 이탈을 붙잡습니다.
24.7 검색 결과 없음
같은 빈 상태라도 검색 결과가 없을 땐 문구가 달라야 합니다. 무엇을 찾았는지 되짚어 주고 빠져나갈 길을 냅니다.
.empty--search h3::after {
content: attr(data-q);
color: #111827;
}
.empty--search .reset {
margin-top: 4px;
color: #2563eb;
text-decoration: underline;
background: none;
border: 0;
cursor: pointer;
}
제목에 검색어를 attr() 로 되비추면 사용자는 자기가 뭘 쳤는지 확인하고, 필터 초기화 링크로 넓은 결과로 돌아갑니다.
24.8 404 페이지 레이아웃
없는 주소로 들어왔을 때도 사용자를 미아로 두면 안 됩니다. 상황을 짧게 설명하고 돌아갈 문을 크게 엽니다.
.notfound {
min-height: 60vh;
display: grid;
place-content: center;
gap: 12px;
text-align: center;
}
.notfound .code {
font-size: 64px;
font-weight: 800;
color: #e5e7eb;
line-height: 1;
}
.notfound .home {
justify-self: center;
padding: 10px 20px;
border-radius: 8px;
background: #2563eb;
color: #fff;
}
흐린 회색으로 큼직하게 띄운 숫자가 상황을 말하고, 파란 홈 버튼이 시선을 모읍니다. 당황한 화면일수록 나가는 길은 또렷해야 합니다.
24.9 에러난 카드만 남기고 다독이기
목록 중 일부만 불러오기 실패했을 때, 전체를 지우기보다 그 자리에 다시 시도 버튼을 둡니다. 나머지는 그대로 살립니다.
.card.is-failed {
border: 1px dashed #fca5a5;
background: #fff7f7;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 20px;
color: #b91c1c;
}
.card.is-failed .retry {
padding: 6px 14px;
border: 1px solid #b91c1c;
border-radius: 6px;
background: #fff;
color: #b91c1c;
cursor: pointer;
}
점선 테두리가 이 칸은 아직 미완이라고 알리고, 재시도 버튼이 회복의 여지를 남깁니다. 실패가 끝이 아니라 잠깐 멈춤으로 보입니다.
24.10 정리
에러와 빈 상태는 서비스의 뒷모습입니다. 같은 자리에서 도움말이 에러로 바뀌고, 색과 소리로 함께 알리고, 빈 화면엔 다음 행동을 놓아 두는 것, 이 몇 가지만 지켜도 사용자는 막힌 순간에도 길을 잃지 않습니다. 잘될 때가 아니라 어긋날 때 챙기는 배려가 신뢰를 만듭니다.