오늘은 css in js 중 대중적으로 많이 알려져있는 Styled-Component에 대한 설명과, 간단한 typo 컴포넌트를 구현하는 것에 대해 작성해보려고 합니다.
들어가기 앞서, css in js 가 무엇일까요? 단어 그대로 자바스크립트 코드에서 css를 작성하는 방식을 말합니다.

css in js의 대표적인 라이브러리들은 위와 같고, 가장 많이 사용하는 것은 Styled-Component로 확인이 됩니다. 요즘은 많은 테크기업에서 emotion을 사용하는 곳들을 종종 보긴 했지만, 아무래도 접근성 측면에서 styled-component가 좀 더 인지도가 높지 않나 라는 생각을 해봅니다..
사용 방식은 emotion 과 styled-component는 크게 다르지 않습니다. 모두 styled와 css를 지원해주는 라이브러리를 추가로 갖고있기 때문입니다. 둘 다 매력적이고 React같은 라이브러리에서 사용하기 매우 좋기때문에 사용해보지 않으셨다면 이번기회에 한번 사용해보는것도 좋을 것 같습니다 :D
아래부터 진행될 예제에 설치 튜토리얼은 없습니다. 공식 홈페이지 를 참고해주세요.
효율적인 스타일 관리?
프로젝트를 진행하다보면 스타일 관리에 있어서 많은 어려움이 있습니다. 인라인 스타일을 선호하는 타입, emotion, styled-component, tailwind css 등의 라이브러리를 선호하는 타입, 커스텀 스타일을 만드는 타입 등 여러 방식속에서 최선을 찾기위해 노력하죠.
모든 방식에 있어 정답은 없다 생각합니다. 하지만, 개인 프로젝트가 아닌 여럿이 함께하는 대규모 프로젝트로 가게 된다면 스타일 관리의 포맷을 맞추는 것은 필수라고 생각합니다.
우리는 styled-component를 사용해서 이 포맷을 맞추는 작업을 진행해볼 생각입니다.
프로젝트 테마 설정
여러 프로젝트들은 각각의 디자인 시스템이 존재할거고, 모든 화면을 구성하는 컴포넌트들은 큰 틀에서 벗어나지 않는
“테마”
를 갖고 있습니다.- 오늘의 집에선 아래와 같은 라벨과, 스와이프 된 UI 구성을 가지고 있습니다.

- 토스의 경우에도 시그니처 컬러와, 그 외에 사용하는 컬러들에 라벨을 붙여서 사용하고 있습니다.


이 이외에도 “카카오” 하면 떠오르는 시그니처 노란색 도 결국 테마가 되는 것입니다. 이러한 테마를 토스처럼 컬러에 네이밍으로 구분해서 관리한다면 저희는 관리되어있는 색상 네이밍에 접근해서 사용하면 편하지 않을까요?
Styled-Component는 이러한 테마 설정을 쉽고 깔끔하게 구현하는데 도움을 줍니다.
// theme.ts const font = { title1: { fontSize: 32px, fontWeight: 600 }, titke2: { fontSize: 28px, fontWeight: 500 }, body1: { fontSize: 16px, fontWeight: 400 }, body2: { fontSize: 14px, fontWeight: 400 } } const colors = { error: 'red', warning: 'orange', info: 'blue' success: 'green' } const theme = { font, colors } export default theme
우리가 자주 사용하게 될
theme
을 설정합니다. 예제에서는 font와 color만 들어가지만 여기엔 border, padding 등 여러가지 디자인 시스템을 넣어서 사용할 수 있습니다.이렇게 추가한 theme 을 사용하기 위해선 사용할 컴포넌트를
ThemeProvider
로 감싸주어야 합니다.// app.tsx import theme from './stlyes/theme'; import { ThemeProvider } from 'styled-components'; function App() { return ( <> <ThemeProvider theme={theme}> <Routes> ... </Routes> </ThemeProvider> </> ); }
이렇게 감싸게 되면 하위 컴포넌트에선 theme에 접근할 수 있게 됩니다.
const Button = styled.button` border: none; border-radius: 8px; background-color: ${({theme}) => theme.colors.info}; cursor: pointer; &:hover { transform: scale(1.05); background-color: ${({ theme }) => theme.colors.success}; } ${({ theme }) => ...theme.font.body1}; `;
이렇게 우리는 하나의
버튼
컴포넌트를 만들었습니다. 물론, 인라인 스타일로 만들 수 있겠지만 우리가 처음에 설계한 theme에 쉽게 접근하면서 원하는 대로 핸들링을 할 수 있게 된 것입니다.우리가 원하는 Theme으로 Typo 컴포넌트 만들기!
이제 우리가 원하는 파트에 도달했습니다. 하나의 서비스를 구현할때 정해진 디자인 시스템이 있기 마련입니다. 그 중 가장 번거로운게텍스트일 것입니다.
글자 크기, 글자 두께, 글자 색상 등 여러개의 조합으로 이루어져 있을텐데, 이 조합을 만들기 위해 우리는 아래처럼 수행할 것입니다.
export const Description = styled.span` color: ${({theme}) => theme.colors.success}; ${({ theme }) => ...theme.font.body1}; `
근데, 만약
Description
말고, Title
이라는 텍스트 컴포넌트가 필요하다면 불필요하지만 비슷하면서도 다른 형태의 컴포넌트가 생길 것입니다. 이런게 한개도,,, 두개도,,, 세개도 아니라면 관리하기가 더 어려워 질 수 있죠. 그럴때마다 새로운 컴포넌트를 만들지 않고, 파일 내에 Styled-Component
를 사용해 일회성으로 만들어도 되지만, 우리는 이 방식을 하나의 컴포넌트에서 관리하는 방식을 구현해볼 예정입니다.예제의 Colors와 Typography 의 상세한 내용은 생략하겠습니다.
우선 뼈대를 잡아야 합니다. 필요한 Props를 정의해야하고, 어떤 태그를 기본 태그로 사용할지 정해야 합니다.
저는 기본 태그로
<span />
을 생각했습니다. span이 기본이지만, 상황에 따라 h1, h2 … p, span 등 여러가지로 변할 수 있어야 합니다.또한, 필수적인 Props 는 아래처럼 설계했습니다.
interface Props { // 꼭 Text가 아닌, 다른 Tag나 SVG가 들어갈 것을 대비.. (예외 케이스) children?: ReactNode; className?: string; // Typo 설정 typography?: Typography; // color 설정 color?: Colors; textAlign?: CSSProperties['textAling']; }
이 필수적인 Props이외에 기존 태그인 span 의 속성도 가지고 있어야 합니다. 그렇기에 span의 기본 태그도 추가해줍니다.
type TextProps<Element extends keyof JSX.IntrinsicElements = 'span'> = Props & { as?: Element; } & Omit<AllHTMLAttributes<Element>, 'as'>;
기본 설정은 완성됐으니, 기본 태를 구현해보겠습니다.
const createStyledText = <E extends keyof JSX.IntrinsicElements = 'span'>(Component: E) => styled(Component)<TextProps<E>>` ${props => ` color: ${props.theme.color[props.color || 'neutral90']}; text-align: ${props.textAlign || 'inherit'}; ${props.theme.font[props.typography || 'body1']}; `} `; // Text 컴포넌트 정의 const Text = forwardRef<HTMLElement, TextProps<any>>(({ as = 'span', children, className, role, style, ...props }, ref) => { const StyledComponent = createStyledText(as); return ( <StyledComponent ref={ref} role={role ?? (as === 'span' ? 'text' : undefined)} className={`text ${className || ''}`} style={style} {...props} > {children} </StyledComponent> ); }); export default Text;
어떤가요? 우리는 이제 사용하는 방식이 조금 더 간결해졌습니다. 여러번의
Styled-Component
를 만들 필요 없이 필요한 props만 전달해주면 될 것입니다.const App = () => { return ( <div> <Text as="h1" color="primary" typography="headline" textAlign="center" > 메인 헤드라인 </Text> <Text color="secondary" typography="body1" > 이것은 일반 텍스트 섹션입니다. </Text> <Text as="p" color="neutral90" typography="body2" textAlign="left" > 여기에는 좀 더 긴 설명이 포함되어 있으며, 왼쪽 정렬로 표시됩니다. </Text> <Text as="label" htmlFor="inputExample" color="info" typography="caption" > 레이블 예시 </Text> <input id="inputExample" type="text" /> </div> ); };
우리는 디자인 시스템에 있는 theme 조건들을 토대로
Styled-Component
를 활용해서 Text 컴포넌트를 만들었습니다. 이렇게 한다면, theme을 벗어나는 텍스트 규칙은 없을 것입니다. 어떤가요! 정말 심플하지 않나요?어려분들의 프로젝트는 어떤 라이브러리로 스타일을 관리하시나요? 어디까지나 서로 장단점이 있다 생각하기에 어떻게 구조를 잡느냐가 퀄리티 높은 코드를 만드는데 기여한다 생각합니다.
Styled-Component도 무분별하게 매번 생성한다면, 그 또한 관리하기 어렵지 않을까요? 어떻게 하면 더 효율적으로 관리할 수 있을지.. 좋은 아이디어가 있다면 공유 부탁드립니다 😀
오늘도 글 읽어주셔서 감사합니다.