지나가던 개발(zigae)

프론트엔드 웹 접근성(a11y) - rem

2022년 10월 15일 • ☕️☕️ 8 min read

장애인 표시 문구

폰트 크기에 픽셀 사용 지양

웹에서 크기를 조정하는 전통적인 단위는 CSS 픽셀이지만 적어도 폰트 크기에는 픽셀을 사용하지 않는 것이 좋다. 픽셀은 디자인 시안을 쉽게 CSS로 변환 할 수 있지만 절대 길이 단위 이므로 하나의 CSS픽셀이 사용자의 화면의 고정된 물리적 픽셀(모니터 및 장치 픽셀) 크기에 해당한다.

96개의 DPI 장치에서는 1/96inch 일 수도 있고, DPI가 다른 장치에서는 다른 값이 될 수 있다. 본 포스팅의 목적은 CSS 픽셀이 DPI가 다른 두 장치 간에 항상 동일한 값을 가지는 것은 아니지만 특정 장치의 고정된 크기를 참조한다는 것이다. Elad Schechter의 글에서 자세한 내용을 확인할 수 있다.

CSS 픽셀은 물리적 측정을 기반으로 하기 때문에 가장 쉬운 단위이다. 그러나 다른 절대 단위와 마찬가지로 15px은 동일한 장치에서 항상 15px으로 확대되지 않는다. 픽셀을 사용하는 것은 시각 장애가 있는 사용자에게 접근성 문제를 일으킬 수 있기 때문에 폰트 크기 조정해야 하는 경우 특히 좋지 않다. 그 이유를 이해하려면 사용자 폰트 크기 기본 설정에 대해 알아야 한다.

픽셀을 사용해서는 안 된다는 의미는 아니다. 픽셀은 패딩, 마진, 그리드, 레이아웃 등 폰트 크기에 의존하지 않는 곳에서 유용하게 사용할 수 있다.

사용자 폰트 크기 기본 설정

모든 브라우저는 도큐먼트에 루트 폰트 크기를 적용하는데, 이는 페이지에 사용자 지정 스타일을 적용하지 않으면 스타일링 되지 않은 본문 폰트가 CSS 16px 으로 렌더링된 폰트 크기를 갖게 된다는 것을 의미한다. 그러나 개발자와 사용자 모두 이 동작을 변경할 수 있다. 개발자는 루트 태그(html)의 폰트 크기를 CSS로 변경하여 모든 태그의 기본 폰트 크기를 변경할 수 있다.

html {
  /* 간단한 예시이다. (실제로 이렇게 하진 않는다) */
  font-size: 18px;
}

마찬가지로 사용자는 브라우저 설정(chrome://settings/fonts)으로 이동하여 원하는 폰트 크기를 지정할 수 있다.

Chrome 폰트 크기 설정

Chrome 폰트 크기 설정

결론은 폰트 크기에 대한 사용자 기본 설정이 항상 CSS보다 우선순위가 높아야 한다. 사용자가 페이지를 보는 방식에 대해 개발자가 가정해서는 안된다. 텍스트를 더 쉽게 읽기 위해 페이지의 글꼴 크기를 확대해야 할 수도 있기 때문에 폰트 크기에 하드 코딩된 픽셀을 사용하면 시각 장애가 있는 사용자가 페이지를 읽을 수 없게 된다. 그러나 위의 예에서와 같이 폰트 크기를 픽셀 단위로 설정하면 사용자가 기본 폰트 크기에 관계 없이 텍스트가 항상 해당 크기로 렌더링 된다. WCAG 2.0의 1.4.4 Resize Text 가이드라인을 준수하기 위해 사용자가 페이지의 폰트 크기를 확대할 수 있도록 해야 한다.

기본 폰트 크기 vs 브라우저 확대/축소

사용자는 브라우저 설정에서 모든 웹 페이지를 전체적으로 확대할 수도 있다. 이 경우 페이지가 비례하게 확대 되기 때문에 픽셀을 사용해도 문제가 없는 것처럼 보여진다.

Chrome 페이지 확대/축소 설정

Chrome 페이지 확대/축소 설정

예를 들어 루트 폰트 크기를 16px에서 18px으로 변경하는 것은 페이지 줌을 112.5%로 설정하는 것과 같다. 따라서 모든 폰트 크기에 대해 픽셀을 사용하고 텍스트를 읽는 데 문제가 있는 경우 사용자가 페이지를 확대 하도록 사용자에게 위임하고 싶을 수 있다.

그러나 일반적으로 사용자는 기본 브라우저 확대/축소 비율보다 모니터에 대해 기본 폰트 크기를 가지고 있다. 결국 사용자가 임의 비율을 확대보다 픽셀에 대해 추론하는 것이 쉽다. 또한 위의 예에서와 같이 브라우저 비율은 부동 소수점 결과를 생성하여 사용자가 사용 가능한 가장 가까운 값을 반올림한다.

또 다른 중요한 차이점은 확대/축소는 전체 페이지의 크기를 조정한다는 것이다. 이는 사용자의 기본 폰트 크기를 확대하기로 결정할 때 원하지 않을 수도 있다. 일반적으로 사용자는 모든 것이 확장 되는 것이 아니라 페이지에 보이는 텍스트만 확대 하기를 원한다.

즉, 페이지 확대/축소를 지원하는지, 폰트 크기를 조정을 원하는지 또는 둘 다 원하는지는 개발자가 강제로 결정하기 보다 사용자의 기본 폰트 크기를 항상 존중해야 한다.

상대적 폰트 크기

사용자의 폰트 크기 기본 설정의 우선순위를 높이려면 픽셀과 같은 절대 단위는 이상적이지 않다. 다행히 CSS는 절대값을 사용하지 않고 페이지의 다른 요소를 참조하는 상대 단위 em, rem, % 를 제공한다. %와 em은 폰트 크기 조정과 관련하여 동일한 동작원리를 가지고 있기 때문에 본 글에서는 em, rem만 비교한다.

em

다음 HTML을 살펴보자

<div class="parent">
  <div class="child"></div>
</div>

font-size 속성의 경우 1em은 해당 엘리먼트의 상위 폰트 크기에 상대적이다. 따라서 부모 엘리먼트의 폰트 크기가 24px이면 font-size1em인 자식 엘리먼트의 폰트 크기는 24px으로 계산된 폰트 크기를 얻는다. 마찬가지로 0.5em12px으로 계산 된다.

.parent {
  /* 설명 목적으로 픽셀을 사용했다. */
  font-size: 24px;
}
.child {
  /* 0.5 * 24px = 12px */
  font-size: 0.5em;
}

그러나 em 은 폰트 크기 조정에 문제가 있다. 하나의 엘리먼트에 폰트 크기를 지정하고 자식 엘리먼트에 em 을 적용하면 두 엘리먼트의 폰트 크기가 다르게 적용된다. 다른 엘리먼트의 자식의 자식을 도입 하고 해당 엘리먼트에 em 기반 폰트 크기를 지정한다고 가정해보자.

<div class="parent">
  <div class="child">
    <div class="deeply-nested"></div>
  </div>
</div>
.parent {
  /* 설명 목적으로 픽셀을 사용했다. */
  font-size: 24px;
}
.child {
  /* 0.5 * 24px = 12px */
  font-size: 0.5em;
}
.deeply-nested {
  /* 0.5 * 0.5 * 24px = 6px */
  font-size: 0.5em;
}

이러한 이유로 엘리먼트가 DOM에서 깊히 중첩될 수 있을 때는 폰트 크기 단위로 em을 사용하지 않는 것이 좋다. 따라서 em기반 엘리먼트의 폰트 크기 자체는 CSS를 보는 것만으로 개발자가 예측하기 어렵다. 요소가 트리에 삽입되는 위치에 따라 다르기 때문이다. 이로 인해 독립적이고 재사용 가능한 엘리먼트를 만드는 것은 어렵다.

rem

em 과는 대조적으로 rem(root em)은 항상 도큐먼트의 루트 폰트 사이즈를 단일 출처로 참조한다. 루트 폰트 사이즈가 16px 이라고 가정하면 다음과 같은 값을 얻을 수 있다.

  • 0.5rem = 8px
  • 1rem = 16px
  • 1.5rem = 24px

rem의 가장 좋은 점은 개발자가 예측이 가능하다는 것이다. 임의 부모 폰트 사이즈가 아닌 루트 폰트 사이즈를 항상 참조하기 때문에 중첩 레이아웃 및 컴포넌트 프레임워크에서 안전하게 사용할 수 있다. 따라서 rem은 사용자의 기본 설정을 존중하는 반응형 단위로 폰트 크기를 정의할 수 있는 이상적인 단위이다. 사용자의 브라우저 설정에서 기본 폰트 크기를 변경하면 모든 rem 기반 엘리먼트의 폰트 크기가 설정에 따라 조정되고 새 값으로 렌더링 된다. rem 을 사용하여 위 예제를 업데이트해보겠다.

.parent {
  /* 1.5 * 16px = 24px */
  font-size: 1.5rem;
}
.child {
  /* 0.5 * 16px = 8px */
  font-size: 0.5rem;
}
.deeply-nested {
  /* 0.5 * 16px = 8px */
  font-size: 0.5rem;
}

이제 사용자가 브라우저 설정한 기본 폰트 사이즈를 18px으로 변경한다고 가정하면, rem 은 효과적으로 부모 및 자식 엘리먼트의 크기를 독립적으로 확대한다. 1.5rem27px으로 계산하고 0.5rem9px로 계산된다. 반대로 사용자의 기본 폰트 사이즈를 줄이는 경우도 크기는 비례적으로 감소한다. 이를 통해 사용자를 하드코딩 된 폰트 사이즈로 고정하지 않고 16px의 루트 글꼴 크기를 가진 브라우저의 “일반” 사용 사례에 맞게 앱을 설계할 수 있다. 즉, 사용자는 원하는 대로 자유롭게 변경할 수 있다.

rem 단위 추론

처음에는 rem으로 숫자를 표현하는 요령을 익히는 데 약간의 연습이 필요할 수 있다. 필자 역시 마찬가지였다. 그러나 시간이 지나면, 특히 2의 거듭제곱으로 작업하는 것을 좋아한다면 꽤 쉽다는 것을 알게 될 것이다.

실제로 개발을 하게 될 때는 이와 같이 무작위로 가져와 스타일에 하드코딩하고 싶지 않을 것이다. 단기적으로는 편리해 보이지만 장기적으로 CSS를 유지하기 어렵게 만들기 때문이다. 대신 type scale 을 사용하여 원하는 모든 크기에 대해 사용자 정의 프로퍼티를 미리 정의하는 것이 좋다.

html {
  --font-size-300: 0.75rem; /* 12px */
  --font-size-400: 1rem;    /* 16px, base */
  --font-size-500: 1.25rem; /* 20px */
  --font-size-600: 1.5rem;  /* 24px */
  --font-size-700: 1.75rem; /* 28px */
  --font-size-800: 2rem;    /* 32px */
  --font-size-900: 2.25rem; /* 36px */
  /* etc. */
}

이제 CSS에서 폰트 크기를 하드코딩하는 대신 다음 변수를 참조할 수 있게 된다.

.element {
  font-size: var(--font-size-400);
}

굳이 필자와 같이 작성할 필요는 없다. 다음과 같은 단위도 많이 사용한다. 하나의 규칙일 뿐이기에 팀 구성원들과 상의하여 사용하면 된다.

html {
  --font-size-sm: 0.75rem;   /* 12px */
  --font-size-base: 1rem;    /* 16px, base */
  --font-size-md: 1.25rem;   /* 20px */
  --font-size-lg: 1.5rem;    /* 24px */
  --font-size-xl: 1.75rem;   /* 28px */
  --font-size-xxl: 2rem;     /* 32px */
  --font-size-xxxl: 2.25rem; /* 36px */
  /* etc. */
}

CSS 변수를 사용할 때 좋은 점은 디자인을 구현할 때 픽셀을 rem으로 계속 변환할 필요가 없다는 것이다. 미리 정의된 변수 집합을 참조하고 값이 내부의 디자인 시스템 토큰에 해당한다고 신뢰할 수 있다. 디자인에 사용된 루트 픽셀 값을 조회할 필요가 없도록 코드에서와 동일한 명명 규칙을 사용하도록 디자이너에게 요청할 수 있다. 이렇게 하면 대표적으로 피그마에서 폰트 사이즈 변수의 이름을 복사하기만 하면 된다.

실제로 잘 만들어진 서비스는 이 방법 보다 한 단계 복잡하게 사용하고 있다. type scale의 각 단계는 일정한 폰트 사이즈였기 때문에 가독성 향상을 위해 다양한 뷰포트 너비의 엘리먼트에 대한 폰트 사이즈를 늘리거나 줄이는 미디어 쿼리를 작성해야 한다. 이러한 방식은 결국 더 많은 CSS를 작성할 뿐만 아니라 구현하는 것도 꽤 귀찮다. 기기의 해상도 별로 폰트 사이즈에 대해 두 가지 값 세트를 작성해야 하는 경우도 있고, 화면이 브레이크 포인트에 도달했을 때 폰트 크기가 갑자기 변경되어 부드럽지 못한 문제도 있다. 기회가 된다면 다루도록 하겠지만 필자도 type scale 단계에 머무르고 있다.

마지막

픽셀은 웹에서 엘리먼트의 크기를 결정하는 데 가장 널리 사용되는 단위이지만 사용자를 해당 크기로 고정하고 페이지 확대/축소를 제외하고 페이지의 텍스트 크기를 조정할 수 없도록 하기 때문에 폰트 크기로 사용하기에는 적합하지 않다. 사용자의 기본 설정을 존중하도록 하는 글꼴 크기인 rem으로 설정하는 것이 좋다.