Skip to content

feat(react-grab): resolve style edits to design tokens from CSS variables#487

Merged
aidenybai merged 9 commits into
mainfrom
cursor/style-design-tokens-947a
Jun 27, 2026
Merged

feat(react-grab): resolve style edits to design tokens from CSS variables#487
aidenybai merged 9 commits into
mainfrom
cursor/style-design-tokens-947a

Conversation

@aidenybai

@aidenybai aidenybai commented Jun 25, 2026

Copy link
Copy Markdown
Owner

What

Makes Style mode "switch to design tokens when available," derived from the CSS custom properties already present in the page's cascade.

Because design tokens surface as CSS variables across essentially every styling system, this is library-agnostic — it works for shadcn/ui, Radix, Chakra, MUI, Tailwind v4 @theme, Panda, vanilla-extract, etc., not just one hard-coded framework. (Today the only library awareness in Style mode is the Tailwind class chip/auto-apply.)

It does two things:

  1. Annotates the copied prompt with the matching token.
  2. Snaps arrow-key stepping in the Style panel through the token scale.

How

  • New utils/collect-design-tokens.ts: scans document.styleSheets for --* declarations (cross-origin sheets skipped safely, grouping rules recursed), then resolves each token against the grabbed element via getComputedStyle so the active theme/scope wins. It classifies tokens into colors and px/rem lengths and returns a small DesignTokenResolver (matchColor, matchLength, stepLength, hasTokens).
  • The resolver is built once when a Style session opens (stored on EditPanelState) and reused for both stepping and the copied prompt.
  • format-edit-prompt.ts annotates each declaration whose value matches a token with a /* var(--token) */ hint, plus a guidance line nudging the agent to prefer the token.
  • step-property.ts uses stepLength so a plain / on a px property walks the project's token scale.

Real-world token compatibility

Validated against the react-grab website (Tailwind v4 + shadcn). Two things were needed for it to actually fire there:

  • Wide-gamut colors. getComputedStyle returns theme colors as lab(...) (Tailwind v4 / shadcn compile oklch(...) down to lab()), which the old parser couldn't read. parseAnyColor now has a canvas rasterize fallback: it paints one pixel and reads it back, so any browser-renderable color — lab(), lch(), oklab(), oklch(), color() — resolves to sRGB. On the live site this took color-token detection from 0 → 14 tokens (--card, --accent, --primary, …).
  • Tailwind's spacing grid. Tailwind v4 exposes spacing as calc(var(--spacing) * N) with a single --spacing base rather than discrete per-step tokens. stepLength now walks that base-unit grid for spacing/sizing when there's no discrete scale, so / step 16px → 20px → 24px on the 4px grid.

Avoiding false matches

Lengths are numerically ambiguous (a 16px padding would otherwise "match" a --text-base: 1rem font-size token), so a length token only matches when its name shares the CSS property's family (spacing / size / radius / font-size / line-height / letter-spacing / border-width). Colors match by value (deterministic shortest-name tie-break). The literal value is always preserved in the prompt — the token is a hint, never a forced replacement — so there's zero visual-regression risk.

Arrow-key stepping guard rails

  • Shift keeps the coarse raw step.
  • A discrete token scale (Radix/Chakra/--text-*) snaps to neighbours; above the largest token it falls back to a raw step rather than teleporting; below the smallest it snaps up onto the scale.
  • With only a base unit (Tailwind --spacing), it walks that grid.

Testing

  • e2e tests in edit-panel.spec.ts: length/color prompt annotation, non-token length unannotated, ←/→ snapping through the spacing scale, and the raw-step fallback at the top of the scale. Definition-only tokens added to the Vite e2e app's :root (no visual change).
  • Verified end-to-end against the live website (Tailwind v4 + shadcn): colors resolve (lab → sRGB) and spacing steps on the 4px grid.
  • pnpm build, pnpm typecheck, pnpm lint pass. Full edit-panel.spec.ts + edit-panel-color.spec.ts chromium suites pass (124/124).

Notes / follow-ups

Surfacing the matched token directly in the Style panel chip (shift-held, alongside/instead of the Tailwind class) is a natural follow-up, kept out to limit blast radius on the panel's UI e2e tests.

Open in Web Open in Cursor 

Summary by cubic

Style mode in react-grab now resolves edits to CSS‑variable design tokens, snaps Arrow‑key px stepping to the token scale (or Tailwind v4 --spacing grid), and annotates copied CSS with token hints. It’s library‑agnostic, detects wide‑gamut token colors like lab()/oklch(), and caches token discovery for faster sessions.

  • New Features

    • Added collect-design-tokens.ts to read --* from document.styleSheets, resolve via getComputedStyle, and classify px/rem length and color tokens (incl. lab(), lch(), oklab(), oklch(), color()), skipping cross‑origin sheets and recursing grouping rules.
    • Matching: px tokens only when the token name matches the CSS property family (spacing/size/radius/font-size/line-height/letter-spacing/border-width); colors match by value with shortest‑name tie‑breaks.
    • Copied CSS appends /* var(--token) */ per matching declaration and adds one “Prefer the design token” line only when hints exist; raw values are preserved.
    • Arrow keys on px values snap through the property’s token scale; when no discrete scale exists, spacing/size walk the Tailwind --spacing base‑unit grid. Shift keeps a coarse raw step (×10) and Alt/Option adds a fine ±1px raw step — both bypass snapping. Off‑scale values fall back to raw steps; above‑max values snap down on ArrowLeft, below‑min snap up on ArrowRight. Also snaps to a lone token from nearby values.
  • Refactors

    • Generalized shift tracking to a modifier tracker (adds Alt), decomposed token‑stepping helpers, and sourced token hints directly from the resolver. Tightened length token classification to a single family, reused parseNumericValue, and clarified names. Memoized the custom‑property name scan keyed by stylesheet count for better performance.

Written for commit c47a0ef. Summary will update on new commits.

Review in cubic

@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-grab-storybook Ready Ready Preview, Comment Jun 27, 2026 12:11pm
react-grab-website Ready Ready Preview, Comment Jun 27, 2026 12:11pm

@pkg-pr-new

pkg-pr-new Bot commented Jun 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@react-grab/cli@487
npm i https://pkg.pr.new/grab@487
npm i https://pkg.pr.new/react-grab@487

commit: c47a0ef

Comment thread packages/react-grab/src/utils/collect-design-tokens.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/react-grab/src/utils/collect-design-tokens.ts">

<violation number="1" location="packages/react-grab/src/utils/collect-design-tokens.ts:57">
P2: Substring `includes` check causes compound token names to accumulate unintended families. For example, `--line-height` matches both `"line-height"` → line-height family and `"height"` → size family; similarly `--font-size` also picks up the `"size"` → size family. This means `matchLength` can incorrectly suggest a line-height or font-size token for a `width`/`height` property.

Consider either ordering keywords from most-specific to least-specific and short-circuiting on first match, or using word-boundary-aware matching (e.g., splitting on `-` segments) to prevent a compound keyword from also triggering its substring.</violation>

<violation number="2" location="packages/react-grab/src/utils/collect-design-tokens.ts:141">
P2: Token collection rescans all stylesheets per element, creating avoidable O(elements × rules) work during copy/prompt generation. Cache collected custom property names per document snapshot and reuse across elements.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/react-grab/src/utils/collect-design-tokens.ts
Comment thread packages/react-grab/src/utils/collect-design-tokens.ts Outdated
Comment thread packages/react-grab/src/utils/collect-design-tokens.ts Outdated
Comment thread packages/react-grab/src/utils/collect-design-tokens.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/react-grab/src/utils/collect-design-tokens.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/react-grab/src/utils/collect-design-tokens.ts Outdated
Comment thread packages/react-grab/src/core/edit-mode.ts
cursoragent and others added 6 commits June 27, 2026 11:33
…bles

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
… scale

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
… grid

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…ken marker scan

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
… token snapping

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
…NumericValue, clearer names

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
… snapping

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c16fb73. Configure here.

Comment thread packages/react-grab/src/utils/collect-design-tokens.ts Outdated
…naps down)

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants