
실무 프로젝트에서나 사이드 프로젝트에서 디자이너와 디자인 시스템 관련해 소통할 일이 참 많았다. 에이전시에서 근무하다 보니 디자인 토큰을 활용한 디자인 시스템을 구축만 하고 운영까지는 이어지지 않았다. 때문에 원시값을 피그마 토큰 네이밍대로 CSS 변수에 수작업으로 하나하나 기입을 했던 경험이 있다. (짧은 개발 기간..)
사실 그 방법도 개발에서의 디자인 시스템 유지보수성에 대한 중요성을 안 지 얼마 안 됐을 때 실행한 거라서, 디자인 시스템을 제대로 도입한 회사처럼 Tokens Studio for Figma + Style Dictionary 같은 라이브러리를 활용을 하지 못했다.

하지만 백수(이자 홈 프로텍터)가 된 지금! 직접 Figma 플러그인을 만들어 보면 어떨까? 생각을 했다. 그리고 Claude Code CLI를 제대로 사용해 보고 실제 개발까지 이어지는 경험을 해 보고 싶었다.
음.. 여기에서는 구구절절 할 말이 많다.
앞서 말했듯이 나는 디자인 시스템을 처음부터 잘 아는 사람이 아니어서, 디자이너가 피그마로 디자인 토큰을 명시해 주면 그걸 하나하나씩 찾아 CSS 변수나 theme.ts 같은 파일에 하나하나씩 기입하고, 그걸 import나 var()로 활용했었다.
토큰의 수가 많아질 경우 혹시 내가 잘못 기입을 했는지 눈알이 빠지도록 검수에 검수에 검수를 했었다. 토큰의 영향 범위도 제어하기 어려웠다.
대신 이런 방법을 했었다. primitive -> semantic 2-tire로 원시값과 원시값을 참조하는 alias(별칭) 역할만 하는 구조로 .ts를 작성하고, 그걸 CSS 변수로 돌리는 제너레이터를 만들었다.
import { colors, typography } from "@/tokens";
import fs from "node:fs";
import path from "node:path";
type StringRecord = Record<string, string>;
type NestedStringRecord = Record<string, StringRecord>;
function sortedEntries<T extends StringRecord | NestedStringRecord>(obj: T) {
return Object.entries(obj).sort(([a], [b]) => a.localeCompare(b));
}
function generate() {
const outPath = path.join(process.cwd(), "styles/tokens.generated.css");
fs.mkdirSync(path.dirname(outPath), { recursive: true });
const lines: string[] = [];
lines.push("/* 자동 변환한 디자인 토큰. 건드리지 마세요. */");
// 1) TailwindCSS v4 유틸리티 토큰 레이어 (@theme)
lines.push("@theme {");
// colors
for (const [colorName, value] of sortedEntries(colors)) {
lines.push(` --color-${colorName}: ${value};`);
}
// typography
const TYPO_TO_TW: Record<string, string> = {
fontFamily: "font",
fontSize: "text",
lineHeight: "leading",
letterSpacing: "tracking",
fontWeight: "font-weight",
};
for (const [group, tokens] of sortedEntries(typography)) {
const prefix = TYPO_TO_TW[group];
if (!prefix) continue;
for (const [category, token] of sortedEntries(tokens)) {
lines.push(` --${prefix}-${category}: ${token};`);
}
}
lines.push("}");
fs.writeFileSync(outPath, lines.join("\n") + "\n", "utf8");
}
generate();
/* 자동 변환한 디자인 토큰. 건드리지 마세요. */
@theme {
--color-black: oklch(0.2221 0 0);
--color-bright: oklch(0.9712 0.0075 151.89);
--color-gray: oklch(0.8347 0.0101 125.71);
--color-green: oklch(0.8752 0.1855 136.41);
--color-pale: oklch(0.9223 0.0084 157.08);
--color-white: oklch(1 0 0);
--font-default: 'GoogleSansFlex', 'Pretendard', sans-serif;
--font-googleSansFlex: 'GoogleSansFlex', sans-serif;
--font-pretendard: 'Pretendard', sans-serif;
--text-12: 0.75rem;
--text-14: 0.875rem;
--text-16: 1rem;
문제점은 디자인 토큰이 color, font, spacing만 있는 게 아니라는 점이다. 나중에 opacity나 easing값까지 추가될 경우 이 제너레이터 함수를 수정하고 직접 빌드를 돌려야 한다.
소규모 프로젝트나 개인 프로젝트에서는 쓸만하지만 프로젝트의 볼륨이 커질수록 유지보수하는 데에 한계도 있었고, 2-tire로 구성한 만큼 각 컴포넌트의 hover, disabled 등 상태가 많아질 때는 --color-primary-disabled 같은 토큰을 따로 작성해 줘야 했다. 그런데 컴포넌트의 상태값에 따라 모든 컬러값이 다 같을 수도 없고.. 3-tire 구조로 디자인 시스템을 정의해야 토큰에 대한 의미가 담기고, 추후 디자이너와의 커뮤니케이션도 더 원활하게 될 것이다.
다시 말하지만 나는 Tokens Studio for Figma + Style Dictionary 조합을 사용한 적이 없다. 사이드 프로젝트 때 잠깐 활용해 보려고 했으나 개인적인 이유로 사이드 프로젝트 하차를 했기 때문에 지금까지.. 네..
하지만 여러 블로그 글도 읽어 보고 실제로 체험한 결과 아래와 같은 문제점이 있었다. (개인적으로 생각한 pain point다.)
1. 플러그인과 라이브러리를 따로 관리해야 하는 점
-> 피그마 플러그인을 통해 Github에 파일이 들어오면 라이브러리를 통해 CSS 변수나 .ts 파일로 변환해 주는 작업을 따로 해야 한다. 이걸 한꺼번에 해 줄 수는 없을까?
2. Github의 연동이 번거로움
-> 별도 Github Actions 설정이 필요했고, 토큰 저장 -> 변환 -> Push가 한 흐름으로 되지 않았다. 특히 Figma 플러그인 사용 시 Github에 연동하려면 유료 결제가 필요했다. (내가 다른 방법을 못 찾았던 건지..)
3. 네이밍 Lint가 없다.
-> 사이드 프로젝트 당시 디자이너가 이름에 이모지를 넣었다. 또한 오타나 특수문자, 잘못된 참조를 입력해도 그냥 저장이 되고, 실수가 나중에 빌드 오류로 터질 수도 있다는 단점이 있었다.
4. tire 강제가 없다.
-> Primitive Semantic, Component 구조를 권장하지만 강제성은 없다.
5. Style Dictionary 설정 파일이 복잡함
-> config.json + transform + format 개념을 알아야 하고, 커스텀 포맷을 만들려면 Javascript 코드를 직접 작성해야 했다.
Figma Token it이라는 의미를 가지고 피크닉으로 불리게끔 이름을 지은 건데 사실은 픽닉이라고 불리는 게 더 맞다. ㅎㅎ; 그래도 귀여운 이름이라고 생각하고 플러그인 이름으로 채택했다.
기존 피그마 플러그인과 라이브러리의 한계점에서 도출해 낸 차별점은 아래와 같다.
대략적인 MVP를 이렇게 설정하고 Claude Code를 실행했다.
실무에서 AI를 활용한 전반적인 워크플로우를 경험해 본 적이 없는 이유가 가장 크다. 남들은 AI로 N시간 만에 이것 만들었어요~ 하는데 나는.. 나는... 나는!! 해 본 적이 없다.. 이 기회에 Claude Code CLI를 활용해서 만들고자 했다.
또한 Figma API에 대한 기초 지식을 배워야 하는 등 선수 학습에 대한 시간 비용이 많이 든다는 것도 이유다. 이력서 쓰고 지원하고 과제도 하고 면접 준비하기도 바빴다. ㅠ
먼저, CLAUDE.md 파일에 MVP에 대한 가이드를 작성했다. 사실 이것도 AI로 작성했다. 내가 어떤 서비스를 만들 거고, 기존 방식에는 어떤 불편함이 있었고, 어떤 개선 방안을 생각했는데, 이걸 토대로 CLAUDE.md를 작성해 줘. <- 라고 했다.
# Ficnic — CLAUDE.md
## 프로젝트 개요
Figma 플러그인 + 라이브러리 통합 디자인 토큰 도구. Tokens Studio + Style Dictionary를 하나로 합친 도구로, 아래 차별점을 가짐:
- 3-tier 구조 (Primitive → Semantic → Component) 강제/가이드
- 네이밍 lint (이모지, 특수문자, 잘못된 계층 참조 방지)
- JSON 포맷 커스터마이징 (템플릿 3종 + 커스텀, 커스텀 같은 경우에는 사용자가 대략적인 Json 또는 ts 작성 시 그 구조로 자동 포맷 변환)
- JSON → CSS변수 / TS 파일 변환 (브라우저에서 직접 실행, 서버 없음)
- GitHub 자동 푸시
## 기술 스택
- Vite + React + TypeScript
- Figma Plugin API (@figma/plugin-typings)
- GitHub REST API (PAT 토큰으로 직접 호출)
## 폴더 구조
ficnic/
├── src/
│ ├── ui/ ← 플러그인 UI (React)
│ ├── sandbox/ ← Figma API 접근 코드 (sandbox 환경)
│ └── core/ ← 핵심 로직 (토큰 변환, lint, resolve)
├── manifest.json ← Figma 플러그인 설정
└── CLAUDE.md
## 개발 우선순위 (MVP)
### P0
- 토큰 편집 UI (3-tier 구조)
- 네이밍 lint
- GitHub 푸시
### P1
- JSON → CSS변수 / TS 변환
- JSON 포맷 템플릿 3종
### P2
- 실시간 미리보기
- 웹 대시보드
## 커밋 규칙
작업 완료 후 자동으로 아래 형식으로 커밋하고 푸시:
타입: 메시지
타입 종류:
- feat: 새 기능
- fix: 버그 수정
- refactor: 리팩토링
- style: 스타일/포맷
- chore: 설정, 패키지 등
- docs: 문서
### 커밋 자동화 룰
- 작업 완료 시 커밋 메시지 먼저 제안
- 확인 후 `git add . && git commit -m "..." && git push` 실행
- 커밋 단위는 기능 단위로 (파일 단위 X)
## 코딩 컨벤션
- 컴포넌트: PascalCase
- 함수/변수: camelCase
- 타입/인터페이스: PascalCase (prefix I 사용 X)
- 파일명: kebab-case
- 주석: 한국어 OK
## 절대 하지 말아야 할 것
### 코딩 스타일
- any 타입 사용 금지 — unknown 또는 명확한 타입으로
- 타입 단언(as) 남발 금지 — 불가피한 경우 주석으로 이유 명시
- console.log 커밋 금지 — 디버깅 후 반드시 제거
- 컴포넌트 파일 하나에 500줄 이상 금지 — 분리할 것
### Figma 플러그인 특유
- figma.\* API를 ui 코드(src/ui/)에서 직접 호출 금지 — sandbox에서만
- postMessage 없이 ui ↔ sandbox 직접 통신 시도 금지
- 동기 방식으로 Figma 노드 대량 순회 금지 — 플러그인 멈춤
### Claude CLI 작업 방식
- 확인 없이 파일 삭제 금지
- 작업 범위 밖의 파일 무단 수정 금지
- 한 번에 너무 많은 파일 동시 수정 금지 — 기능 단위로 나눠서
### Git
- main 브랜치에 직접 커밋 금지 — feature 브랜치 사용
- 빌드 안 되는 상태로 푸시 금지
- node_modules, dist 커밋 금지
## 하면 좋을 것
### 코딩 스타일
- 함수 하나는 하나의 역할만 — 길어지면 분리
- 복잡한 로직엔 한국어 주석으로 의도 설명
- 타입은 src/types/ 폴더에 모아서 관리
- 에러 처리는 항상 — 특히 GitHub API, Figma API 호출
### Claude CLI 작업 방식
- 작업 시작 전 현재 파일 구조 파악 후 시작
- 큰 기능은 계획 먼저 제안 → 확인 후 구현
- 커밋 메시지는 구현 전에 미리 제안
- 애매한 요구사항은 구현 전에 질문
### Git
- 브랜치명: feat/token-editor, fix/lint-emoji 형식
- PR 없이 squash merge — 사이드 프로젝트니까 가볍게
- 기능 완성될 때마다 커밋 — 하루 끝에 몰아서 X
기존에 동료 개발자가 .md 파일 잘 작성하는 법을 알려 줘서, 그것을 토대로 AI한테 작성해 달라고 했다. 지금 보니까 개선점이 있는 것 같다. 커밋 컨벤션에 맞춰서 커밋과 푸쉬는 잘하는 반면 브랜치 생성 및 브랜치에서 작업하라는 명령은 무시했다. 이 녀석.. 다음에는 호되게 혼내 줘야겠다.
즉, 내가 한 건 기능 요구사항을 말하고, 인간 e2e 테스트로 버그 발견하고, 방향을 결정한 것뿐이었다. 그러면 클로드가 알아서 코드를 작성해 주고, 파일 구조 설계, 리팩토링까지 끝냈다.

완전 테크놀로지아~

며칠 전 항해 플러스 프론트엔드 6기 수료생 친구들과 코치였던 테오와 함께 AI 관련 이야기 나누기 (겸 돼지 파티)에 참석했었다. 그때 테오가 준비한 세션에서 이런 말이 나와서 급하게 카메라로 찍었다.
직접 클로드를 돌려 보니 진짜.. 좀 알아서 성장했으면 좋겠다는 생각이 들었다. AI가 작성한 코드에 오류가 발생했다.
토큰 그룹(color, spacing 등)을 추가하면 콘솔에 Uncaught TypeError: ctypto.randomUUID is not a function이라는 오류가 발생했다.
원인은 Figma 플러그인 UI는 일반 브라우저가 아니라 Figma가 만든 제한된 iframe 안에서 실행된다. ctypto.randomUUID()는 HTTPS 환경에서만 동작하는 Web API인데, Figma iframe은 이 조건을 만족하지 않아서 함수 자체가 없다.
결국 환경을 감지하고, Figma에서 돌아가지 않으면 fallback을 만들어 달라고 요청했다.
function generateId(): string {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID()
}
// Figma iframe fallback
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
}
color.red.500이 이모지로 인식됨토큰 이름에 color.red.500을 입력하면 이모지를 입력하지 말라는 에러 메시지가 나온다. 또 숫자가 들어간 이름은 전부 등록이 불가능했다.
정규식을 직접 까 봤다.. 이모지 감지 정규식으로 \p{Emoji}를 사용했는데, 이 유니코드 속성은 숫자 0~9도 이모지로 분류한다. 유니코드 표준에서 0️⃣ ~9️⃣ 키캡 이모지의 기반 문자가 숫자이기 때문.
// 잘못된 코드
/\p{Emoji}/u.test('500') // → true ← 숫자가 이모지로 판정됨
// 올바른 코드
/\p{Emoji_Presentation}/u.test('500') // → false
결국 \p{Emoji_Presentation} 은 실제로 이모지로 렌더링되는 문자만 매칭하게끔 수정 요청을 했다.
토큰이나 그룹에 고유 ID를 붙이는 함수에서 자기 자신을 무한으로 호출하는 버그가 있었다. 증상이 없어서 발견이 늦었는데, 모든 작업을 완료하고 클로드가 스스로 코드 리뷰를 하게끔 했더니 해당 버그를 발견했다. 기특하다.
// Claude가 처음 짠 코드 (버그 있음)
function generateId(): string {
if (typeof crypto !== 'undefined' && ...) {
return generateId() // ← crypto.randomUUID() 라고 써야 하는데 자기 자신을 호출
}
return 'xxxxxxxx-...'.replace(...)
}
crypto.randomUUID() 대신 generateId() 를 재귀 호출하는 실수를 했다. Figma 환경에서는 crypto.randomUUID가 없으니 항상 fallback으로 빠져서 증상이 안 나왔음. 브라우저 개발 환경에서 crypto.randomUUID가 있었다면 무한 재귀 → 스택 오버플로우로 이어질 뻔했다.
전부 일반 브라우저에서는 가능한데 Figma에서는 안 되는 것들이었다. Figma 플러그인을 개발할 때, Figma UI는 브라우저처럼 보이지만 브라우저가 아님을 명심하면 좋을 듯 하다.
미완성된 개발 단계(아이콘도 못 정했고, 실제 시연 영상도 못 찍었고..)여서 Figma에서 공식적으로 지원하는 플러그인은 아니다.
소스코드는 여기에서 확인할 수 있으며 설치 및 실행은 README에서 확인할 수 있다.

Figma Desktop에서 manifest.json를 불러오면 플러그인에 이렇게 Ficnic이 뜬다.

실행하면 위와 같은 화면이 뜨는데, 아래 그룹 이름에 color, spacing 같은 토큰 그룹을 작성하고 + 그룹 버튼을 누르면 상세 토큰을 추가할 수 있다.


토큰을 추가하고 나면 Semantic -> Component 단계에서 쓰이지 않는 토큰은 미사용 토큰으로 분류된다. 다른 레이어에서 그 값을 참조하고 있으면 미사용이 아니라 사용으로 전환된다.

Github 연동이 간편하다. PAT 토큰을 발급받고, 저장소에 제공된 스크립트와 워크플로우 파일을 한 번만 세팅하면, 이후부터는 플러그인에서 저장 버튼만 눌러도 tokens.json 푸쉬 → CSS·TS 자동 생성까지 자동으로 돌아간다.
Figma 플러그인
↓ GitHub 푸쉬
tokens/tokens.json 업데이트
↓ GitHub Actions 자동 트리거
transform.mjs 실행
↓
tokens/tokens.css ← 자동 생성
tokens/tokens.ts ← 자동 생성
↓ 자동 커밋
사용하는 프로젝트에서 import
사용하려는 프로젝트에서 CI/CD만 설정해 주면,
.github/workflows/transform-tokens.yml
on:
push:
paths:
- 'tokens/tokens.json' # 이 파일 바뀌면 자동 실행
자동으로 TS, CSS로 변환해 준다.
// tokens.ts import
import { tokens } from './tokens/tokens'
const primary = tokens.color.primary[500]
/* tokens.css import */
@import './tokens/tokens.css';
.button {
background: var(--color-primary-500);
}

그외에도 CSS 변수나 TS 파일로 변환된 미리보기를 제공한다. 일회성이나 테스트를 위해서 복사 및 다운로드를 할 수 있다.

디자이너가 Figma에서 색상을 수정했을 때, 토큰과 불일치가 생긴 항목을 한눈에 확인할 수 있다. 자동 반영은 추후 지원 예정이다. 디자이너의 육성공지와 히스토리 추적에 많은 시간을 쏟는 게 싫어서 생각해 낸 방법이다.
실제 개발까지는 이어지지 못했다. 사실 개인 개발 블로그를 만들 때나, 다른 아이디어가 떠오를 때 활용하면 좋을 테지만 나는 Figma로 디자인을 하지 못한다.

(걍 미적 감각이 없기 때문에)
또한 실사용자들의 피드백도 받으면 좋겠지만 Claude Code CLI로 플러그인 개발함에 더 의의를 두고자 한다. 나중에 쓰일 일(사이드 프로젝트나 개인 프로젝트나)이 생기면 좋을 듯 하다. (Figma를 익혀야 하겠지만..)
물론 이게 완성도 100%짜리 툴은 아니다. 내가 불편했던 걸 직접 해결해 본 경험이다. 코드는 클로드가 작성했지만, 버그를 발견한 건 나고, 방향성을 제시하고 결정한 것도 나였다. (인간 E2E)
처음으로 바이브 코딩이라는 걸 해 봤는데 딸깍충처럼 했지만 ㅋㅋ 리팩토링에 더 많은 시간을 투자해야 할 것 같다. 지금은 색상 토큰 Sync만 되지만, 앞으로는 Figma 값이 변경하면 Github에 푸쉬하기 버튼을 누르지 않아도 자동으로 반영되고, 실시간 미리보기, 웹 대시보드까지 붙일 계획이다.
코드는 Github에 공개되어 있으니 (만약 사용할 일이 있다면) 써 보고 피드백 주시면 좋겠다.

This is a really insightful post 👍
I’ve been building with Claude Code too, and totally agree — it’s less about coding now and more about structuring prompts and validating outputs.
I actually used a similar workflow to build an AI image tool recently, and the speed of iteration is crazy:
https://nanobananagen.org/
Excited to see more projects like this.