GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

바이브코딩 마이크로 인터랙션 9종 — 한 줄 복붙으로

마이크로 인터랙션은 클릭 한 번, 저장 한 번처럼 아주 작은 동작에 화면이 곧바로 반응해 주는 짧은 피드백입니다. 아홉 개 중 하나(04)는 서버 응답을 기다리지 않고 먼저 화면부터 바꿨다가, 실패했다고 가정하면 스스로 원래대로 되돌립니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개 css 인터랙션은 인기 순이 아니라 화면에서 실제로 겪는 순서로 놓았습니다. 누르는 순간(01), 그 결과를 알려주는 완료 신호(02~03), 서버 응답을 기다리지 않고 먼저 보여주는 갱신과 로딩(04~05), 아무것도 없거나 값이 틀렸을 때(06~07), 여러 화면을 오가고 알림이 쌓이는 상황(08~09)입니다. 아래 코드는 상태가 바뀌는 규칙만 옮겼고, 클릭까지 반응하는 온전한 버전은 내려받는 압축 파일 쪽에서 확인하십시오. 상태 전환을 아예 화면 전체에 걸고 싶다면 CSS 다크모드 토글 애니메이션이 그 규모의 스위치를 다루고, 건너편에 짧은 설명을 띄우는 쪽은 CSS 툴팁 말풍선 9종입니다.

01버튼 눌림 스케일

어떤 움직임인가

버튼을 누르는 순간 transform: scale(1)이 scale(.96)으로 줄어들고 그림자도 함께 낮아져, 클릭이 실제로 눌렸다는 촉각적 확인을 줍니다.

버튼 눌림 스케일
새 창에서 열기

01 · 버튼 눌림 스케일 — 측정 누적면적 1.256% · 움직인 프레임 7/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

scale(0.96)스프링 이징active 상태

버튼 인터랙션은 언제 쓰나

손을 떼면 스프링 이징으로 원래 크기로 돌아옵니다.

.btn {
  transform: scale(1);
  transition: transform $duration $easing, box-shadow $duration $easing;
}
.btn:active {
  transform: scale(.96);
  box-shadow: $shadow-press;
}

02저장 체크 애니메이션

무엇이 보이나

저장 버튼을 누르면 디스켓 모양이 scale·opacity로 사라지고, 체크 아이콘 두 획이 scaleX로 순서대로 그어지며, 원형 배지가 팝업 이징으로 부풀어 오릅니다.

저장 체크 애니메이션
새 창에서 열기

02 · 저장 체크 애니메이션 — 측정 누적면적 0.583% · 움직인 프레임 11/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

scaleX 획 긋기색상 전환0.3초

코드에서 볼 곳

.save.is-saved .save__disk {
  animation: disk-out $duration $easing forwards;
}
.save.is-saved .save__check-bar--short {
  animation: check-short $duration $easing forwards;
  animation-delay: $duration * .35;
}
@keyframes check-short {
  to { transform: rotate(45deg) scaleX(1); }
}

03복사 완료 피드백

화면에서 일어나는 일

복사 아이콘을 누르면 겹쳐진 두 사각형이 사라지고 체크 아이콘이 나타나며, 버튼 위 툴팁이 위로 살짝 뜬 채 1.2초 머물다가 사라집니다.

복사 완료 피드백
새 창에서 열기

03 · 복사 완료 피드백 — 측정 누적면적 1.494% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

아이콘 스왑툴팁 페이드1.2초 유지

코드에서 볼 곳

.copy.is-copied .copy__sheet--front,
.copy.is-copied .copy__sheet--back {
  opacity: 0;
  transform: scale(.6);
}
@keyframes tip-float {
  0%   { opacity: 0; transform: translate(-50%, 4px); }
  15%  { opacity: 1; transform: translate(-50%, -2px); }
  85%  { opacity: 1; transform: translate(-50%, -2px); }
  100% { opacity: 0; transform: translate(-50%, -10px); }
}

04낙관적 좋아요(즉시+되돌림)

어떤 움직임인가

하트를 누르면 서버 응답을 기다리지 않고 즉시 채워지고 숫자가 오도미터처럼 한 칸 올라갑니다.

낙관적 좋아요(즉시+되돌림)
새 창에서 열기

04 · 낙관적 좋아요(즉시+되돌림) — 측정 누적면적 0.354% · 움직인 프레임 9/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

낙관적 업데이트롤백 애니메이션0.8초

언제 쓰나

실패로 가정한 흐름에서는 0.8초 뒤 두 값 모두 원래대로 돌아갑니다.

.like__count { position: relative; height: 1em; overflow: hidden; }
.like__count-track {
  display: flex;
  flex-direction: column;
  transition: transform $duration $easing;
}
.like.is-liked .like__heart--fill { opacity: 1; transform: scale(1); }
.like.is-liked .like__count-track { transform: translateY(-50%); }

05스켈레톤→콘텐츠 크로스페이드

무엇이 보이나

같은 자리에 겹쳐 둔 스켈레톤 레이어와 실제 콘텐츠 레이어가 opacity로 교차합니다.

스켈레톤→콘텐츠 크로스페이드
새 창에서 열기

05 · 스켈레톤→콘텐츠 크로스페이드 — 측정 누적면적 3.401% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

opacity 크로스페이드겹친 레이어0.4초

어디에 쓰나

이 마이크로 인터랙션 예시에서 콘텐츠 레이어는 scale(.98→1)도 같이 줘서 자리 잡는 느낌을 살짝 더합니다.

.card { position: relative; height: clamp(110px, 40vw, 130px); }
.card__layer {
  position: absolute; inset: 0;
  transition: opacity $duration $easing, transform $duration $easing;
}
.card__layer--skeleton { opacity: 1; }
.card__layer--content  { opacity: 0; transform: scale(.98); }

06빈 상태 아이콘 바운스

화면에서 일어나는 일

목록이 비었을 때 뜨는 트레이 아이콘이 translateY로 위아래를 부드럽게 오갑니다.

빈 상태 아이콘 바운스
새 창에서 열기

06 · 빈 상태 아이콘 바운스 — 측정 누적면적 1.361% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

translateY 바운스ease-out 이징무한 반복

쓰는 자리

텍스트는 움직이지 않고 아이콘만 반복해서 시선을 붙잡습니다.

.empty.is-demo .empty__icon {
  animation: bounce $duration $easing infinite;
}
@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}

07입력 오류 흔들림

어떤 움직임인가

잘못된 값을 제출하면 입력창을 감싼 프레임이 좌우로 짧게 흔들리고, 겹쳐 둔 빨간 테두리 링이 opacity로 나타나며, 아래 에러 문구가 위로 살짝 뜨며 표시됩니다.

입력 오류 흔들림
새 창에서 열기

07 · 입력 오류 흔들림 — 측정 누적면적 3.311% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

translateX 흔들림빨강 테두리0.4초

코드에서 볼 곳

.field__ring {
  position: absolute; inset: 0;
  border: 2.5px solid $error;
  opacity: 0;
}
.field.is-error .field__frame { animation: shake $duration $easing both; }
.field.is-error .field__ring  { opacity: 1; }
@keyframes shake {
  20%, 80% { transform: translateX($amount); }
  30%, 70% { transform: translateX(-$amount); }
  50%      { transform: translateX(-$amount * .7); }
}

08탭 인디케이터 슬라이드

무엇이 보이나

탭 세 칸을 grid-template-columns: repeat(3, 1fr)로 똑같은 폭으로 맞춰 두면, 인디케이터는 고정 폭에서 translateX를 100% 배수로만 옮기면 됩니다.

탭 인디케이터 슬라이드
새 창에서 열기

08 · 탭 인디케이터 슬라이드 — 측정 누적면적 0.5% · 움직인 프레임 11/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

transform 슬라이드width 보간0.25초

어디에 쓰나

폭 자체를 다시 재는 스크립트가 필요 없습니다.

.tabs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
.tabs__indicator {
  width: calc((100% - 8px) / 3);
  transform: translateX(calc(var(--i, 0) * 100%));
  transition: transform $duration $easing;
}

09토스트 쌓임

화면에서 일어나는 일

토스트 세 개가 순서대로 아래에서 올라와 쌓입니다.

토스트 쌓임
새 창에서 열기

09 · 토스트 쌓임 — 측정 누적면적 19.122% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

translateY 밀림scale 축소순차 스택

쓰는 자리

새 토스트가 뜰 때마다 기존 토스트는 고정된 슬롯 높이만큼 translateY로 밀려 올라가며 scale도 함께 줄어듭니다.

.toasts__list.is-demo .toast--1 {
  animation: slot-1 $duration $easing infinite;
}
@keyframes slot-1 {
  12%, 38% { opacity: 1;   transform: translateY(0) scale(1); }
  50%, 68% { opacity: .88; transform: translateY(-42px) scale(.96); }
  80%, 92% { opacity: .7;  transform: translateY(-84px) scale(.92); }
}

어디서 깨지는가 — 함정 하나

겹친 레이어와 부모 높이

05의 스켈레톤·콘텐츠 크로스페이드는 두 레이어를 position: absolute로 겹쳐 두는 방식이라, 부모 .card에 height를 직접 정해 주지 않으면 자식이 없는 것처럼 계산돼 카드가 0으로 접힙니다. absolute 요소는 문서 흐름에서 빠지므로 부모 높이에 기여하지 못한다는 점을 MDN의 CSS 위치 지정 레이아웃 문서가 설명합니다. 눌림·체크 같은 반응에 재질까지 얹고 싶어지면 스큐어모피즘 디자인 랜딩 키트가 01의 스케일을 질감 있는 부품으로 확장합니다. 09의 토스트 스택도 비슷한 값을 미리 정해 두는 방식이라, 슬롯 높이(42px)를 코드에 고정해 뒀습니다. 언어팩을 입혀 글자가 길어지는 en·ja 판에서는 문구가 이 높이를 넘지 않도록 clamp()로 글자 크기를 줄여 확인했고, 이 값이 반영된 아홉 개 파일의 압축 비밀번호는 dawuywyc이고, 아래 버튼으로 받을 수 있습니다.

체크 아이콘을 SVG 로

02와 03의 체크 아이콘은 SVG 대신 border와 회전한 div로 그렸습니다. 아이콘 크기를 바꾸면 rotate(-52deg)처럼 손으로 맞춘 각도도 다시 계산해야 하는데, 이건 SVG path를 쓰면 안 생기는 수고입니다. 크기를 자주 바꿀 아이콘이라면 처음부터 SVG로 그리는 편이 낫습니다.

접근성

동작 줄이기를 켰을 때

아홉 개 전부 prefers-reduced-motion: reduce에서 반복 애니메이션을 끕니다. 다만 상태 전환 자체는 지우지 않는 게 원칙이라, 04의 좋아요 표시나 07의 에러 링처럼 값이 바뀌었다는 사실은 트랜지션 없이 즉시 남습니다. 07의 입력 필드는 role="alert"로 에러 문구를 스크린 리더에 바로 읽어 주고, 04의 좋아요 버튼은 aria-pressed로 지금 상태를 알립니다. 08의 탭은 role="tab"과 aria-selected를 함께 씁니다. 어떤 기준으로 이 설정이 켜지는지는 MDN의 @media prefers-reduced-motion 문서에서 확인할 수 있습니다.

@media (prefers-reduced-motion: reduce) {
  .field__ring, .field__msg { transition: none; }
  .field.is-demo .field__frame { animation: none !important; }
}

다른 CSS 애니메이션 주제는 CSS 애니메이션 카테고리에 모여 있고, 이 사이트가 무엇을 다루는지는 소개에 있습니다.

FAQ

마이크로 인터랙션 ui와 일반 CSS 애니메이션은 뭐가 다릅니까?

마이크로 인터랙션은 배너 등장처럼 장식이 아니라 클릭·저장·오류처럼 사용자가 한 행동에 대한 응답입니다. 그래서 지속 시간이 짧고(220~600ms), 실제 상태 변화와 묶여 있어야 합니다. 이 글의 아홉 개는 전부 실제 클릭이나 입력에 반응하도록 짠 이유가 여기 있습니다. 클릭에 응답하는 대표 부품인 체크박스와 라디오만 모은 CSS 체크박스 커스텀 라디오 애니메이션도 같은 원칙으로 묶여 있습니다.

04의 낙관적 업데이트는 실제 서버 요청과 어떻게 연결합니까?

React 판의 onToggle 같은 콜백에 실제 요청을 넣고, 성공하면 true를 돌려주면 됩니다. 실패하거나 콜백이 없으면 정해 둔 시간(기본 800ms) 뒤 화면이 자동으로 원래 값으로 돌아갑니다. zip의 react/ 폴더에 전체 로직이 있습니다.

아홉 개를 전부 한 페이지에 같이 써도 됩니까?

네, 흔한 조합입니다. 다만 08처럼 .is-demo 클래스로 자동 반복시켜 둔 요소는 실제 화면에서는 사용자의 진짜 상태(클릭·오류 여부)로만 움직이게 그 클래스를 지우고 시작해야 합니다. zip의 vanilla/ 폴더 각 파일 안 스크립트가 그 처리를 그대로 담고 있습니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.