YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
Introduction▾
Tutorials▾
Core Concepts▾
Architecture▾

Recipe Layer

A primary button needs about a dozen classes. Written out on every button, those dozen classes drift apart, and changing the look means editing every copy. A recipe names that look once, and every button asks for it by name.
Technically, a recipe is a variant factory built on tailwind-variants, and it sits between the token layer and the components. Each layer answers one question and knows only the layer below it:
tokens
CSS variables in styles.css. Theme-aware, server/client agnostic. What is this color?
recipes
Variant factories that compose tokens. Server-safe, no use client. How does it look?
components
Consume recipes and add interaction and state; use client only when needed. How does it behave?
Words used on this page
recipe
A function that returns the class string for one look, built with recipe(tv({ … })).
variant
One named axis of a recipe (variant, size, side) and the values it can take.
semantic token
A color named for its role (primary, background, destructive) instead of its value.
slot
A named place (button, badge, input) where a route's _overrides.tsx can swap a recipe.
Why a recipe works on both sides
One recipe, callable from both sides
buttonRecipe carries no use client, so a server component and a client component can both call it and get the same class string.

Framework Recipes

akanjs/ui ships buttonRecipe, badgeRecipe and inputRecipe from a server-safe module, and the Button, Badge and Input components use the same recipes inside. So a raw element styled with a recipe looks exactly like the component.
Pass the variants as the first argument and any extra classes as the second. The recipe merges them with tailwind-merge for you, so you never wrap it in cn():
What each framework recipe accepts
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
Bold values are the defaults. outline is a flag: it keeps the variant's color and draws it as an outline.
Every variant class is a semantic token (bg-primary, text-success-foreground …), so every value follows the theme automatically.

App-Level Recipes

Your app has repeating surfaces the framework knows nothing about: a gradient hero, an icon tile, a chat bubble. Instead of inlining the same class string everywhere, give each one an app recipe:
  1. Add one file per recipe under the app's ui/Recipe/. App ui folders are PascalCase, so it is ui/Recipe/ even though the framework's is the lowercase ui/recipe/.
  2. Build the factory with recipe(tv({ base, variants })). Both are re-exported from akanjs/ui.
  3. Name it <name>Recipe and keep the file free of 'use client'.
  4. Import it from the ui barrel. The folder's index.ts also re-exports the framework recipes, so one import path covers both.
apps/myapp/ui/Recipe/chatBubble.ts
The page then stops repeating class strings and reads its variant from data:
apps/myapp/page/(home)/inbox/chat.tsx

When To Reach For A Recipe

Recipes earn their keep when a class set is reused, conditionally composed, or needed from a server component. One-off classes should stay inline. Find your case in the left column:
Situation
New recipe
Variant
Inline
A class set you would otherwise repeat
Repeated or variant-like surface
✓
A status pill, a hero, a bubble or a tile: extract a recipe.
Class chosen from a fixed set by data
✓
Tone, size, side or status decides the class: make it a variant of a recipe.
Styling a server component or raw element
✓
A recipe is server-safe, so a server page can call it directly.
A class you write once
Genuinely one-off className
✓
Keep it inline. Do not over-abstract.
✓Reach for thisNot this

Recipe Override — Re-skin Without Rebuilding

Sometimes one section of the app needs a different look, say a neon admin area, but the components should behave exactly as before. A recipe override changes the look and nothing else.
A route's _overrides.tsx can swap a recipe slot (button, badge, input). Every framework client component that consumes that recipe re-skins across the whole route subtree, while its behavior (async states, focus trap, a11y) stays exactly as the framework ships it. Only the className factory changes.
First, write a recipe with the same variant surface as the one it replaces:
apps/myapp/ui/Recipe/neonButton.ts
Then bind it to the slot in the section's _overrides.tsx. The screens under it do not change a line:
apps/myapp/page/(section)/_overrides.tsx
What the swap reaches
Caller
New look
Canonical
Inside the route subtree
Framework client components
✓
Button, Badge, Input, Dropdown, Pagination … read the slot, so they re-skin.
Server components (Unit, View)
✓
They keep the canonical recipe.
buttonRecipe(...)
✓
A raw call in your own JSX keeps the canonical recipe; import your own recipe there instead.
✓Uses this recipeNot this one

Three Questions, One Invariant

Customization is decided once, at design-system setup, not per screen. Compare your design spec with the /lab catalog once, then run each difference through the questions below.
Q1. Does the theme differ?
Color · radius · font
Yes → Override token values in the app's page/styles.css.
No → Keep the Akan defaults.
Q2. Does a component look differ?
Same structure, different skin
Yes → Write an app recipe and inject it through the recipes of _overrides.tsx.
No → Use it as it is.
Q3. Does structure or behavior differ?
For example, modal → drawer
Yes → Write a component override that reassembles the headless parts.
No → Not needed.
A surface the lib does not have?
A chat bubble, a tile
Yes → Add a new app recipe. It is an extension with no lib counterpart, so nothing conflicts.
No → Use the lib's recipe.

Released under the MIT License

Connect your AI to these docs

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js All rights reserved.System managed bybassman