[Frontend] 텔레그램 리디자인 개발하기 ② – 알아두면 쓸모있는 설정들

YuminPark·2026년 4월 23일

Frontend

목록 보기
14/19
post-thumbnail

이번 글에서는 이미 알고 있었지만 다시 한 번 짚어볼 만한 내용들, 그리고 실제로 유용하게 활용한 설정들을 정리해보려 합니다.
1편을 먼저 보고 오시면 더 도움이 될 것 같습니다.


디자인 시스템

font-size

디자이너분들이 font-sizepx로 정의해주시는 경우가 많고, 저도 그동안 px로 개발해왔기 때문에 크게 신경 쓰지 않았던 부분인데요. 이번 과제를 통해 웹 접근성 측면에서 px로 폰트 크기를 고정하는 것이 좋지 않다는 점을 새롭게 알게 되었습니다.
당장 반응형을 고려하지 않더라도, 습관적으로 rem을 사용하는 것이 좋겠다고 생각했습니다.
참고로 pxrem으로 변환할 때는 16으로 나눈 값을 사용하면 됩니다.

 /* Font Size */
  --font-size-22: 1.375rem;  /* 22px */
  --font-size-20: 1.25rem; /* 20px */
  --font-size-16: 1rem; /* 16px */
  --font-size-14: 0.875rem; /* 14px */
  --font-size-12: 0.75rem; /* 12px */
  --font-size-11: 0.6875rem; /* 11px */

참고 영상
폰트 크기 px로 하면 접근성 망할지도 | em rem 단위

Tailwind v4에서 타이포그래피 스케일 등록하기

사실 이번 과제에서 피드백을 받은 부분입니다. 다만 이를 수정하려면 CSS뿐만 아니라 font-*로 적용된 스타일 ex) font-heading-1 을 사용하는 모든 부분을 text-* 기준으로 전면 수정해야 했습니다. 작업 범위가 커 공수 대비 우선순위를 고려해 이번에는 반영하지 못했습니다.. 🥲

Tailwind v4에서는 --text-* 네임스페이스를 활용하면 font-size, line-height, letter-spacing, font-weight를 하나의 유틸리티 클래스로 묶을 수 있습니다.
더블 대시(--) 접미사로 부속 속성을 연결하는 방식입니다.

--text-{이름}                   /* font-size */
--text-{이름}--line-height      /* line-height */
--text-{이름}--letter-spacing   /* letter-spacing */
--text-{이름}--font-weight      /* font-weight */

공식 문서의 font-size 커스터마이징 섹션에서 확인할 수 있습니다.

Figma 텍스트 스타일 → @theme 변환

이번 리디자인 프로젝트에서 사용한 text style을 예시로 해 @theme 변환을 해봅시다.

StyleSizeLine HeightWeightLetter Spacing
Heading 122px140%700-0.03em
Heading 220px140%700-0.03em
Body 116px150%700-0.02em
Body 216px150%600-0.02em
Body 316px150%400-0.02em
Body 414px150%700-0.02em
Body 514px150%600-0.02em
Body 614px150%400-0.02em
Caption 112px150%600-0.02em
Caption 212px150%400-0.02em
Caption 311px150%600-0.02em
@theme {
  /* Heading */
  --text-heading-1: 1.375rem;                /* 22px */
  --text-heading-1--line-height: 1.925rem;   /* 22px × 1.4 */
  --text-heading-1--letter-spacing: -0.03em;
  --text-heading-1--font-weight: 700;

  --text-heading-2: 1.25rem;                 /* 20px */
  --text-heading-2--line-height: 1.75rem;    /* 20px × 1.4 */
  --text-heading-2--letter-spacing: -0.03em;
  --text-heading-2--font-weight: 700;

  /* Body */
  --text-body-1: 1rem;                       /* 16px */
  --text-body-1--line-height: 1.5rem;        /* 16px × 1.5 */
  --text-body-1--letter-spacing: -0.02em;
  --text-body-1--font-weight: 700;

  --text-body-2: 1rem;                       /* 16px */
  --text-body-2--line-height: 1.5rem;
  --text-body-2--letter-spacing: -0.02em;
  --text-body-2--font-weight: 600;

  --text-body-3: 1rem;                       /* 16px */
  --text-body-3--line-height: 1.5rem;
  --text-body-3--letter-spacing: -0.02em;

  --text-body-4: 0.875rem;                   /* 14px */
  --text-body-4--line-height: 1.3125rem;     /* 14px × 1.5 */
  --text-body-4--letter-spacing: -0.02em;
  --text-body-4--font-weight: 700;

  --text-body-5: 0.875rem;                   /* 14px */
  --text-body-5--line-height: 1.3125rem;
  --text-body-5--letter-spacing: -0.02em;
  --text-body-5--font-weight: 600;

  --text-body-6: 0.875rem;                   /* 14px */
  --text-body-6--line-height: 1.3125rem;
  --text-body-6--letter-spacing: -0.02em;

  /* Caption */
  --text-caption-1: 0.75rem;                 /* 12px */
  --text-caption-1--line-height: 1.125rem;   /* 12px × 1.5 */
  --text-caption-1--letter-spacing: -0.02em;
  --text-caption-1--font-weight: 600;

  --text-caption-2: 0.75rem;                 /* 12px */
  --text-caption-2--line-height: 1.125rem;
  --text-caption-2--letter-spacing: -0.02em;

  --text-caption-3: 0.6875rem;               /* 11px */
  --text-caption-3--line-height: 1.03125rem; /* 11px × 1.5 */
  --text-caption-3--letter-spacing: -0.02em;
  --text-caption-3--font-weight: 600;
}

--text-body-3처럼 --font-weight를 지정하지 않으면 Tailwind는 해당 속성에 개입하지 않고 브라우저 기본값(400)을 그대로 따릅니다.
디자인 스펙에 명시된 속성만 골라 선언하면 되므로, 불필요한 오버라이드 없이 깔끔하게 스케일을 유지할 수 있습니다.

주의할 점

Tailwind에서 text-lgtext-black은 둘 다 text- 네임스페이스를 공유하지만, 하나는 font-size, 다른 하나는 color에 해당합니다.
Tailwind CSS 커스텀 스케일 이름이 기존 색상 토큰 이름과 겹치면 유틸리티가 의도대로 생성되지 않을 수 있습니다. 예를 들어 --text-primary처럼 색상 토큰으로 쓸 법한 이름은 피하는 게 좋습니다.
실제로 --text-banner--text-color-banner를 함께 정의했을 때 font-size 유틸리티가 생성되지 않고 color 유틸리티만 생성되는 충돌이 보고된 바 있습니다.

참고 자료


clsx & cn (tailwind-merge)

Tailwind로 조건부 스타일링을 하다 보면 아래 같은 상황이 자주 생깁니다.

// 문자열 보간으로 쓰면 지저분하고, 클래스 충돌도 발생할 수 있음
className={`text-sm ${isActive ? "text-blue-500" : "text-gray-500"}`}

이를 깔끔하게 해결해주는 조합이 바로 clsx + tailwind-merge입니다.

  • clsx — 조건부로 클래스명을 조합해주는 유틸리티입니다. 배열, 객체, 삼항 연산자 등 다양한 방식으로 클래스를 선언할 수 있어 가독성이 좋아집니다. 다만 내부적으로는 문자열을 이어붙이는 것에 불과해서, 클래스 충돌은 해결하지 못합니다.
  • tailwind-mergeTailwind 클래스 간 충돌을 자동으로 해결해줍니다. 예를 들어 px-2px-4가 동시에 적용되면 후자만 살려줍니다.
    cn("px-2 py-1", "px-4")
    // clsx만 → "px-2 py-1 px-4" (충돌 발생)
    // tailwind-merge까지 → "py-1 px-4" (px-2 제거)

두 라이브러리를 합쳐 아래처럼 cn 유틸 함수로 만들어두면, 프로젝트 전반에서 편리하게 사용할 수 있습니다.

import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));

실제 사용 예시입니다. 현재 탭이 활성화 상태인지에 따라 배경색과 글자색을 조건부로 적용했습니다.

<button
  key={path}
  onClick={() => navigate(path)}
  className={cn(
    "rounded-100 flex flex-1 cursor-pointer flex-col items-center justify-center gap-0.5 px-4 py-1 transition-colors duration-400 ease-in-out",
    isActive ? "bg-primary-100 text-primary-400" : "text-gray-500",
  )}
>

기본 클래스는 항상 적용하되, isActive 여부에 따라 색상 클래스만 교체되는 패턴입니다.
cn 덕분에 충돌 걱정 없이 조건부 스타일을 간결하게 작성할 수 있습니다.


아이콘

SVG 파일을 컴포넌트처럼 사용하기 & 크기 조정하기

아이콘 파일은 vite-plugin-svgr을 통해 React 컴포넌트처럼 사용했습니다. 프로젝트마다 적용해서 쓰고 있는데, 번들 최적화와 접근성 측면에서도 유리하고 사용하기도 편리하니 적극 추천드립니다.
(잊어버릴 수 있으니 초기세팅 때 함께 설정해두시는 걸 추천드립니다.)

먼저 vite-plugin-svgr을 설치합니다.

npm install vite-plugin-svgr

그리고 vite.config.js에 아래와 같이 설정을 추가합니다.

import svgr from 'vite-plugin-svgr';

export default {
  plugins: [
    svgr({
      svgrOptions: {
        dimensions: false,
      },
    }),
  ],
};

여기서 dimensions: false 옵션이 핵심입니다.

SVG 파일에는 보통 width="16" height="16" 같은 고정 크기값이 포함되어 있는데, 이 값이 남아 있으면 CSS에서 크기를 변경해도 적용되지 않습니다.
dimensions: false를 설정하면 SVGR이 빌드 시 이 고정값을 자동으로 제거해주기 때문에, Tailwind의 size-5 같은 유틸리티 클래스가 정상적으로 동작하게 됩니다.

컴포넌트로 사용할 때는 import 경로 끝에 ?react를 붙이면 됩니다.

import PlusIcon from "@/assets/icons/icon_plus_regular.svg?react";

fill: currentColor 속성 사용하기

같은 모양의 아이콘을 빨간색, 파란색 등 여러 색으로 사용해야 한다면 어떻게 할까요?
색상별로 SVG 파일을 따로 만들 필요 없이, SVG 원본의 fill이나 stroke 값을 currentColor로 설정해두면 CSS에서 color 속성만으로 색상을 자유롭게 제어할 수 있습니다.

 <CheckIcon className={cn("size-4", isRead ? "text-sub" : "text-gray-400")} />

위처럼 하나의 SVG 파일만으로, 읽음 여부에 따라 초록색 또는 회색으로 렌더링할 수 있습니다.

네이밍 컨벤션 정립하기

아이콘 파일명을 icon_{이름}_{변형}.svg 형태로 통일하고 리팩토링했습니다.
파일이 많아질수록 _thin, _filled 등을 제각각 붙이다 보면 일관성이 무너지는 경험을 반복했는데, 이번 기회에 정리했습니다.

icon_{이름}_{변형}.svg 형태로 컨벤션을 잡으면 다음과 같은 이점이 있습니다.

  • 검색 및 자동완성 — icon_으로 시작하는 파일만 필터링되어 에디터 자동완성이 깔끔해집니다.
  • 분류 일관성 — 이름과 변형(regular, thin, filled 등)이 명확하게 구분되어 파일이 늘어나도 관리하기 쉽습니다.
  • OS 호환성 — 공백이나 대소문자 혼용 없이 소문자 snake_case로 통일하면 운영체제 간 파일명 처리 차이로 인한 문제를 방지할 수 있습니다.

참고 자료


사소해 보이지만 한 번 세팅해두면 두고두고 편리한 것 같습니다.
다음 프로젝트 초기 세팅 때 하나씩 챙겨보시는 걸 추천드립니다. 모쪼록 도움이 되셨으면 좋겠습니다!

0개의 댓글