box-shadow 구문
box-shadow: [inset] offset-x offset-y [blur] [spread] color;
- offset-x / offset-y: 그림자 위치입니다. x가 양수면 오른쪽, y가 양수면 아래로 갑니다.
- blur: 그림자가 얼마나 부드러운지입니다. 0이면 경계가 선명합니다.
- spread: 그림자가 요소 바깥으로 얼마나 퍼지는지입니다. 음수도 됩니다.
- inset: 그림자를 요소 안쪽으로 옮깁니다.
- 쉼표로 구분해 여러 그림자를 겹칠 수 있습니다.
기법 1: 겹친 그림자로 진짜 입체감 내기
그림자 하나는 평평하고 흔해 보입니다. 흐림 반경과 불투명도가 다른 그림자를 2~3개 겹치면 전문적인 디자인 시스템에서 보는 깊이가 나옵니다:
/* 머티리얼 디자인 스타일의 고도(elevation) */
.card--elevated {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.30),
0 4px 8px rgba(0, 0, 0, 0.25),
0 16px 32px rgba(0, 0, 0, 0.20);
}
가장 작은 그림자가 또렷한 가장자리를 만들고, 가장 큰 그림자가 주변광 역할을 합니다. 둘이 함께 있을 때 물리적으로 설득력이 생깁니다.
기법 2: 컬러 발광 효과
오프셋 없이 색이 있는 그림자를 쓰면 발광이 됩니다. 여기에 spread를 더하면 더 강해집니다:
/* 주요 버튼의 보라색 발광 */
.btn-primary {
background: #7c5cfc;
box-shadow:
0 0 0 0 transparent, /* 링 오프셋 */
0 8px 24px rgba(124,92,252,0.45), /* 주변 발광 */
0 0 60px rgba(124,92,252,0.15); /* 멀리 퍼지는 후광 */
transition: box-shadow 0.3s ease;
}
.btn-primary:hover {
box-shadow:
0 0 0 0 transparent,
0 12px 32px rgba(124,92,252,0.65),
0 0 80px rgba(124,92,252,0.25);
}
기법 3: 뉴모피즘 (소프트 UI)
뉴모피즘은 밝은 그림자와 어두운 그림자를 서로 반대 방향으로 두어 표면에서 밀려 나온 듯한 느낌을 만듭니다. 요소의 배경색이 컨테이너 배경색과 정확히 같아야 합니다:
:root { --bg: #e0e5ec; }
.neomorphic {
background: var(--bg);
border-radius: 16px;
box-shadow:
8px 8px 16px #b8bec7, /* 어두운 그림자 (오른쪽 아래) */
-8px -8px 16px #ffffff; /* 밝은 그림자 (왼쪽 위) */
}
/* 눌린 상태 */
.neomorphic:active {
box-shadow:
inset 6px 6px 12px #b8bec7,
inset -6px -6px 12px #ffffff;
}
접근성 경고: 뉴모피즘은 요소와 배경 사이 대비가 매우 낮아지는 경우가 많습니다. 명도 대비가 WCAG AA를 통과하는지 반드시 확인하세요. StudioLimb의 대비 검사기를 쓰면 됩니다.
기법 4: 안쪽 그림자로 깊이 내기
안쪽 그림자는 파이거나 새겨진 느낌을 만듭니다. 입력 필드, 우묵한 영역, 눌린 버튼 상태에 잘 맞습니다:
/* 다크 모드 입력 필드 */
.input {
background: rgba(0, 0, 0, 0.3);
box-shadow:
inset 0 2px 4px rgba(0, 0, 0, 0.6),
inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}
.input:focus {
box-shadow:
inset 0 2px 4px rgba(0, 0, 0, 0.6),
0 0 0 2px rgba(124, 92, 252, 0.6);
}
기법 5: 음수 spread로 좁은 그림자 만들기
spread에 음수를 주면 그림자가 줄어듭니다. 옆으로 퍼지지 않고 요소 아래에만 그림자가 생겨서, 표면 위에 떠 있는 것처럼 보입니다:
/* 옆이 아니라 아래에만 그림자가 생김 */
.floating {
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.5);
/* ^^^ 음수 spread */
}
성능: box-shadow와 filter: drop-shadow()
box-shadow는 모든 최신 브라우저에서 GPU 가속되며 대부분의 경우 올바른 선택입니다. filter: drop-shadow()는 요소의 알파 채널을 따라가므로 투명한 PNG의 잘린 모양까지 반영하지만, 비용이 더 크고 애니메이션되는 요소에는 쓰면 안 됩니다. 모양을 따라가는 동작이 꼭 필요한 경우가 아니라면 box-shadow를 쓰세요.
트랜지션 팁
스크롤이나 호버에서 여러 요소의 box-shadow를 동시에 애니메이션하지 마세요. 대신 목표 그림자를 가진 의사 요소의 opacity를 애니메이션하세요. 이쪽이 GPU에서 합성되어 훨씬 부드럽습니다.
/* 성능이 좋은 그림자 호버 애니메이션 */
.card {
position: relative;
transition: transform 0.2s ease;
}
.card::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
}
.card:hover::after {
opacity: 1;
}
기법 6: 테두리로 쓰는 박스 섀도우 (블러 0 트릭)
블러와 오프셋을 모두 0으로 두면 테두리처럼 보이는 윤곽선이 생깁니다. border와 달리 박스 모델에 더해지지 않으므로 레이아웃에 영향을 주지 않습니다. 여러 겹의 "테두리"를 만들거나, 레이아웃이 밀리지 않는 포커스 링을 만들 때 유용합니다:
/* 레이아웃에 영향을 주지 않고 겹쳐 쓸 수 있음 */
.multi-border {
box-shadow:
0 0 0 2px #7c5cfc, /* 안쪽 링 */
0 0 0 5px rgba(124,92,252,0.2); /* 바깥 발광 링 */
}
/* 레이아웃이 밀리지 않는 포커스 링 */
.btn:focus-visible {
outline: none;
box-shadow:
0 0 0 2px #050508, /* 간격 */
0 0 0 4px #7c5cfc; /* 보이는 링 */
}
섀도우 디자인 토큰 시스템 만들기
전문적인 디자인 시스템은 그림자를 일회성 값이 아니라 이름 있는 토큰으로 정의합니다. 컴포넌트 사이의 일관성이 유지되고, 전체를 한 번에 바꾸기도 쉬워집니다:
:root {
/* 고도 스케일 */
--shadow-xs: 0 1px 2px rgba(0,0,0,0.2);
--shadow-sm: 0 1px 2px rgba(0,0,0,0.2), 0 4px 8px rgba(0,0,0,0.15);
--shadow-md: 0 1px 2px rgba(0,0,0,0.25), 0 8px 16px rgba(0,0,0,0.2), 0 20px 40px rgba(0,0,0,0.15);
--shadow-lg: 0 2px 4px rgba(0,0,0,0.3), 0 16px 32px rgba(0,0,0,0.25), 0 40px 80px rgba(0,0,0,0.2);
/* 의미 기반 그림자 */
--shadow-card: var(--shadow-sm);
--shadow-modal: var(--shadow-lg);
--shadow-button: var(--shadow-xs);
/* 컬러 발광 */
--shadow-primary-glow: 0 8px 24px rgba(124,92,252,0.4);
--shadow-danger-glow: 0 8px 24px rgba(239,68,68,0.4);
}
/* 사용 */
.card { box-shadow: var(--shadow-card); }
.modal { box-shadow: var(--shadow-modal); }
.btn-cta { box-shadow: var(--shadow-primary-glow); }
흔한 실수
1. 모든 곳에 그림자 하나만 쓰기
흐림 반경 하나짜리 그림자는 평평하고 흔해 보입니다. 2006년부터 포토샵 사용자들이 써 온 바로 그 그림자입니다. 흐림 반경이 다른 2~3개 층을 쓰세요. CSS 두 줄을 더 쓸 값어치는 언제나 있습니다.
2. 어두운 배경에 검은 그림자
어두운 배경 위의 순수한 검정 그림자(rgba(0,0,0,X))는 탁해 보입니다. 표면 색과 맞는 색조를 살짝 넣으세요. 짙은 파란 배경이라면 파란빛이 도는 그림자(rgba(10,10,30,X))를 쓰면 됩니다.
3. spread를 키워 그림자가 너무 커지는 것
양수 spread를 크게 주면 그림자가 요소 경계를 훨씬 넘어가면서 보기 싫은 후광이 생깁니다. 대부분의 경우 spread는 0이나 음수로 두세요. 양수 spread는 의도한 발광 효과에만 쓰세요.
4. box-shadow를 직접 애니메이션하기
box-shadow를 애니메이션하면 브라우저가 매 프레임 다시 그려야 합니다. 복잡한 페이지에서는 비용이 큽니다. 호버 전환에는 위에서 설명한 의사 요소 불투명도 기법을 쓰세요.
5. 다크 모드를 잊는 것
흰 배경에서 근사해 보이는 그림자가 어두운 배경에서는 아예 보이지 않는 경우가 많습니다. 라이트 모드와 다크 모드용 그림자 토큰을 따로 만들거나, @media (prefers-color-scheme: dark)로 교체하세요.
자주 묻는 질문
- box-shadow는 최대 몇 겹까지 쓰는 게 좋나요?
- 3겹이면 거의 항상 충분하고 실무의 모든 요구를 감당합니다. 그 이상은 대부분의 사용자가 차이를 느끼지 못하는 반면 성능 비용은 남습니다. 구글의 머티리얼 디자인도 그림자 시스템에 정확히 3개의 고도 층을 씁니다.
- 인라인 요소에도 box-shadow를 쓸 수 있나요?
- 쓸 수 있지만 동작이 다릅니다. 텍스트가 줄바꿈되면 각 줄 상자마다 따로 렌더링됩니다. 인라인 텍스트 강조에는
background와 패딩을 조합하는 편이 모양을 더 잘 제어할 수 있습니다. - 성능은 box-shadow와 filter: drop-shadow() 중 어느 쪽이 낫나요?
box-shadow는 모든 최신 브라우저에서 GPU로 합성되며 대부분의 경우 더 나은 선택입니다.filter: drop-shadow()는 요소의 알파 마스크를 따라가므로 잘라낸 PNG에는 유용하지만, CPU에서 다시 그려지므로 애니메이션되는 요소에는 쓰지 마세요.- 아래쪽에만 그림자를 만들려면 어떻게 하나요?
- 음수 spread로 옆면에 그림자가 생기지 않게 하세요.
box-shadow: 0 20px 30px -15px rgba(0,0,0,0.5)처럼 씁니다.-15pxspread가 그림자를 안쪽으로 당겨서 아래쪽에만 남습니다.