Text 디자인 시스템🎨 구축하기

Text 컴포넌트를 만들어서 텍스트 스타일 통일하기🍀


Table Of Contents


텍스트가 너무 많아!


팀에 합류하고 Figma 링크를 전달받았다! 디자인 시스템부터 살펴보는데 굉장히 많은 텍스트 스타일이 있었다🤯

1. design system

이걸 다... 사용하게 되나요?!

 

CSPG 프로젝트는 스타일링 라이브러리로 tailwind를 쓰는데, 나는 tailwind로 어느 정도 큰 프로젝트를 해본 적이 없어서 어느 정도 불안함이 있었다.

다행히도 디자인 시스템은 다른 개발자분께서 미리 세팅해 주셨다고 한다...! 그렇게 코드를 열었는데 개발자분의 깊은 고민이 담긴 코드를 볼 수 있었다🥲

2. previous code

미니맵 낑기는거보세요,,,

tailwind.config.ts 파일을 이용해서 모든 사이즈 / 두께에 대해서 스타일을 미리 정의해놓은 모습이다.

 

이렇게 텍스트 스타일을 미리 정의해 놓으면

<h1 className="title-2-regular">h1입니다!</h1>

처럼 글자에 스타일을 한 번에 줄 수 있기 때문에 편리하다.

하지만 중복되는 코드가 너무 많고, 무엇보다 텍스트 관련 스타일만 200줄에 달하기 때문에 뭔가 아쉽다는 느낌을 받았다🤔

중복되는 코드 줄이기


tailwind에서 제공하는 기능을 이용해 코드 중복을 줄일 수 있을 지 여러 방면으로 고민해봤는데, 내 결론은 모듈화를 이용하자! 였다.

tailwind의 기능으로는 만족스러운 코드를 작성할 수 없었고, 결국 나는 React를 사용하고 있으니 React의 컴포넌트를 활용하기로 한 것이다.

1. 컴포넌트 구상하기

나는 컴포넌트의 세부 구현보다는 어떻게 불러와서 사용할 것인가를 먼저 생각해봤다.

1. design system

다시 한 번 우리 디자인 시스템을 보자.

텍스트는 4가지 타입(Title, Heading, Body, Caption)으로 구성되어 있고, 그 안에 1, 2, 3같이 사이즈가 변화한다. 그리고 또 그 안에서 weight가 변화하는 구조다.

 

따라서

// Title 3 extrabold <Title level={3} weight="extrabold">이건 Title 3 extrabold입니다!</Title> // Heading 1 normal <Heading level={1} weight="normal">이건 Heading 1입니다!</Heading> // Body 5 medium <Body level={5} weight="medium">이건 Body 5 medium입니다!</Body> // Caption bold <Caption weight="bold">이건 Caption bold입니다!</Caption>

처럼 사용할 수 있는 컴포넌트를 만들기로 했다.

 

단, Title, Body같은 컴포넌트명은 다른 곳에서도 흔하게 사용하는 이름이다.

따라서 Text라는 컴포넌트로 한 번 감싸준 다음에 그 안에서 불러오는 식으로 사용하는 방향으로 잡았다.

 

실제 사용 시에는 아래처럼 불러올 것이다.

import Text from "경로"; // Title 3 extrabold <Text.Title level={3} weight="extrabold">이건 Title 3 extrabold입니다!</Text.Title> // Heading 1 normal <Text.Heading level={1} weight="normal">이건 Heading 1입니다!</Text.Heading> // Body 5 medium <Text.Body level={5} weight="medium">이건 Body 5 medium입니다!</Text.Body> // Caption bold <Text.Caption weight="bold">이건 Caption bold입니다!</Text.Caption>

이렇게 사용하면 여기에서 사용하는 컴포넌트가 Title이라는 영역이 아니라 Text 중에서도 Title임을 보기 쉽게 알려줄 수 있을 것 같았다.

2. Title 구현하기

우선 Title이 가장 먼저 있으니까 Title부터 시작하자

  • 잠시, 그 전에 Text 컴포넌트는 어디서든 사용할 수 있어야 하기 때문에 src/components/atoms/Text 경로에 생성하기로 했다.

2-1. 타입 정의

Title은 1, 2, 3으로 나뉜다. 따라서

  • level에는 1 | 2 | 3만 들어올 수 있도록 하고,
  • weight도 가능한 경우의 수 "black" | "bold" | "medium" | "normal"만 가능하도록 타입을 작성한다.
type TitleProps = { level?: 1 | 2 | 3; weight?: "black" | "bold" | "medium" | "normal"; children: ReactNode; };
<Text.Title level={3} weight="extrabold"> 이건 Title 3 extrabold입니다! // 이 부분이 children이다. </Text.Title>

처럼 호출하므로 내용은 children으로 받아서 그대로 렌더링할 예정이다.

default props는 가장 먼저 오는 값들로 지정해준다.

2-2.적절한 태그 선택하기

이제 이 컴포넌트를 렌더링해야 하는데, Title 컴포넌트는 Body와는 달리 p 태그가 아니라 h1 ~ h6 태그를 이용하는 게 HTML의 구조상 더 좋을 것 같았다.

마침 레벨도 1~3으로 나뉘어 있어서 h1 ~ h3까지의 태그를 이용해 렌더링했다.

const TagName = `h${level}` as keyof JSX.IntrinsicElements;

처럼 어떤 태그를 사용할 지 계산하고,

<TagName className={`font-suit font-${weight}`}>{children}</TagName>

처럼 불러오면 h1 ~ h3 태그로 렌더링된다.

 

Title은 모두 SUIT 폰트를 사용하기 때문에 font 스타일도 적용해줬다.

지금까지의 코드는 아래와 같다.

type TitleProps = { level?: 1 | 2 | 3; weight?: "black" | "bold" | "medium" | "normal"; children: ReactNode; }; function Title({ level = 1, weight = "black", children }: TitleProps) { const TagName = `h${level}` as keyof JSX.IntrinsicElements; return <TagName className={`font-suit font-${weight}`}>{children}</TagName>; }

2-3. 스타일링

이제 텍스트의 레벨에 따라서 상세 스타일을 적용해야 하는데, 이 부분은 object를 이용해서 처리했다.

// src/components/ataoms/Text/Title.tsx const style = { "1": "text-[32px] leading-[44px] tracking-[-0.96%]", "2": "text-[28px] leading-[40px] tracking-[-0.84%]", "3": "text-[24px] leading-[36px] tracking-[-0.72%]", } as const; type TitleProps = { level?: 1 | 2 | 3; weight?: "black" | "bold" | "medium" | "normal"; children: ReactNode; }; function Title({ level = 1, weight = "black", children }: TitleProps) { const TagName = `h${level}` as keyof JSX.IntrinsicElements; return ( <TagName className={`${style[level]} font-suit font-${weight}`}> {children} </TagName> ); }

디자인 시스템에서는 rem이 아니라 px 단위를 사용했기 때문에 text-[32px]처럼 사용했는데, 이 부분은 나중에 수정할 방법이 있었으면 좋겠다...!

2-4. Text 컴포넌트로 감싸 내보내기

위에서 언급했듯이, Title 컴포넌트는 단독으로 쓰기보다는 Text에서 불러와 쓰는 것을 권장한다.

Text 컴포넌트를 만들고, Title을 감싸서 내보내자.

// src/components/ataoms/Text/index.tsx import Title from "./Title"; const Text = { Title, }; export default Text;

 

이제 Title을 src/components/atoms/Text에서 import해서 사용할 수 있다.

import Text from "@/components/atoms/Text"; <div> <Text.Title weight="bold">Stay Connected</Text.Title> <Text.Title weight="bold">with the Latest Trends and Events</Text.Title> </div>;

3. title example

개발자 도구로 선택해봐도 h1 태그, font weight 등이 정상적으로 적용되어서 나온다.

3. Body 구현하기

Heading은 Title과 크게 다르지 않으므로 똑같이 구현하면 된다.

대신 Body는 Title, Heading과 달리 p 태그로 렌더링한다는 점이 다르다.

따라서 이렇게 구현해 주면 된다.

// src/components/ataoms/Text/Body.tsx const style = { 1: "text-[20px] leading-[30px] tracking-[-0.54%]", 2: "text-[18px] leading-[26px] tracking-[-0.54%]", 3: "text-[16px] leading-[24px] tracking-[-0.48%]", 4: "text-[14px] leading-[20px] tracking-[-0.32%]", 5: "text-[12px] leading-[18px] tracking-[-0.36%]", } as const; type BodyProps = { level?: 1 | 2 | 3 | 4 | 5; weight?: "bold" | "medium" | "normal"; children: ReactNode; }; function Body({ level = 1, weight = "bold", children }: BodyProps) { return ( <p className={`${style[level]} font-pretendard font-${weight}`}> {children} </p> ); }

가능한 level, weight의 종류에만 주의해주자.

 

Caption 역시 p로 렌더링하되, level만 사라진다고 보면 된다.

4. 추가적인 스타일링 적용하기

문제가 하나 발생했다!

4. need text color

이렇게 Sub Text의 경우에는 글자 색이 달라질 수도 있는데, 현재로써는 글자색을 변경할 수 있는 방법이 없다.

이런 경우를 처리하기 위해, 모든 컴포넌트에 className이라는 속성을 주어 추가적인 스타일링을 가능하도록 했다.

// src/components/ataoms/Text/Title.tsx type TitleProps = { level?: 1 | 2 | 3; weight?: "black" | "bold" | "medium" | "normal"; className?: string; children: ReactNode; }; function Title({ level = 1, weight = "black", className = "", children, }: TitleProps) { const TagName = `h${level}` as keyof JSX.IntrinsicElements; return ( <TagName className={`${style[level]} font-suit font-${weight} ${className}`} > {children} </TagName> ); }

 

이제 아래처럼 추가적인 스타일을 입힐 수 있다.

<Text.Heading level={5} weight="medium" className={"text-label-assistive"}> {text} </Text.Heading>

화면에도 잘 나타난다!

5. text color example

프로젝트 구조


최종적인 프로젝트 구조는 이렇다.

6. project structure

마무리


이렇게 텍스트 컴포넌트를 만듦으로써 텍스트가 사용되는 부분을 쉽게 알 수 있게 되었고, 스타일링 코드의 중복도 줄일 수 있었다고 생각한다.

혹시 더 좋은 방법이 있다면 알려주세요🥹