---
title: "background-clip: content-box 는 border-radius 도 깎는다"
description: "background-clip을 content-box로 설정하면 padding 두께만큼 border-radius가 깎여 의도치 않게 각진 모양이 되는데, 이를 해결하려면 슬래시 문법을 사용해 가로와 세로 반경을 개별적으로 보정해야 합니다"
date: 2026-08-05
updated: 2026-08-05T10:25:50.090Z
tags: [css, ui, border-radius, frontend]
canonical: https://blog.wooncloud.com/posts/background-clip-shrinks-border-radius
---

![background-clip 이 모서리 반경을 깎는 모습](/images/posts/background-clip-shrinks-border-radius/3750d6e8-8856-4df8-a39f-0e59286eff08.webp)

on/off 스위치를 `<button>` 하나로 그렸다. 트랙은 button 자신의 배경이고, knob 은 `position: absolute` 인 자식 하나. 트랙을 자식으로 중첩하면 부모 flex 에서 눌려 knob 이 밖으로 삐져나오는 문제가 있어서 이 구조로 고정해 뒀다.

모바일 터치 타겟 44px 을 맞추려면 22px 짜리 트랙에 세로 여백이 필요하다. 그런데 `padding` 을 주면 배경도 같이 커진다. 그래서 `box-sizing: content-box` 로 padding 이 크기를 밀어내게 하고, `background-clip: content-box` 로 배경은 content box 안에만 그리게 했다. 히트 영역은 44px, 보이는 트랙은 50×22.

여기까지는 잘 됐는데, `border-radius: 11px`(높이의 절반)을 준 트랙이 **pill 이 아니라 각진 사각형**으로 렌더됐다.

```css
.track {
  box-sizing: content-box;
  width: 50px;
  height: 22px;
  padding: 11px 0;            /* 44px 히트 영역 */
  border-radius: 11px;        /* pill 이어야 하는데... */
  background: #5b40f8;
  background-clip: content-box;
}
```

## computed style 은 거짓말을 하지 않는다, 다만 답도 아니다

DevTools 에서 확인한 `border-radius` 는 `11px` 그대로다. 지정한 값이 무시된 게 아니다. 문제는 **배경을 자를 때 쓰는 반경이 지정값과 다르다**는 것.

CSS Backgrounds & Borders Level 3 은 padding edge 와 content edge 의 모서리 반경을 이렇게 정의한다: 바깥 반경에서 **그 변의 border/padding 두께를 뺀 값**. 음수면 0.

여기 세로 padding 이 11px 이니까,

- 가로 반경: `11 - 0 = 11` (좌우 padding 은 0)
- 세로 반경: `11 - 11 = 0`

세로 반경이 0 이면 곡선이 사라진다. 각진 사각형이 나온 이유다. `background-clip: border-box`(기본값)였다면 바깥 반경을 그대로 써서 멀쩡했을 텐데, content box 로 자르는 순간 이 계산이 끼어든다.

## 큰 값으로 밀어붙이면 이번엔 타원이 된다

반경이 깎이는 게 문제라면 넉넉하게 주면 되지 않나 — `border-radius: 999px` 로 바꿔봤다. 결과는 더 나빴다. 좌우가 과하게 부푼 **타원**이 되면서 knob 이 트랙 밖으로 삐져나왔다.

큰 값은 먼저 border box 기준으로 클램프된다. border box 는 50×44(padding 포함)이므로 반경이 22 로 잘리고, 거기서 padding 을 빼면

- 가로 반경: `22 - 0 = 22`
- 세로 반경: `22 - 11 = 11`

가로 22 / 세로 11 의 타원. 좌우 곡선이 트랙 안쪽 깊숙이 파고들기 때문에, 오른쪽 끝에 붙인 knob 의 위아래 모서리가 트랙 바깥으로 노출된다.

단일 값으로는 이 문제를 풀 수 없다. 가로와 서로 다른 양만큼 깎이는데 뺄 값을 하나만 줄 수 있기 때문이다.

## 슬래시 문법으로 가로·세로를 따로 준다

`border-radius` 는 `/` 로 가로 반경과 세로 반경을 분리해서 쓸 수 있다. 원하는 최종 반경 `r` 에 대해, **가로는 `r`, 세로는 `r + 세로 padding`** 을 주면 깎인 뒤에 정확히 `r` 이 남는다.

```css
.track {
  box-sizing: content-box;
  width: 50px;
  height: 22px;
  padding: 11px 0;
  border-radius: 11px / 22px; /* 가로 11 · 세로 11+11 → content box 에서 정확히 pill */
  background: #5b40f8;
  background-clip: content-box;
}
```

일반화하면 `border-radius: r / calc(r + <padding-block>)`. 좌우에도 padding 이 있다면 가로 쪽에도 같은 보정이 붙는다.

## 테스트로 못박을 때는 값이 아니라 관계를

이런 버그는 값을 단언하는 테스트로는 못 잡는다. `border-radius: 11px` 도 통과하고 `999px` 도 통과한다. 둘 다 화면에서는 깨져 있는데.

깨지는 조건이 padding 과의 관계이므로, 단언도 관계로 써야 한다.

```ts
const [h, v] = el.style.borderRadius.split("/").map(parseFloat);
const padY = parseFloat(el.style.padding);   // "11px 0px" → 11
const height = parseFloat(el.style.height);

expect(h).toBe(height / 2);          // 좌우 padding 0 → 깎이지 않는다
expect(v - padY).toBe(height / 2);   // 상하 padding 만큼 미리 더해 둔다
```

그리고 이 계열의 버그는 결국 **실제 브라우저에 띄워 눈으로 확인해야** 확정된다. jsdom 은 배경 클리핑을 계산하지 않고, computed style 은 클립 후의 실효 반경을 알려주지 않는다. 후보 값 몇 개를 한 페이지에 나란히 놓고 스크린샷을 찍는 게 제일 빠르다.

## 정리

- `background-clip` 을 `padding-box` 나 `content-box` 로 바꾸면 모서리 반경도 **그 변의 두께만큼 깎여서** 적용된다.
- 가로·세로 padding 이 다르면 단일 반경 값으로는 원하는 모양이 안 나온다. `r / (r + padding)` 처럼 분리해서 준다.
- 히트 영역을 padding 으로 벌리는 패턴(스위치, 작은 아이콘 버튼, 칩)에서 특히 자주 만난다.

## 참고

- [CSS Backgrounds 3 — Corner Shaping](https://www.w3.org/TR/css-backgrounds-3/#corner-shaping)
- [MDN — background-clip](https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip)
- [MDN — border-radius](https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius)
