사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
소개▾
튜토리얼▾
핵심 개념▾
시스템 아키텍처▾

레시피 레이어

primary 버튼 하나에는 클래스가 열 개쯤 필요합니다. 버튼마다 그 열 개를 적으면 조금씩 어긋나고, 모양을 바꾸려면 모든 사본을 고쳐야 합니다. recipe는 그 모양에 한 번 이름을 붙이고, 모든 버튼이 그 이름으로 가져다 쓰게 합니다.
기술적으로 recipe는 tailwind-variants 기반의 변형 팩토리이고, 토큰 계층과 컴포넌트 사이에 있습니다. 계층마다 질문 하나에 답하고, 바로 아래 계층만 압니다:
tokens
styles.css의 CSS 변수입니다. 테마를 따르고 서버/클라이언트를 가리지 않습니다. 이 색은 무엇인가?
recipes
토큰을 조합하는 변형 팩토리입니다. 서버에서도 안전하고 use client가 없습니다. 어떻게 보이는가?
components
recipe를 쓰고 상호작용과 상태를 더합니다. use client는 필요할 때만. 어떻게 동작하는가?
이 페이지에서 쓰는 말
recipe
모양 하나의 클래스 문자열을 돌려주는 함수입니다. recipe(tv({ … }))로 만듭니다.
variant
recipe의 이름 붙은 축 하나(variant, size, side)와 그 축이 가질 수 있는 값들입니다.
semantic token
값이 아니라 역할로 이름 붙인 색입니다. primary, background, destructive 같은 이름입니다.
slot
라우트의 _overrides.tsx가 recipe를 갈아 끼울 수 있는 이름 붙은 자리(button, badge, input)입니다.
recipe가 양쪽에서 동작하는 이유
recipe 하나를 양쪽에서 호출
buttonRecipe에는 use client가 없으므로 서버 컴포넌트와 클라이언트 컴포넌트가 모두 호출해 같은 클래스 문자열을 받습니다.

프레임워크 레시피

akanjs/ui는 서버에서도 안전한 모듈에서 buttonRecipe, badgeRecipe, inputRecipe를 제공하고, Button, Badge, Input 컴포넌트도 안에서 같은 recipe를 씁니다. 그래서 recipe로 꾸민 raw 엘리먼트는 컴포넌트와 똑같이 보입니다.
첫 번째 인자로 variant를, 두 번째 인자로 추가 클래스를 넘깁니다. recipe가 tailwind-merge로 알아서 합쳐 주므로 cn()으로 감쌀 필요가 없습니다:
프레임워크 recipe가 받는 값
buttonRecipe
variantdefault · primary · secondary · accent · neutral · outline · ghost · destructive · success · warning · info · link
sizexs · sm · md · lg · icon
shapedefault · square · circle
outlinetrue
badgeRecipe
variantdefault · primary · secondary · accent · neutral · success · warning · info · error · outline
sizexs · sm · md · lg
outlinetrue
inputRecipe
kindfield · area
sizexs · sm · md · lg · xl
tonedefault · primary · error
굵은 값이 기본값입니다. outline은 플래그로, variant의 색을 유지한 채 외곽선으로 그립니다.
모든 variant 클래스는 시맨틱 토큰(bg-primary, text-success-foreground …)이라서, 어떤 값이든 자동으로 테마를 따라갑니다.

앱 레벨 레시피

앱에는 프레임워크가 모르는 반복 표면이 있습니다. 그라디언트 히어로, 아이콘 타일, 챗 버블 같은 것입니다. 같은 클래스 문자열을 곳곳에 인라인하지 말고, 각각에 앱 recipe를 만드세요:
  1. 앱의 ui/Recipe/ 아래에 recipe마다 파일 하나를 추가합니다. 앱 ui 폴더는 PascalCase라서, 프레임워크의 소문자 ui/recipe/와 달리 ui/Recipe/입니다.
  2. 팩토리는 recipe(tv({ base, variants }))로 만듭니다. recipe와 tv 모두 akanjs/ui에서 re-export됩니다.
  3. 이름은 <name>Recipe로 짓고, 파일에 'use client'를 넣지 않습니다.
  4. ui 배럴에서 import합니다. 폴더의 index.ts가 프레임워크 recipe도 재수출하므로 import 경로 하나로 둘 다 씁니다.
apps/myapp/ui/Recipe/chatBubble.ts
그러면 페이지는 클래스 문자열 반복을 멈추고 데이터에서 variant를 읽습니다:
apps/myapp/page/(home)/inbox/chat.tsx

언제 레시피를 쓸까

클래스 묶음이 재사용되거나, 조건에 따라 조합되거나, 서버 컴포넌트에서 필요할 때 recipe가 제값을 합니다. 일회성 클래스는 인라인으로 두세요. 내 경우를 왼쪽 열에서 찾아보세요:
상황
새 recipe
variant
인라인
안 그러면 반복하게 될 클래스 묶음
반복되거나 variant 같은 표면
✓
상태 pill, 히어로, 버블, 타일 같은 것입니다. recipe로 뽑아냅니다.
데이터로 고정된 집합에서 고르는 클래스
✓
tone, size, side, status가 클래스를 정합니다. recipe의 variant로 만듭니다.
서버 컴포넌트나 raw 엘리먼트 꾸미기
✓
recipe는 서버에서도 안전하므로 서버 페이지가 직접 호출할 수 있습니다.
한 번만 쓰는 클래스
정말 일회성인 className
✓
인라인으로 둡니다. 과하게 추상화하지 마세요.
✓이것을 씁니다이것이 아닙니다

레시피 오버라이드 — 재구현 없이 리스킨

앱의 한 구역만 모양이 달라야 할 때가 있습니다. 네온 스타일의 관리자 구역처럼요. 그래도 컴포넌트의 동작은 그대로여야 합니다. recipe override는 모양만 바꾸고 나머지는 건드리지 않습니다.
라우트의 _overrides.tsx는 recipe 슬롯(button, badge, input)을 교체할 수 있습니다. 그 recipe를 쓰는 프레임워크 클라이언트 컴포넌트는 라우트 서브트리 전체에서 새 모양이 되지만, 동작(async 상태, 포커스 트랩, a11y)은 프레임워크 그대로입니다. 바뀌는 것은 className 팩토리뿐입니다.
먼저, 교체할 recipe와 같은 variant 표면을 가진 recipe를 씁니다:
apps/myapp/ui/Recipe/neonButton.ts
그다음 그 구역의 _overrides.tsx에서 슬롯에 연결합니다. 그 아래 화면 코드는 한 줄도 바뀌지 않습니다:
apps/myapp/page/(section)/_overrides.tsx
교체가 닿는 곳
호출하는 쪽
새 모양
원래 모양
라우트 서브트리 안에서
프레임워크 클라이언트 컴포넌트
✓
Button, Badge, Input, Dropdown, Pagination …은 슬롯을 읽으므로 새 모양으로 바뀝니다.
서버 컴포넌트(Unit, View)
✓
canonical recipe를 그대로 씁니다.
buttonRecipe(...)
✓
내 JSX 안의 직접 호출은 canonical recipe를 씁니다. 거기서는 내 recipe를 직접 import합니다.
✓이 recipe를 씀이쪽이 아님

세 질문, 하나의 불변

커스터마이징은 화면마다가 아니라 디자인 시스템을 셋업할 때 한 번 결정합니다. 디자인 스펙을 /lab 카탈로그와 한 번 대조한 뒤, 다른 점마다 아래 질문에 넣어 보세요.
Q1. 테마가 다른가?
색 · 각도 · 폰트
예 → 앱의 page/styles.css에서 토큰 값을 override합니다.
아니오 → akan 기본값을 씁니다.
Q2. 컴포넌트의 look이 다른가?
구조는 같고 겉모습만 다름
예 → 앱 recipe를 쓰고 _overrides.tsx의 recipes로 주입합니다.
아니오 → 그대로 씁니다.
Q3. 구조나 동작이 다른가?
예: 모달 → 드로어
예 → headless 부품을 다시 조립하는 component override를 씁니다.
아니오 → 필요 없습니다.
lib에 없는 표면인가?
챗 버블, 타일
예 → 앱 recipe를 새로 추가합니다. lib에 대응물이 없는 확장이라 충돌하지 않습니다.
아니오 → lib의 recipe를 씁니다.

MIT 라이선스 하에 배포되었습니다.

내 AI에 이 문서 연결하기

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman