입력 폼은 사용자가 손과 머리를 가장 많이 쓰는 자리입니다. 한 글자씩 채워 나가는 동안 잘하고 있는지 어디가 어긋났는지를 곁에서 일러 주는 반응을, CSS의 상태 선택자와 짧은 애니메이션으로 어떻게 만드는지 코드로 보여드리겠습니다.

81.1 포커스 링: 지금 여기라고 짚어 주기

손을 댄 칸은 또렷이 도드라져야 합니다. :focus에서 테두리 색을 바꾸고 box-shadow로 은은한 링을 두르면, 지금 어느 칸에 글자가 들어가는지 한눈에 잡힙니다.


.input {
border: 1px solid #d1d5db;
border-radius: 8px;
padding: 10px 12px;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}


커서를 두면 테두리가 파래지고 은은한 링이 감쌉니다. 기본 outline을 지웠으니 그 자리를 이 링이 대신 채워, 마우스든 키보드든 지금 여기를 놓치지 않습니다.

81.2 focus-within: 필드 그룹 통째로 강조

칸 하나가 아니라 여러 칸이 묶인 그룹 전체를 밝히고 싶을 때가 있습니다. :focus-within은 안쪽 어느 요소든 포커스를 받으면 부모까지 반응하게 해 줍니다.


.field-group {
padding: 12px;
border: 1px solid #e5e7eb;
border-radius: 10px;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.field-group:focus-within {
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}


그룹 안의 입력칸에 손을 대는 순간 감싼 상자 전체가 파랗게 살아납니다. 지금 다루는 묶음이 어디인지가 넓게 드러나, 여러 항목이 늘어선 폼에서도 자리를 잃지 않습니다.

81.3 플로팅 라벨: 라벨이 위로 올라가기

라벨을 칸 안에 뉘어 두었다가, 입력이 시작되면 위로 작게 올라가 자리표가 되게 하는 방식입니다. 빈 칸을 가려내는 :placeholder-shown:focus를 함께 씁니다.


.float {
position: relative;
}
.float input {
padding: 18px 12px 6px;
}
.float label {
position: absolute;
left: 12px;
top: 14px;
color: #9ca3af;
pointer-events: none;
transition: transform 0.15s ease, color 0.15s ease;
}
.float input:focus + label,
.float input:not(:placeholder-shown) + label {
transform: translateY(-10px) scale(0.8);
color: #2563eb;
}


:placeholder-shown이 켜지려면 입력에 빈 자리표를 하나 넣어 둬야 합니다.


<span class="float">
<input id="email" placeholder=" ">
<label for="email">이메일</label>
</span>


칸이 비어 있으면 라벨이 안내문처럼 안에 앉아 있다가, 글자를 넣거나 포커스를 주면 작게 줄며 위로 올라가 제목이 됩니다. 라벨과 안내문을 한 요소로 겸하니 화면이 한결 단정해집니다.

81.4 valid와 invalid: 형식을 색으로 답하기

브라우저는 입력값이 규칙에 맞는지를 스스로 압니다. typerequired 같은 속성을 걸어 두면 :valid:invalid로 그 판단을 곧바로 색에 실을 수 있습니다.


.field input:required:invalid {
border-color: #f87171;
}
.field input:valid {
border-color: #34d399;
}
.field input:focus:invalid {
box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.2);
}


대상 입력에는 형식과 필수 여부만 표시해 두면 됩니다.


<input type="email" required placeholder=" ">


골뱅이가 빠진 주소를 적는 동안엔 테두리가 붉게 머물다, 형식이 맞아떨어지는 순간 초록으로 넘어갑니다. 붉은 신호와 초록 신호가 함께 있어야 사용자가 안심하고 다음 칸으로 넘어갑니다.

81.5 흔들림: 에러를 몸짓으로 알리기

제출이 막혔을 때 칸이 짧게 좌우로 떨리면, 색을 보지 않아도 여기가 문제라는 것이 즉각 전해집니다. @keyframes로 좌우를 몇 번 오가게 한 뒤 제자리로 돌립니다.


@keyframes shake {
0%, 100% {
transform: translateX(0);
}
20%, 60% {
transform: translateX(-6px);
}
40%, 80% {
transform: translateX(6px);
}
}
.field.error input {
border-color: #ef4444;
animation: shake 0.4s ease;
}


error 클래스가 붙는 순간 칸이 좌우로 두어 번 흔들리고 멈춥니다. 애니메이션이 반복 없이 한 번만 돌기에, 고개를 젓듯 잘못을 짚고는 곧 차분히 가라앉습니다.

81.6 커스텀 체크박스: 체크가 그려지며 나타나기

기본 체크박스는 꾸미기 어렵습니다. 진짜 입력은 숨겨 두고 그 옆의 상자를 대신 보여 준 뒤, :checked에 맞춰 안쪽 체크 표시가 커지며 그려지게 합니다.


.check input {
position: absolute;
opacity: 0;
}
.check .box {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border: 2px solid #cbd5e1;
border-radius: 5px;
transition: background 0.15s ease, border-color 0.15s ease;
}
.check .box::after {
content: "";
width: 6px;
height: 11px;
border: solid #fff;
border-width: 0 2px 2px 0;
transform: rotate(45deg) scale(0);
transition: transform 0.15s ease;
}
.check input:checked + .box {
background: #2563eb;
border-color: #2563eb;
}
.check input:checked + .box::after {
transform: rotate(45deg) scale(1);
}


체크 표시는 짧은 변 두 개를 기울여 만든 갈고리입니다. 체크를 켜면 상자가 파랗게 차오르며 그 갈고리가 scale 0에서 1로 톡 튀어나옵니다.

81.7 움직임을 줄이는 사용자 배려하기

흔들림이나 튀어나오는 체크는 어떤 사용자에게는 거슬립니다. 움직임 줄이기를 켠 경우엔 전환과 흔들림을 걷어 내되, 색과 상태로 전하는 신호만은 그대로 남깁니다.


@media (prefers-reduced-motion: reduce) {
.input,
.field-group,
.float label,
.check .box,
.check .box::after {
transition: none;
}
.field.error input {
animation: none;
}
}


이제 라벨은 미끄러지지 않고 곧장 제자리에 놓이고, 에러 칸은 흔들리는 대신 붉은 테두리로만 잘못을 알립니다. 체크 갈고리도 튀어나오지 않고 바로 나타납니다. 움직임은 빠졌지만 통과와 실패를 가르는 신호는 하나도 잃지 않습니다.

81.8 정리

폼 피드백은 사용자가 가장 애쓰는 자리에서 곁을 지키는 반응입니다. :focus:focus-within으로 지금 다루는 칸을 밝히고, 플로팅 라벨로 안내와 제목을 겸하고, :valid:invalid로 형식을 색에 실으며, 흔들림과 체크 애니메이션으로 잘못과 확인을 몸짓에 담습니다. 그리고 어떤 움직임을 넣든 prefers-reduced-motion 대체를 함께 두어, 신호는 남기고 움직임만 덜어 냅니다.