CSS를 쓰다 보면 font-size: 14px 로 쓸지 0.875rem 으로 쓸지, 버튼 패딩은 em 으로 줄지 매번 고민돼요. 결론부터 말하면 px는 고정 크기, rem은 루트(html) 글자 크기 기준(브라우저 기본 16px), em은 부모(또는 자기 자신)의 글자 크기 기준이에요. 글자 크기와 간격은 rem, 글자 크기에 비례해야 하는 컴포넌트 안쪽 여백은 em, 1px 테두리·그림자처럼 커지면 어색한 것은 px로 쓰는 게 기본 원칙이에요.
이 원칙이 왜 생겼는지 알면 헷갈릴 일이 없어요. em이 중첩되면 크기가 복리처럼 곱해지는 문제, 사용자가 브라우저 글자 크기를 키웠을 때 px만 안 커지는 접근성 문제, 유명한 html { font-size: 62.5% } 트릭의 함정까지 실제 숫자로 계산해서 정리했어요.
핵심 요약
rem은 항상 html 요소의 font-size를 기준으로 해서 어디에 써도 값이 같아요. 기본값 16px이면 1rem = 16px, 0.875rem = 14px이에요.
em은 font-size 속성에서는 부모 글자 크기, 다른 속성(padding 등)에서는 자기 글자 크기 기준이에요. 0.8em을 4단계 중첩하면 16px이 6.55px까지 줄어들어요.
브라우저 설정에서 글자 크기를 키우면 rem·em으로 지정한 글자는 따라 커지지만 px 글자는 그대로예요. 그래서 글자 크기는 rem이 접근성에 유리해요.
미디어 쿼리 안의 em·rem은 html의 font-size가 아니라 브라우저 초기값 기준이라, 48em은 기본 설정에서 768px이에요.
px, rem, em 차이 한 번에 보기
세 단위의 기준을 표로 먼저 정리할게요.
| 단위 | 기준 | 1의 크기(기본 설정) | 브라우저 글자 크기 설정 반영 | 주 용도 |
|---|---|---|---|---|
| px | CSS 픽셀(고정) | 1px | 반영 안 됨 (화면 확대는 반영) | 테두리, 그림자, 아이콘 선 굵기 |
| rem | html 요소의 font-size | 16px | 반영됨 | 글자 크기, 레이아웃 간격 |
| em | font-size에서는 부모, 그 외 속성은 자기 font-size | 상황마다 다름 | 반영됨 | 버튼·배지 안쪽 여백, 아이콘 크기 |
rem의 r은 root예요. 문서 최상위 요소인 html 의 글자 크기를 기준으로 하니까, 어느 깊이의 요소에 쓰든 1rem 은 같은 크기예요. 반면 em은 "지금 이 요소가 놓인 문맥의 글자 크기"를 기준으로 해서, 같은 1em 이라도 위치에 따라 크기가 달라져요. 정의는 W3C CSS Values and Units Level 4의 font-relative lengths 항목에 있어요.
헷갈리기 쉬운 부분이 em의 기준이에요. font-size: 1.5em 처럼 font-size 속성 자체에 em을 쓰면 부모의 글자 크기가 기준이고, padding: 0.5em 처럼 다른 속성에 쓰면 그 요소 자신의 (계산된) 글자 크기가 기준이에요.
.card { font-size: 20px; }
.card h2 { font-size: 1.5em; /* 부모 20px × 1.5 = 30px */
margin-bottom: 0.5em; /* 자기 30px × 0.5 = 15px */ }
.card p { font-size: 0.875rem; /* html 16px × 0.875 = 14px, 부모와 무관 */ }
em 중첩 문제, 복리처럼 곱해지는 계산
em의 가장 유명한 함정은 중첩이에요. 목록 글자를 조금 작게 하려고 li { font-size: 0.8em; } 을 썼다고 해 볼게요. 목록 안에 목록이 들어가면 이렇게 돼요.
- 1단계 li: 16 × 0.8 = 12.8px
- 2단계 li: 12.8 × 0.8 = 10.24px
- 3단계 li: 10.24 × 0.8 = 8.192px
- 4단계 li: 8.192 × 0.8 = 6.5536px
4단계면 원래의 41% 크기라 사실상 못 읽어요. 반대로 1.2em 을 중첩하면 19.2 → 23.04 → 27.648 → 33.1776px로 커져요. 같은 코드를 0.8rem 으로 바꾸면 깊이와 상관없이 전부 12.8px이에요.
그래서 글자 크기(font-size)에는 em보다 rem을 쓰는 게 안전해요. em이 의도대로 동작하는 건 "부모가 커지면 같이 커져야 하는" 경우예요. 예를 들어 버튼의 패딩을 0.5em 1em 으로 주면 .btn-sm { font-size: 0.875rem }, .btn-lg { font-size: 1.25rem } 처럼 글자 크기만 바꿔도 패딩이 비율대로 따라와요. 크기별로 패딩을 따로 정의할 필요가 없어지죠.
.btn { padding: 0.5em 1em; border: 1px solid; border-radius: 0.375em; }
.btn-sm { font-size: 0.875rem; } /* 패딩 7px 14px */
.btn-lg { font-size: 1.25rem; } /* 패딩 10px 20px */
브라우저 글자 크기 설정과 rem — 접근성 차이
px와 rem의 실질적인 차이는 사용자가 브라우저 설정에서 기본 글자 크기를 바꿨을 때 드러나요. 크롬은 설정 > 모양 > 글꼴 크기에서 아주 작게·작게·보통·크게·아주 크게를 고를 수 있고, 이는 기본 글자 크기 9·12·16·20·24px에 해당해요. 시력이 약한 사용자는 이 설정을 크게 바꿔 두고 쓰는 경우가 많아요.
이때 html의 font-size를 직접 지정하지 않았다면 1rem이 그 값이 돼요. 본문을 1rem 으로 쓴 사이트는 따라서 커지고, 16px 로 박아 둔 사이트는 그대로예요.
"요즘 브라우저는 확대(줌)하면 px도 커지니까 상관없다"는 말도 반은 맞아요. 페이지 확대는 모든 것을 키우니 px 글자도 커져요. 하지만 글꼴 크기 설정만 바꾼 사용자는 px 사이트에서 아무 변화도 못 느껴요. WCAG 1.4.4 텍스트 크기 조정 기준은 글자를 200%까지 키워도 내용과 기능이 유지돼야 한다고 요구하는데, rem으로 짜 두면 두 가지 확대 방식 모두에 자연스럽게 대응돼요. 색 대비 같은 다른 접근성 기준은 WCAG 명도 대비 4.5:1 기준 글에 정리했어요.
가장 흔한 실수는 html { font-size: 16px; } 처럼 루트를 px로 고정하는 거예요. 이러면 rem을 아무리 열심히 써도 사용자 설정이 무시돼요. 루트를 바꾸고 싶다면 100%, 112.5% 같은 퍼센트로 지정하세요.
html font-size 62.5% 트릭의 장단점
계산 편의를 위해 html { font-size: 62.5%; } 를 쓰는 패턴이 있어요. 16px × 0.625 = 10px이 되니까 1.4rem = 14px, 2.4rem = 24px처럼 px 값을 10으로 나누기만 하면 돼요.
html { font-size: 62.5%; } /* 16px × 0.625 = 10px */
body { font-size: 1.6rem; } /* 다시 16px로 복구 */
h1 { font-size: 3.2rem; } /* 32px */
퍼센트로 지정했으니 사용자가 기본 글자를 20px로 바꾸면 루트가 12.5px이 되어 비율은 유지돼요. 접근성 자체가 망가지는 건 아니에요. 다만 단점이 있어요.
- body 복구를 잊으면 사고: body에 1.6rem을 안 주면 rem을 쓰지 않은 요소, 서드파티 위젯, 브라우저 기본 스타일이 전부 10px로 보여요.
- 외부 라이브러리와 충돌: 1rem = 16px을 전제로 만든 UI 라이브러리나 디자인 시스템 컴포넌트가 모두 62.5% 크기로 줄어들어요.
- 미디어 쿼리에는 안 먹힘: 아래에서 설명하듯 미디어 쿼리의 rem은 html의 font-size를 무시해서,
@media (min-width: 76.8rem)은 768px이 아니라 1228.8px이 돼요.
요즘은 트릭 대신 루트를 기본값 그대로 두고, 변환은 Sass 함수나 px rem 변환기에 맡기는 쪽을 더 많이 권해요. 16으로 나누는 계산은 도구가 하면 되니까요.
px → rem 변환표 (루트 16px 기준)
공식은 rem = px ÷ 루트 글자 크기(16), 거꾸로 px = rem × 16이에요. 자주 쓰는 값을 정확히 계산해 뒀어요. 62.5% 트릭을 쓸 때의 값도 함께 적었어요.
| px | rem (루트 16px) | rem (62.5% 트릭, 루트 10px) | 주로 쓰는 곳 |
|---|---|---|---|
| 4 | 0.25 | 0.4 | 작은 간격 |
| 8 | 0.5 | 0.8 | 간격 기본 단위 |
| 10 | 0.625 | 1 | 캡션 |
| 12 | 0.75 | 1.2 | 보조 텍스트 |
| 13 | 0.8125 | 1.3 | 작은 본문 |
| 14 | 0.875 | 1.4 | 작은 본문, 버튼 |
| 15 | 0.9375 | 1.5 | 본문 |
| 16 | 1 | 1.6 | 기본 본문 |
| 18 | 1.125 | 1.8 | 큰 본문, h4 |
| 20 | 1.25 | 2 | h3 |
| 24 | 1.5 | 2.4 | h2, 간격 |
| 28 | 1.75 | 2.8 | h2 |
| 32 | 2 | 3.2 | h1, 섹션 간격 |
| 40 | 2.5 | 4 | 큰 제목 |
| 48 | 3 | 4.8 | 히어로 제목 |
| 64 | 4 | 6.4 | 섹션 여백 |
13px(0.8125rem), 15px(0.9375rem)처럼 소수점 넷째 자리까지 나오는 값도 있지만 브라우저는 정확히 계산하니 반올림하지 말고 그대로 쓰는 게 좋아요. 0.81rem으로 줄이면 12.96px이 돼요.
디자인 시안 px를 rem으로 옮기는 순서
피그마 시안은 대부분 px로 나와요. 옮길 때는 이 순서로 하면 깔끔해요.
- 시안의 기준 본문 크기 확인: 본문이 16px이면 그대로 1rem 기준으로 변환해요. 시안이 14px 본문이라도 루트를 바꾸지 말고 본문을 0.875rem으로 쓰는 게 나아요.
- 글자 크기·줄 간격 외 간격은 rem: margin, gap, 섹션 padding을 rem으로 바꾸면 사용자가 글자를 키웠을 때 여백도 비례해서 숨이 막히지 않아요.
- 줄 간격은 단위 없이:
line-height: 1.5처럼 단위 없는 값을 쓰면 자식에게 비율로 상속돼요.1.5em이나24px은 계산된 값이 상속돼서 자식 글자가 커질 때 줄이 겹쳐요. - 테두리·그림자·구분선은 px 유지: 1px 선이 1.25px로 번지면 흐릿해 보여요.
- 컴포넌트 안쪽 여백은 em: 버튼, 배지, 태그처럼 크기 변형이 있는 컴포넌트예요.
vw와 clamp()로 반응형 타이포그래피 만들기
화면 너비에 따라 제목 크기를 부드럽게 바꾸고 싶을 때 vw 를 써요. 1vw는 뷰포트 너비의 1%예요. 그런데 font-size: 2.5vw 만 쓰면 320px 휴대폰에서 8px, 1280px 모니터에서 32px로 양 끝이 극단적이에요. 게다가 vw만으로 지정한 글자는 브라우저 글꼴 크기 설정을 무시해요.
그래서 clamp()로 최솟값·선호값·최댓값을 묶고, 선호값에 rem을 섞어요. 뷰포트 320px에서 16px, 960px에서 24px이 되게 만들어 볼게요.
기울기 = (24 - 16) ÷ (960 - 320) = 0.0125 → 1.25vw
절편 = 16 - 0.0125 × 320 = 12px → 0.75rem
결과 = clamp(1rem, 0.75rem + 1.25vw, 1.5rem)
640px에서 확인하면 12 + 640 × 0.0125 = 20px이에요. 960px 이후로는 최댓값 1.5rem(24px)에서 멈춰요.
clamp는 16~24px 사이에서만 움직이고, 선호값의 rem 부분 덕분에 글꼴 크기 설정도 반영돼요
표로 보기
| 구분 | 2.5vw | clamp(1rem 0.75rem+1.25vw 1.5rem) |
|---|---|---|
| 320 | 8 | 16 |
| 480 | 12 | 18 |
| 640 | 16 | 20 |
| 800 | 20 | 22 |
| 960 | 24 | 24 |
| 1280 | 32 | 24 |
미디어 쿼리에 em을 쓰는 이유
미디어 쿼리의 상대 단위는 특별해요. Media Queries Level 4 명세는 미디어 쿼리 안의 em·rem이 html에 지정한 font-size가 아니라 초기값(브라우저 기본 글자 크기) 을 기준으로 한다고 정해요. 그래서 62.5% 트릭을 써도 미디어 쿼리의 1em은 여전히 16px이에요.
@media (min-width: 48em) { ... } /* 기본 설정에서 768px */
@media (min-width: 64em) { ... } /* 1024px */
em으로 쓰면 장점이 있어요. 사용자가 기본 글자를 20px로 키우면 48em은 960px이 돼요. 글자가 커진 만큼 한 줄에 들어가는 글자 수가 줄어드니, 더 넓은 화면에서도 모바일 레이아웃을 유지해서 글이 비좁게 깨지는 걸 막아줘요. 과거 사파리에서 rem 미디어 쿼리가 확대와 엇갈리게 동작한 버그가 있어 em이 관례로 굳었고, 지금은 em과 rem이 사실상 같게 동작해요. 다만 사내 규칙이 px 브레이크포인트라면 일관성을 지키는 것도 중요해요.
상황별로 어떤 단위를 쓸지 정리
| 대상 | 추천 단위 | 이유 |
|---|---|---|
| 본문·제목 font-size | rem | 중첩 영향 없음, 글꼴 설정 반영 |
| 레이아웃 간격(margin, gap) | rem | 글자와 함께 비례 |
| 버튼·배지 padding, 아이콘 크기 | em | 컴포넌트 글자 크기를 따라감 |
| line-height | 단위 없음 | 비율로 상속 |
| border, box-shadow, outline | px | 번지지 않는 선명한 선 |
| 반응형 제목 | clamp(rem, rem + vw, rem) | 범위 제한 + 설정 반영 |
| 미디어 쿼리 | em | 초기값 기준, 글꼴 설정 반영 |
| 최대 너비(max-width) | rem 또는 ch | 글자 수 기준 가독 폭 |
간격 체계를 rem으로 정해 두면 색처럼 토큰으로 관리하기도 좋아요. 색 단계 토큰을 만드는 방식은 디자인 시스템 컬러 스케일 글에 있어요.
자주 묻는 질문
rem과 em 중 하나만 써야 하나요?
아니에요. 글자 크기와 레이아웃 간격은 rem, 컴포넌트 안쪽 여백처럼 그 요소의 글자 크기에 비례해야 하는 값은 em으로 섞어 쓰는 게 일반적이에요. font-size에 em을 쓰는 것만 피하면 중첩 문제는 거의 생기지 않아요.
1rem은 항상 16px인가요?
아니에요. 1rem은 html 요소의 font-size예요. 브라우저 기본값이 16px이라 대부분 16px이지만, 사용자가 글꼴 크기 설정을 바꾸거나 사이트가 html의 font-size를 바꾸면 달라져요. 그래서 rem을 쓰는 의미가 있어요.
px를 쓰면 접근성 위반인가요?
px 자체가 위반은 아니에요. 페이지 확대는 px 글자도 키우니까요. 다만 글꼴 크기 설정만 바꾸는 사용자에게는 반영되지 않아서, 글자 크기만큼은 rem으로 쓰는 게 더 많은 사용자를 배려하는 방법이에요. 테두리나 그림자는 px로 써도 괜찮아요.
62.5% 트릭은 써도 되나요?
퍼센트로 지정하니 접근성이 깨지진 않아요. 하지만 body 복구를 빠뜨리거나 1rem = 16px을 전제로 한 라이브러리와 섞이면 크기가 틀어지고, 미디어 쿼리에서는 적용되지 않아 혼란이 생겨요. 새 프로젝트라면 루트를 기본값으로 두고 변환 도구를 쓰는 편이 안전해요.
14px은 몇 rem인가요?
루트 16px 기준으로 14 ÷ 16 = 0.875rem이에요. 12px은 0.75rem, 18px은 1.125rem, 24px은 1.5rem이에요. 다른 값은 위 변환표나 px rem 변환기로 바로 확인할 수 있어요.
정리
px는 고정, rem은 html 글자 크기 기준, em은 부모(font-size일 때) 또는 자기 글자 크기 기준이에요. 글자 크기에 em을 쓰면 0.8em 4단계 중첩만으로 16px이 6.55px이 되니, 글자 크기는 rem, 크기 변형 컴포넌트의 안쪽 여백은 em, 선과 그림자는 px로 나누면 대부분의 상황이 정리돼요. 루트를 px로 고정하지 않는 것, 줄 간격은 단위 없이 쓰는 것, 반응형 제목은 rem을 섞은 clamp()로 범위를 묶는 것까지 지키면 사용자가 글자를 키워도 레이아웃이 버텨요.
시안의 px 값을 옮길 때는 px rem 변환기로 16으로 나눈 값을 바로 확인하세요. 색상 값을 함께 다룬다면 색상 변환기와 HEX·RGB·HSL 차이 글도 도움이 돼요.