🎨 Frontend CSS5. 색·시각 효과03 — Shadows (box·text·inset·multi)

03 — Shadows (box·text·inset·multi)

한 줄 답: box-shadowtext-shadow는 모두 <offset-x> <offset-y> <blur> [spread] <color> 의 5축 함수이고, 콤마로 여러 개를 겹쳐 깊이·층·발광·신경모피즘을 만든다. inset 키워드로 안쪽 그림자가 되며, 모던 패턴은 drop-shadow filter·gradient border와 함께 stacking된다.


Why — 왜 shadow가 시각 위계의 핵심인가

화면은 2D지만 사람의 인지는 z축의 깊이를 본다. UI에서 깊이를 만드는 도구는 셋이다:

  1. Shadow — 광원이 있다는 가정, 가장 보편적.
  2. Filter blur — 거리에 따라 흐려진다는 가정, 모달·툴팁 배경.
  3. Color contrast / lightness — 가까운 건 밝다는 가정.

이 중 shadow가 비용이 가장 싸다 (paint 단계, blur는 합성). 그래서 디자인 시스템의 elevation token은 거의 모두 box-shadow로 구현된다 (Material, iOS, Tailwind, Open Props).


How — 다섯 인자와 스택 순서

다섯 인자

box-shadow: <offset-x> <offset-y> <blur-radius> <spread-radius> <color> [inset];
text-shadow: <offset-x> <offset-y> <blur-radius> <color>;  /* spread, inset 없음 */
인자단위효과
offset-xpx/rem양수=오른쪽, 음수=왼쪽
offset-ypx/rem양수=아래, 음수=위
blur-radiuspx0=선명, 클수록 흐림. 음수 안 됨.
spread-radiuspx양수=더 크게, 음수=작게. box-shadow에만.
colorany안 적으면 currentColor
insetkeyword안쪽 그림자

스택 순서 (콤마로 여러 개)

box-shadow:
  0 1px 2px rgb(0 0 0 / 0.1),     /* 가까운, 선명한 그림자 */
  0 4px 8px rgb(0 0 0 / 0.06),    /* 중간 거리 */
  0 16px 32px rgb(0 0 0 / 0.04);  /* 멀고 부드러운 */

먼저 적은 게 위에 그려진다. (z-index 순서와 반대 아님 — 적힌 순서대로 layer가 쌓임.)


What — 자주 쓰는 6가지 패턴

1) Elevation — Material/Tailwind 스타일

:root {
  --shadow-sm:  0 1px 2px   rgb(0 0 0 / 0.05);
  --shadow:     0 1px 3px   rgb(0 0 0 / 0.1),
                0 1px 2px   rgb(0 0 0 / 0.06);
  --shadow-md:  0 4px 6px   rgb(0 0 0 / 0.07),
                0 2px 4px   rgb(0 0 0 / 0.06);
  --shadow-lg:  0 10px 15px rgb(0 0 0 / 0.1),
                0 4px 6px   rgb(0 0 0 / 0.05);
  --shadow-xl:  0 20px 25px rgb(0 0 0 / 0.1),
                0 10px 10px rgb(0 0 0 / 0.04);
}
.card {  box-shadow: var(--shadow-md);  }

두 개 그림자가 표준: 가까운 작은 그림자가 윤곽을 살리고, 먼 큰 그림자가 분위기를 만든다. 하나만 쓰면 항상 어색하다.

2) Colored Shadow — 색 있는 그림자

.btn-primary {
  background: oklch(62% 0.21 254);
  box-shadow: 0 8px 24px oklch(62% 0.21 254 / 0.4);
}
 
/* relative color로 자동 추적 */
.btn {
  --c: oklch(62% 0.21 254);
  background: var(--c);
  box-shadow: 0 8px 24px oklch(from var(--c) l c h / 0.35);
}

회색 그림자만 쓰면 진흙 같다. 요소의 색과 같은 hue·낮은 알파가 사실상 표준 — 발광하는 느낌.

3) Inset Shadow — 안쪽

/* 눌린 버튼 */
.pressed {
  background: oklch(95% 0 0);
  box-shadow:
    inset 0 1px 2px rgb(0 0 0 / 0.1),
    inset 0 4px 8px rgb(0 0 0 / 0.05);
}
 
/* input focus 안쪽 글로우 */
.input:focus {
  box-shadow: inset 0 0 0 2px oklch(62% 0.21 254);
}

insetborder 안쪽에 그려진다. focus ring을 안쪽으로 넣어 레이아웃 변동 없이 강조하는 트릭에 자주 쓰인다.

4) Neumorphism — 두 방향 그림자

.neumorph {
  background: oklch(94% 0.005 250);
  box-shadow:
    8px 8px 16px oklch(85% 0.005 250),   /* 우하단 어두운 그림자 */
    -8px -8px 16px oklch(99% 0.005 250); /* 좌상단 밝은 하이라이트 */
  border-radius: 16px;
}

좌상에서 빛이 오는 가정 → 우하에 어둠, 좌상에 밝음. 2020년 한때 유행, 지금은 접근성(콘트라스트 부족) 때문에 제한적으로 쓰임.

5) Multi-layer Glow — 발광

.glow {
  --c: oklch(72% 0.2 280);
  box-shadow:
    0 0 5px  var(--c),
    0 0 10px var(--c),
    0 0 20px var(--c),
    0 0 40px oklch(from var(--c) l c h / 0.5);
}

같은 색을 blur 반경만 다르게 4단계로 겹치면 네온 발광 효과. 다크 테마의 강조 요소·로딩 인디케이터에 자주.

6) Text Shadow — 가독성과 임팩트

/* 텍스트 가독성 — 흐린 배경 위에 */
.title {
  text-shadow: 0 1px 2px rgb(0 0 0 / 0.5);
}
 
/* 다중 stroke (윤곽선 흉내) */
.outlined {
  text-shadow:
    -1px -1px 0 black,
     1px -1px 0 black,
    -1px  1px 0 black,
     1px  1px 0 black;
}
/* 모던 대안: -webkit-text-stroke: 1px black */

text-shadowspread, inset이 없다. 그라데이션 텍스트도 못 만든다 — 그건 background-clip: text의 영역 (06-background).


What-if — 잘못 쓰면

1) box-shadow를 transition하면 페인트 폭발

/* 나쁨 — hover마다 paint */
.card {
  box-shadow: var(--shadow);
  transition: box-shadow 0.2s;
}
.card:hover {
  box-shadow: var(--shadow-lg);
}
 
/* 좋음 — pseudo 요소 + opacity transition */
.card {
  position: relative;
  box-shadow: var(--shadow);
}
.card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transition: opacity 0.2s;
  pointer-events: none;
}
.card:hover::after { opacity: 1; }

box-shadow transition은 매 프레임 다시 페인트한다. 큰 카드 리스트의 hover에서 jank가 잘 생긴다. ::after로 미리 그려두고 opacity만 트랜지션하면 합성 단계로 옮겨져 60fps 유지.

2) text-shadow로 그라데이션 텍스트?

/* 안 됨 */
.gradient-text {
  text-shadow: 0 0 0 linear-gradient(red, blue);  /* 문법 자체 안 됨 */
}
 
/* 해답: background-clip: text */
.gradient-text {
  background: linear-gradient(in oklch, red, blue);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

text-shadow단색 + offset만 가능. 그라데이션 텍스트는 06-backgroundbackground-clip: text로.

3) filter: drop-shadow vs box-shadow

/* box-shadow — *상자* 모양 따라 그림자 */
.png {
  background: url(transparent.png);
  box-shadow: 0 8px 16px rgb(0 0 0 / 0.2);
  /* 사각형 상자 그림자 — PNG 투명 영역도 포함 */
}
 
/* drop-shadow — *알파 채널* 모양 따라 그림자 */
.png {
  filter: drop-shadow(0 8px 16px rgb(0 0 0 / 0.2));
  /* PNG 실제 모양대로 그림자 */
}

투명 PNG·SVG·clip-path로 모양 자른 요소는 filter: drop-shadow 가 맞다. 단, filter는 stacking context를 만들기 때문에 z-index 동작이 달라진다 (04-filters).

4) Spread 음수의 바깥 contour 트릭

/* 상자 위쪽만 그림자 */
.top-shadow-only {
  box-shadow: 0 -10px 20px -5px rgb(0 0 0 / 0.3);
  /* spread -5px → 그림자 자체가 5px 작아짐 → 좌우/하단은 안 보임 */
}

음수 spread는 그림자를 작게 만들어 특정 방향만 보이게 하는 트릭. 상단만, 하단만, 한 쪽만 그림자를 그릴 때 사실상 표준.

5) currentColor로 자동 추적

.alert {
  color: oklch(60% 0.2 25);
  box-shadow: 0 4px 12px currentColor;  /* color가 바뀌면 그림자도 함께 */
}

color 한 줄만 바꿔도 border, shadow, gradient(background-clip: text로 만든) 가 함께 따라온다. 테마 시스템의 비밀 무기.


Insight — Shadow의 진화

“2010년대 초 skeuomorphism → flat → material → neumorphism → glassmorphism의 10년”

2007년 iPhone OS의 skeuomorphic(가죽·금속 텍스처)은 다중 그림자의 시대를 열었다. 2013년 iOS 7의 flat design은 그림자를 거의 제거했다. 2014년 Material Design은 elevation이라는 z축 메타포로 그림자를 수학적으로 복귀시켰다 — “높이가 곧 그림자 크기”.

2018년 카드 디자인이 보편화되면서 Tailwind shadow-md / lg / xl이 사실상 표준이 됐다. 흥미로운 점은 Tailwind v1~v3의 그림자가 모두 경험적으로 튜닝된 값이라는 것 — 디자이너가 눈으로 결정한 두 개 그림자의 조합이 사실상 표준이 됐다.

2020년 한때 Neumorphism(soft UI)이 유행했지만, 어두운 그림자 + 밝은 하이라이트의 콘트라스트가 WCAG AA를 못 맞춰서 메인스트림에서 빠르게 사라졌다. 같은 시기 Glassmorphism(backdrop-filter + 반투명 + 약한 그림자) 이 등장해 macOS Big Sur·iOS 7 부활이라 불렸다.

2024년 OKLCH 도입 이후 colored shadow가 다시 주목받는다 — oklch(from var(--c) l c h / 0.4)요소 색을 자동 추적하는 그림자가 가능해졌기 때문.

또 하나의 반전: box-shadow는 GPU 가속을 거의 안 받는다. blur가 들어가면 paint 비용이 blur 반경의 제곱에 비례한다. 큰 카드의 큰 그림자는 생각보다 비싸다 — 100개 그릴 거면 SVG·이미지로 대체하는 게 빠르다.


요약 + Mermaid

  • box-shadow = offset-x · offset-y · blur · spread · color · [inset].
  • text-shadow = spread / inset 없음. 그라데이션 텍스트는 background-clip: text로.
  • 두 개 그림자가 사실상 표준 — 가까운 + 먼.
  • Colored shadow는 요소 색 hue + 낮은 알파가 모던 패턴.
  • transition: box-shadow는 paint 폭발 — pseudo 요소 + opacity transition으로.
  • 투명 영역 있는 요소(SVG·PNG)는 filter: drop-shadow 사용.

다음: 04-filters — drop-shadow도 filter의 일종이다. blur·brightness·backdrop의 GPU 비용도 함께.