2026.04.01·4

Pickle을 만들며 고른 기술들 (06) - 디자인 토큰과 아이콘을 왜 자동 생성 파이프라인으로 묶었을까

Pickle에서 Figma 토큰을 CSS 변수로 자동 변환하고, 아이콘을 React 컴포넌트와 팔레트로 생성한 이유를 정리합니다.

Pickle을 만들며 고른 기술들 (06) - 디자인 토큰과 아이콘을 왜 자동 생성 파이프라인으로 묶었을까 대표 이미지

디자인 시스템이 자주 흔들리는 이유는 값이 없어서가 아니라, 값을 옮기는 과정이 매번 수작업이기 때문인 경우가 많습니다.

들어가며

Pickle에서는 디자인 토큰과 아이콘을 단순한 에셋으로 두지 않았습니다. 둘 다 결국 코드에서 계속 써야 하는 자산이었고, 수동으로 옮길수록 어긋날 가능성이 커진다고 봤기 때문입니다.

특히 이 프로젝트에서는 웹, 클라이언트, 익스텐션이 함께 있었기 때문에 한쪽에서만 값이 달라지거나, 아이콘 사용 방식이 조금씩 달라지는 상황을 초기에 줄이고 싶었습니다. 그래서 디자인 토큰은 Figma에서 JSON으로 뽑아 CSS 변수로 변환했고, 아이콘은 SVG를 React 컴포넌트와 팔레트로 자동 생성하는 방식으로 묶었습니다.

이 글에서는 왜 Pickle이 디자인 자산을 "파일 모음"이 아니라 "반복 가능한 생성 파이프라인"으로 다루게 되었는지를 정리하려고 합니다.

아이콘은 하나의 파일로 모든 크기를 해결하고 싶지 않았습니다

아이콘을 다루면서 먼저 신경 쓴 것은 사용 편의성보다 품질이었습니다. Figma에서는 12px, 16px, 20px처럼 필요한 크기별로 아이콘을 따로 정의하고 있었는데, 이유는 분명했습니다.

하나의 아이콘을 단순 확대나 축소로만 쓰기 시작하면 선 두께와 여백이 크기에 따라 어색해집니다. 어떤 크기에서는 라인이 너무 얇아 보이고, 어떤 크기에서는 반대로 답답하게 느껴집니다. 결국 "같은 아이콘"이어도 작은 크기와 큰 크기는 따로 다뤄야 더 안정적인 결과가 나옵니다.

Pickle에서도 이 기준을 그대로 가져가고 싶었습니다. 즉 search 아이콘 하나를 만들어 놓고 모든 곳에서 기계적으로 리사이즈하는 방식보다, search-16, search-20 같은 실제 자산을 구분해서 쓰는 편이 더 맞다고 판단했습니다.

이 기준은 조금 번거롭습니다. 하지만 아이콘은 작을수록 디테일이 더 빨리 망가지기 때문에, 저는 이 부분에서만큼은 사용 편의성보다 시각 품질을 먼저 두는 편이 낫다고 봤습니다.

Pickle 아이콘을 12px, 16px, 20px 크기별로 따로 정의한 피그마 보드

Pickle에서는 아이콘을 크기별로 따로 정의했습니다. 같은 아이콘이라도 크기에 따라 선 두께와 여백의 기준이 달라지기 때문입니다.

문제는 아이콘을 코드에서 쓰는 순간 시작됐습니다

아이콘을 크기별로 따로 관리하기로 하면, 다음 문제는 개발 쪽에서 바로 생깁니다. SVG 파일을 추가하고, React 컴포넌트로 바꾸고, 팔레트에 등록하고, 타입을 맞추는 일을 매번 손으로 해야 하기 때문입니다.

처음에는 이 과정도 감당할 수 있을 것처럼 보입니다. 하지만 아이콘 수가 조금만 늘어나도 반복 작업이 됩니다. 새 파일을 넣고, export를 추가하고, 사용 가능한 목록을 갱신하고, 사용 코드까지 연결하는 일을 계속 하다 보면 정작 귀찮아지는 것은 SVG를 그리는 일이 아니라 등록 절차 자체입니다.

그래서 Pickle에서는 이 부분을 Lucide 같은 방식으로 쓰고 싶었습니다. 개발자는 최종적으로 Icon 컴포넌트 하나만 보고, 내부에서는 필요한 React 컴포넌트와 아이콘 팔레트가 자동으로 준비되는 흐름이 더 낫다고 판단했습니다.

예를 들어 사용 쪽에서는 이런 형태가 됩니다.

import { Icon } from "@pickle/icons";
 
<Icon name="search_16" />
<Icon name="layout_20" />

이렇게 하려면 결국 SVG 파일을 코드 친화적인 형태로 다시 정리해 주는 생성 단계가 필요합니다.

그래서 아이콘도 생성 스크립트로 묶었습니다

Pickle에서는 packages/icons 안에 SVG 원본을 두고, 스크립트로 React 컴포넌트와 팔레트를 함께 생성하는 방식을 택했습니다.

흐름은 단순합니다.

  1. src/svgsearch-16.svg, layout-20.svg 같은 파일을 넣습니다.
  2. 스크립트가 SVG를 React 컴포넌트로 변환합니다.
  3. 파일명을 기준으로 이름과 크기를 읽어 ICON_PALETTE를 다시 씁니다.
  4. 코드에서는 그 팔레트를 통해 일관된 방식으로 아이콘을 사용합니다.

스크립트도 결국 이런 일을 하고 있습니다.

const baseName = path.basename(file, ".svg"); // search-16
const dashIndex = baseName.lastIndexOf("-");
 
const namePart = baseName.slice(0, dashIndex); // search
const sizePart = baseName.slice(dashIndex + 1); // 16
const flatKey = `${namePart}_${sizePart}`; // search_16

그리고 이 메타데이터를 바탕으로 팔레트를 자동으로 만듭니다.

export const ICON_PALETTE = {
  search_16: IconSearch16,
  search_20: IconSearch20,
  layout_20: IconLayout20,
} as const;

이 구조의 장점은 "아이콘을 코드에서 어떻게 쓸 것인가"가 늘 같은 규칙으로 고정된다는 점입니다. SVG가 늘어나도 개발자는 등록 절차를 다시 떠올릴 필요가 없습니다. 새 파일을 넣고 스크립트를 실행하면, 나머지는 같은 방식으로 따라옵니다.

저는 이런 자동화가 아이콘을 편하게 만들었다기보다, "아이콘 추가 과정에서 생기는 작은 번거로움과 누락"을 줄여 줬다는 점에서 더 유용했습니다.

디자인 토큰도 수동 복붙으로는 오래 못 간다고 봤습니다

토큰도 비슷했습니다. Figma에서 정리한 색상과 역할 값을 코드로 옮길 때, 처음에는 직접 옮겨 적는 방식으로도 시작할 수 있습니다. 하지만 이 방식은 반드시 어긋납니다.

값 하나만 잘못 복사해도 문제가 생기고, 이름을 다르게 적어도 문제가 생기고, 한쪽만 바꾸고 다른 쪽을 놓쳐도 문제가 생깁니다. 그리고 이런 종류의 문제는 대부분 눈에 바로 띄지 않고, 시간이 지난 뒤에야 "왜 여기만 색이 다르지?" 같은 형태로 드러납니다.

저는 이런 문제를 결국 휴먼 에러 문제라고 봤습니다. 사람이 실수해서가 아니라, 사람이 반복해서 옮기는 구조 자체가 실수를 만들기 쉽기 때문입니다.

그래서 Pickle에서는 Luckino 플러그인으로 Figma 토큰을 JSON으로 추출하고, 그 JSON을 기준으로 CSS 변수를 생성하는 흐름을 잡았습니다.

Figma에서 뽑아 오는 데이터는 대략 이런 형태입니다.

{
  "Primitive": {
    "gray": {
      "neutral-50": { "$value": "#fafafa", "$type": "color" },
      "neutral-100": { "$value": "#ededed", "$type": "color" }
    }
  },
  "Atomic": {
    "base": {
      "background": { "$value": "{gray.neutral-950}", "$type": "color" }
    }
  }
}

그리고 스크립트가 이 JSON을 읽어 CSS 변수 파일을 다시 생성합니다.

const rawData = fs.readFileSync(TOKEN_PATH, "utf8");
const tokens = JSON.parse(rawData);
 
cssContent += "@theme {\n";
cssContent += `  --color-neutral-50: ${hexToOklch("#fafafa")};\n`;
cssContent += `  --color-base-background: var(--color-neutral-950);\n`;

결과적으로 코드에서는 이렇게 정리된 값을 일관되게 참조할 수 있습니다.

@theme {
  --color-neutral-50: oklch(0.9851 0 0);
  --color-base-background: var(--color-neutral-950);
}

이 흐름에서 중요한 것은 "Figma에서 JSON으로 뽑는다"보다, "토큰을 옮기는 경로를 한 번 정해두고 계속 같은 방식으로 갱신한다"는 점이었습니다.

자동화의 핵심은 속도보다 일관성이었습니다

아이콘 생성과 토큰 변환 모두 겉으로 보면 개발 편의성을 위한 자동화처럼 보일 수 있습니다. 물론 그것도 맞습니다. 하지만 제가 더 중요하게 본 것은 속도보다 일관성이었습니다.

아이콘은 크기별 품질을 유지하면서도 코드에서는 같은 규칙으로 써야 했습니다. 토큰은 Figma와 코드 사이를 오가더라도 값이 제멋대로 어긋나지 않아야 했습니다. 이 둘을 손작업에 계속 맡겨 두면, 언젠가는 등록 누락이나 값 불일치가 생길 수밖에 없다고 생각했습니다.

그래서 Pickle에서는 디자인 자산을 "디자이너가 만든 파일"로만 보지 않고, "계속 업데이트되고 다시 생성될 수 있는 입력값"으로 보는 쪽이 더 맞았습니다.

마치며

Pickle에서 아이콘과 토큰을 자동화한 이유는 멋진 파이프라인을 만들고 싶어서가 아니었습니다. 오히려 반대에 가까웠습니다. 반복해서 옮기고 등록하는 과정이 언젠가는 흔들릴 것 같았기 때문에, 그 흔들림을 줄이는 쪽으로 구조를 정리한 것입니다.

아이콘은 크기별 품질을 유지하고 싶었고, 토큰은 사람이 옮기는 과정에서 생기는 오차를 줄이고 싶었습니다. 그래서 둘 다 "수동 관리"보다 "같은 방식으로 다시 생성할 수 있는 구조"가 더 중요해졌습니다.

Pickle을 만들면서 느낀 것은, 디자인 시스템은 예쁜 컴포넌트보다 먼저 "어떻게 갱신되는가"를 설계해야 오래 간다는 점이었습니다.

참고

  • Atlassian Iconography: 시스템 아이콘을 16px, 24px, 32px 기준으로 어떻게 다루는지 참고할 수 있습니다.
  • IBM UI Icons Design: 아이콘 그리드와 정렬, 패딩 같은 기본 설계 기준을 볼 수 있습니다.
  • IBM UI Icons Usage: 크기별로 아이콘을 어떻게 쓰는지와 스트로크 기준을 함께 참고할 수 있습니다.