Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
2b3701c
feat(figma-variables): Figma variables exported 2026-05-19 19:52 CET
Luko248 May 19, 2026
c561a8d
feat(figma-variables): Figma variables exported 2026-05-19 21:43 CET
Luko248 May 19, 2026
59e8d22
chore(tools): add figma-token-binding skill + scripts
Luko248 May 28, 2026
40ac3b8
feat(ui-kit/badge): bind Badge to Figma tokens via light-dark()
Luko248 May 28, 2026
714a3ef
feat(ui-kit/button): bind Button to Figma tokens via light-dark()
Luko248 May 28, 2026
e9dc0ab
feat(ui-kit/checkbox): bind Checkbox to Figma tokens via light-dark()
Luko248 May 28, 2026
4ed2b87
feat(ui-kit/icon): bind Icon to Figma tokens via light-dark()
Luko248 May 28, 2026
944cd0b
feat(ui-kit/input): bind Input to Figma tokens via light-dark()
Luko248 May 28, 2026
6893b41
feat(ui-kit/label): bind Label to Figma tokens via light-dark()
Luko248 May 28, 2026
8d9ec78
feat(ui-kit/link): add Figma-bound _link.css and register import
Luko248 May 28, 2026
83a0f8e
feat(ui-kit/numeric-input): bind NumericInput to Figma tokens via lig…
Luko248 May 28, 2026
a80604f
feat(ui-kit/rating): bind Rating to Figma tokens via light-dark()
Luko248 May 28, 2026
3f5e4aa
feat(ui-kit/skeleton): bind Skeleton to Figma tokens via light-dark()
Luko248 May 28, 2026
7f9780b
feat(ui-kit/status-text): bind StatusText to Figma tokens via light-d…
Luko248 May 28, 2026
c9e5c43
feat(ui-kit/textarea): bind Textarea to Figma tokens via light-dark()
Luko248 May 28, 2026
b6fb07c
feat(ui-kit/tooltip): bind Tooltip to Figma tokens via light-dark()
Luko248 May 28, 2026
c323ec9
chore(ui-kit/figma): biome formatting for Code Connect templates
Luko248 May 28, 2026
a377e12
feat(apps,ui-kit): rebind brand overrides to new Figma button token n…
Luko248 May 28, 2026
d9b1170
fix(ui-kit/input): use renamed pe/ps-input-with-button utilities
Luko248 May 28, 2026
0691721
fix(ui-kit/link): alias derived fg tokens via the reference layer
Luko248 May 28, 2026
0a50a7d
docs(ui-kit): add THEME-FLOW.md visualizing token pipeline
Luko248 May 28, 2026
7fa5d85
fix(ui-kit/badge): add dark-mode variants for info/warning/danger/dis…
Luko248 May 28, 2026
615b442
docs(ui-kit): drop THEME-FLOW.md (out of PR scope)
Luko248 May 28, 2026
ccd02aa
feat(tools): primitive aliasing (Strategy B) + PRIMITIVES-STRATEGY doc
Luko248 May 29, 2026
18f7572
refactor(ui-kit/tokens): atoms alias fluid primitives where values match
Luko248 May 29, 2026
6631cc6
fix(ui-kit/tokens): address CodeRabbit Phase 1 findings
Luko248 May 31, 2026
d87ca4e
refactor(ui-kit/tokens): restore two-layer pattern across 12 atoms
Luko248 May 31, 2026
c09f93e
refactor(ui-kit/tokens): drop -base suffix on single-state fg tokens
Luko248 May 31, 2026
a7e003c
refactor(ui-kit/tokens): link ProductCard button slots to Button atom…
Luko248 May 31, 2026
ea912d7
refactor(figma-tokens): strip /base from 21 component-specific tokens
Luko248 May 31, 2026
3974d1a
fix(figma-token-binding): scope generator strip + WCAG-fix toast cont…
Luko248 May 31, 2026
85f1604
fix(ui-kit): address 2nd-round CodeRabbit findings on #425
Luko248 May 31, 2026
bfc6103
fix(figma-tokens): dedupe text/duration scale (Semantic+Core overlap)
Luko248 May 31, 2026
9267419
feat(figma-variables): Figma variables exported 2026-05-31 13:10 CET
Luko248 May 31, 2026
a4ce4d0
fix(figma-token-binding): replace previous generated region instead o…
Luko248 May 31, 2026
26c4646
feat(figma-variables): Figma variables exported 2026-05-31 13:52 CET
Luko248 May 31, 2026
42e8bbb
feat(figma-variables): Figma variables exported 2026-05-31 14:01 CET
Luko248 May 31, 2026
353c026
fix(figma-token-binding): capture prefix/suffix strings instead of st…
Luko248 May 31, 2026
710b51b
feat(figma-variables): Figma variables exported 2026-05-31 14:53 CET
Luko248 May 31, 2026
79bb960
feat(ui-kit/tokens): wire Figma export into Tailwind runtime
Luko248 May 31, 2026
3f075c6
refactor(ui-kit/tokens): strip _badge.css duplicates — Figma now owns
Luko248 May 31, 2026
f27f16e
refactor(ui-kit/tokens): strip _button.css duplicates — Figma now owns
Luko248 May 31, 2026
1503e38
refactor(ui-kit/tokens): strip _checkbox.css duplicates — Figma now owns
Luko248 May 31, 2026
7e892c8
refactor(ui-kit/tokens): strip _icon.css duplicates — Figma now owns
Luko248 May 31, 2026
b8b3171
refactor(ui-kit/tokens): strip _input.css duplicates — Figma now owns
Luko248 May 31, 2026
0ed30e5
refactor(ui-kit/tokens): strip _numeric-input.css duplicates — Figma …
Luko248 May 31, 2026
e37cb78
refactor(ui-kit/tokens): strip _rating.css duplicates — Figma now owns
Luko248 May 31, 2026
791a469
refactor(ui-kit/tokens): strip _skeleton.css duplicates — Figma now owns
Luko248 May 31, 2026
af19a76
refactor(ui-kit/tokens): strip _status-text.css duplicates — Figma no…
Luko248 May 31, 2026
40086b3
refactor(ui-kit/tokens): strip _textarea.css duplicates — Figma now owns
Luko248 May 31, 2026
73e9062
refactor(ui-kit/tokens): strip _tooltip.css duplicates — Figma now owns
Luko248 May 31, 2026
5d94809
refactor(ui-kit/tokens): strip _label.css and _link.css duplicates — …
Luko248 May 31, 2026
f9151fa
chore: gitignore .visual-diff/ (per-atom Storybook screenshots for ve…
Luko248 May 31, 2026
c6defab
feat(figma-variables): Figma variables exported 2026-06-02 10:05 CET
Luko248 Jun 2, 2026
d21b9a1
refactor(ui-kit/tokens): Wave 1 — strip dead atom duplicates Figma no…
Luko248 Jun 2, 2026
4138813
docs(ui): rewrite THEME-FLOW.md to mirror current Figma-source pattern
Luko248 Jun 2, 2026
a2e0c6f
Merge branch 'master' into lchylik/feat/figma-token-binding-atoms
KaiUweCZE Jun 9, 2026
e3ad2a2
Merge branch 'master' into lchylik/feat/figma-token-binding-atoms
KaiUweCZE Jun 10, 2026
58352f3
fix libs/ui in herbatica
KaiUweCZE Jun 10, 2026
5317a08
fix spacing in herbatica: button, select
KaiUweCZE Jun 10, 2026
58489d8
fix input height
KaiUweCZE Jun 10, 2026
7cf70c9
fix: align token values with master
KaiUweCZE Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .agents/skills/component-to-figma/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ Create or update Figma variables strictly in tier order: core first, then semant

Core variables hold **raw values only**. Names must be maximally short — strip all redundant words like "Primitive", "Space", "Default".

> **Fluid responsive sizing.** If the codebase uses Utopia-style
> `clamp(min, mid + vw, max)` for spacing/typography, see
> [`../figma-token-binding/PRIMITIVES-STRATEGY.md`](../figma-token-binding/PRIMITIVES-STRATEGY.md).
> Figma can store either a single value (Strategy B — code keeps the clamp
> formula, atoms alias the primitive) or a min/max pair via an extra mode
> axis (Strategy A — Figma is the full source of truth). Pick the strategy
> *before* populating core sizing values, since Strategy A changes the
> collection mode schema.

**Naming pattern**: `type/value`

Examples — sizing and spacing:
Expand Down Expand Up @@ -289,9 +298,17 @@ When it varies by size, keep `type/component/size`.
- **Semantic**: `type/role` — e.g., `size/sm`, `color/primary/base`, `radius/md`
- **Component color**: `color/component/cssProperty/variant/state` — depth varies per component
- **Component non-color**: `type/component/size` — flat, no redundant segments (`root`, `gap`, `x`, `y` are forbidden), no state suffix because these values don't change between states
- **State suffix rule — add a state segment only when the value varies by state:**
- Color tokens with multiple states: use **"base"** for the resting state, never "default" (e.g., `color/button/bg/primary/base`, `color/button/bg/primary/hover`)
- Tokens that are state-invariant (padding, radius, spacing, border-width, text size, font-weight): **no state suffix at all** — not "base", not "default". The name ends at the size or role segment (e.g., `radius/button/sm`, `padding/button/sm`, `spacing/button/sm`).
- **State suffix rule — add a state segment only when the value varies by state. This applies to EVERY token, color and non-color alike:**
- **Multi-state tokens** (the value differs between states like base/hover/active/focus/disabled): use **"base"** for the resting state, never "default" (e.g., `color/button/bg/primary/base`, `color/button/bg/primary/hover`, `color/button/bg/primary/active`).
- **Single-state tokens** (the value is identical across all states the component supports): **no state suffix at all** — not "base", not "default". The name ends at the variant/size/role segment.
- This rule applies to **color tokens too**. A color token that does not vary by state must NOT carry a `-base` suffix. Examples:
- `color/button/fg/primary` — solid primary text is the same color in default, hover, and active. **No `/base`.**
- `color/button/fg/outlined/primary` — outlined primary text doesn't change on hover/active. **No `/base`.**
- `color/button/border/primary` — outlined border color doesn't change on hover. **No `/base`.**
- `color/button/bg/disabled` — only one disabled bg exists; there is no `disabled/hover`. **No `/base`.**
- `color/button/bg/outlined` — transparent across every outlined variant; no hover/active sibling. **No `/base`.**
- **State-invariant non-color tokens** (padding, radius, spacing, border-width, text size, font-weight): same rule. Name ends at the size or role segment (e.g., `radius/button/sm`, `padding/button/sm`, `spacing/button/sm`).
- **How to test:** if there is no `-hover`, `-active`, `-focus`, or `-disabled` sibling for the token, the `-base` suffix is wrong — drop it.
- **Modes**: light and dark only, applied to color variables. No responsive modes.
- **Inheritance is strict**: component => semantic => core. No skipping tiers except when no semantic equivalent exists.

Expand Down
99 changes: 99 additions & 0 deletions .agents/skills/figma-token-binding/PRIMITIVES-STRATEGY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Primitives strategy for Figma ↔ code token binding

How spacing, typography, radius, border-width, and other sizing primitives are handled in the Figma export → code pipeline, including the fluid-responsive (Utopia clamp) tokens.

## The problem

Figma variables hold **single values per mode**. The code uses **fluid `clamp(min, mid + vw, max)`** for spacing and typography so the UI scales gracefully between mobile and desktop breakpoints. When the figma-token-binding skill writes literal values into component CSS, the fluid scaling is lost — every padding, spacing, and font size becomes static.

## Two strategies

### Strategy A — Figma owns min + max

Make Figma the source of truth for the fluid envelope:

- Add a 2nd axis to the Semantic collection's modes: `Light-min` / `Light-max` / `Dark-min` / `Dark-max` (4 modes) — or a separate `Min` / `Max` mode pair if L = D for sizing.
- Each primitive (`spacing/200`, `text/md`, …) carries two values; the splitter reads both and emits `clamp(min, midpoint+vw, max)` into `_spacing.css` / `_typography.css`.
- Component atoms then **alias the primitives** (`--text-button-sm: var(--text-sm)`).

**Trade-offs**
- ✅ Figma is end-to-end source of truth.
- ❌ Requires Figma schema work (mode reshape).
- ❌ Splitter has to encode the Utopia midpoint/vw formula.
- ❌ Cross-collection coordination: every per-component collection needs the same min/max axis.

### Strategy B — Code owns fluid, atoms alias primitives *(current)*

Keep Utopia `clamp()` in the code primitives. The script detects when a Figma-exported atom value matches a known primitive's value and emits `var(--primitive)` instead of the literal.

```css
/* Before (literal from Figma) — fluid scaling lost */
--text-input-sm: 1rem;

/* After (Strategy B alias) — restores fluid scaling */
--text-input-sm: var(--text-sm);
/* resolves to clamp(0.9375rem, 0.9158rem + 0.1087vw, 1rem) */
```

**Trade-offs**
- ✅ Zero Figma changes.
- ✅ Restores fluid scaling for all migrated atoms immediately.
- ✅ Matches the two-layer convention in CLAUDE.md (reference layer = primitive, derived layer = component).
- ✅ Mechanically simple — one lookup table, deterministic mapping.
- ⚠ Figma's typography/spacing numbers are decorative for designers — they don't drive runtime output.
- ⚠ When you change a primitive value in code, designers won't see it in Figma until they sync manually.

## Current implementation (Strategy B)

`apply-light-dark.mjs` maps Figma single values to primitive aliases per property prefix:

| Figma token kind | Looks up in | Emits if match |
|---|---|---|
| `--text-{comp}-{size}` | `--text-{xs,sm,md,lg,xl,2xl}` max | `var(--text-{size})` |
| `--padding-{comp}-…` | `--spacing-{50,100,150,200,250,…}` max | `var(--spacing-{n})` |
| `--spacing-{comp}-…` | `--spacing-*` max | `var(--spacing-{n})` |
| `--radius-{comp}-{size}` | `--radius-{none,sm,md,lg,full}` | `var(--radius-{size})` |
| `--border-width-{comp}-…` | `--border-width-{sm,md,lg}` | `var(--border-width-{n})` |

Mapping is exact-string against rem values that Figma writes (e.g., `1rem`, `0.94rem`). Borderline cases (where the Figma rounding deviates from the clamp max by < 1px) are matched explicitly in the lookup table; everything else falls through to the literal.

The mapping table lives at the top of `apply-light-dark.mjs` as `PRIMITIVE_ALIAS_BY_PREFIX`. Update it when adding new primitive scales.

## Decision rule, per token

When the script sees `--<prefix>-<comp>-<rest>: <value>` in a component file, it tries in order:

1. Is the value identical to a sibling reference-layer token (`--color-X-{bg|fg|border}-Y` ↔ `--color-X-Y`)?
→ emit `var(--ref)` (CLAUDE.md two-layer convention)
2. Does the value (with L = D) match a known primitive of the same property kind?
→ emit `var(--primitive)` (this strategy)
3. Does L ≠ D in Figma?
→ emit `light-dark(L, D)`
4. Default
→ emit the literal value

## What this does not cover

- **OKLCH derivation chains** (`oklch(from var(--X) calc(l + var(--state-hover)) c h)`) — Figma can't compute these. They live in `_semantic.css`.
- **Breakpoints, aspect ratios, shadows** — not in Figma. Stay code-owned.
- **`--text-3xl`, `--text-4xl`** — used in code, missing from Figma. Designers should add them to Figma if needed.
- **Border width naming mismatch** — Figma uses `border/sm`, code uses `border-width-sm`. The script handles the prefix dispatch internally; consider renaming in Figma for consistency.

## When to migrate to Strategy A

Migrate when one of these is true:
- Designers start changing fluid envelopes in code reviews instead of in Figma.
- The primitives table in `apply-light-dark.mjs` grows past ~30 entries.
- A new project adopts the binding workflow with different breakpoints — the in-code clamp formulas become per-project rather than shared.

When that happens:
1. Add the `Min` / `Max` modes to the Semantic collection in Figma.
2. Write a new splitter that emits `_spacing.css` / `_typography.css` from the min+max export.
3. Remove the primitive alias lookup from `apply-light-dark.mjs` — atom values will already match primitives because both come from Figma.
4. Re-run on all atoms; the existing alias detection collapses duplicated values.

## Related references

- Skill workflow: [`./SKILL.md`](./SKILL.md)
- Visual diagram: regenerate via `.agents/skills/figma-token-binding/...` (or local `libs/ui/THEME-FLOW.md`)
- Codebase convention: `libs/ui/CLAUDE.md` (two-layer reference → derived)
Loading
Loading