Skip to content

docs: add migrate-styled-components-to-vanilla-extract skill - #1774

Merged
stipsan merged 14 commits into
mainfrom
cursor/migrate-styled-components-skill-91e1
Aug 6, 2026
Merged

docs: add migrate-styled-components-to-vanilla-extract skill#1774
stipsan merged 14 commits into
mainfrom
cursor/migrate-styled-components-skill-91e1

Conversation

@stipsan

@stipsan stipsan commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

Adds a new agent skill, .agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md — the step-by-step procedure for migrating one plugin's styling off styled-components to vanilla-extract, distilled from:

  • #1417@sanity/google-maps-input (the reference implementation)
  • #1450sanity-plugin-workflow (clean mechanical migration on the current tsdown setup)
  • #1670@sanity/color-input (the dynamic-styling example: styleVariants, createVar + assignInlineVars, useTheme_v2(), migrating computed inline style objects)
  • #1654 — the Vitest disableRuntimeStyles guidance

Stack

Base branch: cursor/ve-disable-runtime-styles-docs-9315 (#1654), not main — so the skill links the in-repo Disabling runtime styles in tests section of the styling reference directly instead of pointing at a PR. Merge after #1654 lands (or keep stacked).

The skill covers the workflow (inventory table, three migration shapes, tsdown.config.ts, dependency changes, Vitest wiring, snapshot update, verification, changeset, checklist) and defers to the sanity-plugin-best-practices styling reference for the underlying patterns, rather than duplicating them.

Notable lessons encoded from the source PRs:

Cross-links

  • sanity-plugin-best-practices/SKILL.md + references/styling.md now point at the new skill for the procedure; the styling reference's post-migration dependency guidance ("Remove both once the migration is complete") is updated to match merged practice (remove the peer, verify lockfile peer-variant alignment before dropping the devDep).
  • plugin-transfer/SKILL.md's "do not migrate styling during a transfer" notes now point follow-up work at the new skill.

Testing

Docs-only change (no published packages touched, so no changeset). pnpm format, pnpm lint, and pnpm knip all pass.

Open in Web Open in Cursor 

Clarify that the Vite plugin remains required for .css.ts transforms,
while disableRuntimeStyles is an optional jsdom/happy-dom opt-in when
tests do not need real CSS injection.
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
plugins-studio Ready Ready Preview Aug 6, 2026 7:19am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
plugins-e2e-test-studio Ignored Ignored Aug 6, 2026 7:19am

Request Review

@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: b17f455

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

✅ E2E Tests

🟢 25 passed🟡 1 flakyview full reportview run

Studio: https://plugins-e2e-test-studio-64sbv49xg.sanity.dev

Datasets: pr-1774-chromium-31080474363, pr-1774-firefox-31080474363

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 32% 5508 / 17209
🔵 Statements 31.78% 5812 / 18288
🔵 Functions 27.78% 1427 / 5136
🔵 Branches 23.33% 2828 / 12118
File CoverageNo changed files found.
Generated in workflow #8748 for commit b17f455 by the Vitest Coverage Report Action

…time-styles-docs-9315

# Conflicts:
#	AGENTS.md
Step-by-step migration procedure distilled from PR #1417
(@sanity/google-maps-input) and PR #1450 (sanity-plugin-workflow),
covering inventory, code patterns, tsdown config, dependency changes
(including the styled-components devDep peer-alignment pitfall),
Vitest wiring, snapshot updates, verification, and changesets.
Point sanity-plugin-best-practices and plugin-transfer at the new
step-by-step skill, and update the styling reference's post-migration
dependency guidance to match merged practice: keep the
styled-components catalog devDependency while the plugin depends on
@sanity/ui.
Adds Shape C to the migration skill: per-instance/theme-driven values
bridged through createVar + assignInlineVars, including migrating
computed inline style objects, with the Checkboard example.
Link the in-repo 'Disabling runtime styles in tests' section instead of
the external doc, add @sanity/color-input as an in-repo reference
implementation, and soften the styled-components devDep guidance to
verify-lockfile-alignment now that the @sanity/styled-components
override is gone and color-input merged without the devDep.
@cursor
cursor Bot force-pushed the cursor/migrate-styled-components-skill-91e1 branch from 68a1789 to 394e45a Compare July 28, 2026 15:47
@cursor
cursor Bot changed the base branch from main to cursor/ve-disable-runtime-styles-docs-9315 July 28, 2026 15:47
@stipsan
stipsan marked this pull request as ready for review August 3, 2026 11:20
Copilot AI review requested due to automatic review settings August 3, 2026 11:20
@stipsan
stipsan requested a review from a team as a code owner August 3, 2026 11:20
@stipsan
stipsan requested review from pedrobonamin and removed request for a team August 3, 2026 11:20

Copilot AI 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.

Pull request overview

Adds a new internal agent skill documenting the repeatable procedure for migrating a single plugin from styled-components to vanilla-extract, and updates existing skill docs to cross-link to this new procedure (while keeping the underlying styling patterns in the existing styling reference).

Changes:

  • Introduces .agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md with an end-to-end migration workflow (inventory → code shapes → tsdown config → deps → Vitest wiring → verification → changeset).
  • Updates sanity-plugin-best-practices docs to point to the new skill for the procedure and keeps references/styling.md focused on patterns.
  • Updates plugin-transfer guidance to route follow-up styling migrations to the new skill (and reiterates “don’t migrate styling during transfers”).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
.agents/skills/sanity-plugin-best-practices/SKILL.md Adds a cross-link to the new migration procedure skill.
.agents/skills/sanity-plugin-best-practices/references/styling.md Clarifies “migrating off styled-components” section and links to the new procedure skill.
.agents/skills/plugin-transfer/SKILL.md Points follow-up styling migrations (post-transfer) to the new skill.
.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md New skill: step-by-step migration procedure docs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md Outdated
PR links rot over time and can point at patterns we've since moved away
from. Cite the in-repo reference plugins (@sanity/google-maps-input,
sanity-plugin-workflow, @sanity/color-input) directly instead.

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:133

  • The example JSX snippet has a leading semicolon (;<div ...>), which makes the sample confusing and is not a pattern we want to encourage in docs. It reads like a typo rather than intentional ASI protection here.
;<div

Keep the skill reusable across repos: ground rules like one-plugin-per-PR,
never-during-transfer, and the reference-implementation plugin list live in
AGENTS.md. The skill keeps the portable procedure and points at AGENTS.md
for monorepo-specific policy.
Use the repo-agnostic 'workspace' term for the unit being migrated so the
skill reads naturally outside this plugins monorepo. Literal package.json,
the package-exports test, the Vite plugin, and the AGENTS.md policy pointer
keep their names.
Base automatically changed from cursor/ve-disable-runtime-styles-docs-9315 to main August 3, 2026 11:31

Copilot AI 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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:213

  • The note about a “host app or studio” registering the Vite plugin globally is ambiguous in the Vitest section and could be read as permission to skip adding vanillaExtractPlugin() to the package’s vitest.config.ts (which would break .css.ts compilation in package tests). Consider explicitly calling out the test studio file and clarifying that the package’s Vitest config still needs the plugin.
Use `@sanity/vanilla-extract-vite-plugin` (faster drop-in for the upstream
`@vanilla-extract/vite-plugin`). If a host app or studio already registers the Vite plugin
globally, you don't need to touch that file for the workspace under migration.

Copilot AI review requested due to automatic review settings August 3, 2026 11:33
Spreading {...props} then a fixed className drops a caller-provided
className. Pull className out of props and merge with clsx(localClass,
className) in the Shape A example and the styling reference's
encapsulation wrappers, noting any classnames helper or template literal
works.
…-components-skill-91e1

# Conflicts:
#	.agents/skills/sanity-plugin-best-practices/references/styling.md
#	AGENTS.md

Copilot AI 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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:217

  • This paragraph suggests you can skip updating the package’s vitest.config.ts if a host app/studio registers the Vite plugin globally, but Vitest does not inherit the test-studio Vite config. The plugin is still required in the package’s own vitest.config.ts (e.g. for the package-exports test importing .css.ts).
  // ...existing test config
})
**.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:128**
* The TSX snippet has a stray leading semicolon (`;<div`), which makes the example look like a typo and isn’t needed here (it’s not preceded by an expression that would require ASI protection).

This issue also appears on line 215 of the same file.

function Checkboard({borderRadius, background}: {borderRadius?: string; background?: string}) {

</details>

Copilot AI 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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (6)

.agents/skills/sanity-plugin-best-practices/references/styling.md:458

  • This bullet references clsx(...), which isn’t a dependency used/declared by plugin packages in this repo. Updating it to show a dependency-free merge pattern keeps the guidance copy/paste-safe.
- **Merge, don't clobber, `className`.** A fixed `className={errorCode}` after `{...props}` silently
  drops a `className` a caller passes in. Pull `className` out of props and merge it —
  `clsx(errorCode, className)` (any classnames helper, or a `` `${errorCode} ${className ?? ''}` ``
  template literal) — so the wrapper's class always applies while still honoring the caller's.

.agents/skills/sanity-plugin-best-practices/references/styling.md:466

  • This snippet still uses clsx(...), but the repo doesn’t currently standardize on (or declare) clsx as a plugin dependency. Using a simple string join keeps the example dependency-free and consistent with the guidance above.
  export function Root({className, ...props}: ComponentProps<typeof Flex>) {
    return <Flex {...props} className={clsx(root, className)} />
  }

.agents/skills/sanity-plugin-best-practices/references/styling.md:482

  • Same as above: this example uses clsx(...), which plugin packages here don’t declare as a dependency. A dependency-free join avoids implying authors should add an extra package just to merge classNames.
      <Card
        {...props}
        className={clsx(resultContainer, isInvalid && resultContainerInvalid, className)}
      />

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:89

  • This paragraph recommends clsx(...)/classnames, but those aren’t currently standard/declared dependencies for plugin packages here. Consider describing a dependency-free merge pattern to avoid implying consumers should add extra deps.
`className={floatingCard}` after `{...props}` silently drops a `className` a caller passes in. Pull
`className` out of props and merge it with `clsx(floatingCard, className)` (any classnames helper —
`clsx`, `classnames` — or a `` `${floatingCard} ${className ?? ''}` `` template literal works) so the
wrapper's class is always applied while still honoring the caller's. See

.agents/skills/sanity-plugin-best-practices/references/styling.md:444

  • These docs now use clsx, but none of the plugin packages in this repo declare clsx as a dependency. Copying this snippet into a plugin will fail to compile unless the author also adds a new dependency. Consider using a dependency-free className merge in the example (or explicitly calling out the dependency requirement).

This issue also appears in the following locations of the same file:

  • line 455
  • line 464
  • line 479
import {Code} from '@sanity/ui'
import {clsx} from 'clsx'
import {type ComponentProps} from 'react'

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:81

  • This example imports and uses clsx, but plugin packages in this repo don’t declare clsx as a dependency. Using a dependency-free className merge keeps the snippet copy/paste-safe.

This issue also appears on line 86 of the same file.

import {Card} from '@sanity/ui'
import {clsx} from 'clsx'
import type {ComponentProps} from 'react'

Wrapper className merges are string joins (plus optional falsy
conditionals), so import clsx from 'clsx/lite' instead of the full
clsx entry. Reserve full clsx for the object/array API.
@stipsan
stipsan merged commit 287bc44 into main Aug 6, 2026
19 checks passed
@stipsan
stipsan deleted the cursor/migrate-styled-components-skill-91e1 branch August 6, 2026 07:21

Copilot AI 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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (6)

.agents/skills/sanity-plugin-best-practices/references/styling.md:466

  • This snippet uses clsx(...), but the file no longer needs to introduce a clsx dependency for the docs example. Use a simple string merge so the wrapper preserves any caller-provided className.
  ```tsx
  import {clsx} from 'clsx/lite'

.agents/skills/sanity-plugin-best-practices/references/styling.md:482

  • This example still uses clsx(...). Since the docs are already describing class merging, it’s clearer to show a dependency-free pattern (e.g. join/filter) that readers can paste without adding clsx.
    isInvalid,
    className,
    ...props
  }: ComponentProps<typeof Card> & {isInvalid: boolean}) {

.agents/skills/sanity-plugin-best-practices/references/styling.md:458

  • The guidance here uses a JS template literal inside Markdown inline code. Using a dependency-free join/filter example avoids nested-backtick rendering issues and keeps the docs copy/pasteable without adding clsx.
- **Merge, don't clobber, `className`.** A fixed `className={errorCode}` after `{...props}` silently
  drops a `className` a caller passes in. Pull `className` out of props and merge it —
  `clsx(errorCode, className)`. Prefer `import {clsx} from 'clsx/lite'` when only joining strings
  (the usual case); reach for full `clsx` only if you need the object/array API. A

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:89

  • The prose currently names clsx/classnames helpers. Since the docs can demonstrate className merging without implying a new dependency, consider pointing at the same join/filter pattern used in the snippet.
Type the wrapper with `ComponentProps<typeof Primitive>` (or `ComponentProps<'div'>`) and never use
`forwardRef` — `ref` is a regular prop on React 19. **Merge, don't clobber:** a fixed
`className={floatingCard}` after `{...props}` silently drops a `className` a caller passes in. Pull
`className` out of props and merge it with `clsx(floatingCard, className)` — prefer
`import {clsx} from 'clsx/lite'` when only joining strings (the usual case); reach for full `clsx`
only if you need the object/array API. A `` `${floatingCard} ${className ?? ''}` `` template literal

.agents/skills/sanity-plugin-best-practices/references/styling.md:449

  • The docs example uses import {clsx} from 'clsx', but clsx is typically a default export and this repo doesn’t otherwise use it. To keep the snippet copy/pasteable without adding a new dependency, merge className with a simple template expression instead.

This issue also appears in the following locations of the same file:

  • line 455
  • line 464
  • line 479
import {Code} from '@sanity/ui'
import {clsx} from 'clsx/lite'
import {type ComponentProps} from 'react'

import {errorCode} from './QueryErrorDialog.css'

export function ErrorCode({className, ...props}: ComponentProps<typeof Code>) {
  return <Code {...props} className={clsx(errorCode, className)} />

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:81

  • This example imports/uses clsx just to merge className. Since clsx isn’t otherwise used in the repo and is typically a default export, the snippet is easier to copy/paste if it uses a dependency-free join/filter merge instead.

This issue also appears on line 84 of the same file.

import {Card} from '@sanity/ui'
import {clsx} from 'clsx/lite'
import type {ComponentProps} from 'react'

import {floatingCard} from './FloatingCard.css'

function StyledFloatingCard({className, ...props}: ComponentProps<typeof Card>) {
  return <Card {...props} className={clsx(floatingCard, className)} />
}

Copilot AI review requested due to automatic review settings August 6, 2026 07:22

Copilot AI 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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (6)

.agents/skills/sanity-plugin-best-practices/references/styling.md:459

  • This section recommends clsx/lite, but clsx isn’t used or declared as a dependency in this repo. To keep the guidance immediately usable, it’s better to describe a dependency-free className merge (template literal) here.
- **Merge, don't clobber, `className`.** A fixed `className={errorCode}` after `{...props}` silently
  drops a `className` a caller passes in. Pull `className` out of props and merge it —
  `clsx(errorCode, className)`. Prefer `import {clsx} from 'clsx/lite'` when only joining strings
  (the usual case); reach for full `clsx` only if you need the object/array API. A
  `` `${errorCode} ${className ?? ''}` `` template literal also works.

.agents/skills/sanity-plugin-best-practices/references/styling.md:469

  • This example also imports clsx/lite, which isn’t declared in the repo. Consider using the same dependency-free template-literal merge pattern as elsewhere in the guide.
  import {clsx} from 'clsx/lite'

  export function Root({className, ...props}: ComponentProps<typeof Flex>) {
    return <Flex {...props} className={clsx(root, className)} />
  }

.agents/skills/sanity-plugin-best-practices/references/styling.md:487

  • Same concern here: clsx/lite isn’t a declared dependency in this repo, so this example isn’t directly copy/pasteable. Consider rewriting the conditional class merge without clsx.
  import {clsx} from 'clsx/lite'

  function ResultContainer({
    isInvalid,
    className,
    ...props
  }: ComponentProps<typeof Card> & {isInvalid: boolean}) {
    return (
      <Card
        {...props}
        className={clsx(resultContainer, isInvalid && resultContainerInvalid, className)}
      />

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:90

  • This paragraph recommends clsx/lite, but the repo doesn’t currently include clsx. To avoid encouraging migrations to add extra deps, consider removing the clsx guidance and keeping the template-literal merge option as the primary recommendation.
Type the wrapper with `ComponentProps<typeof Primitive>` (or `ComponentProps<'div'>`) and never use
`forwardRef` — `ref` is a regular prop on React 19. **Merge, don't clobber:** a fixed
`className={floatingCard}` after `{...props}` silently drops a `className` a caller passes in. Pull
`className` out of props and merge it with `clsx(floatingCard, className)` — prefer
`import {clsx} from 'clsx/lite'` when only joining strings (the usual case); reach for full `clsx`
only if you need the object/array API. A `` `${floatingCard} ${className ?? ''}` `` template literal
also works. See

.agents/skills/sanity-plugin-best-practices/references/styling.md:446

  • The example uses clsx/lite, but clsx is not currently a dependency anywhere in this repo (no package.json includes it). As written, this snippet won’t be copy/pasteable without adding a new dependency. Consider using a template literal (as already mentioned later in the doc) to merge className without requiring clsx.

This issue also appears in the following locations of the same file:

  • line 455
  • line 465
  • line 476
import {Code} from '@sanity/ui'
import {clsx} from 'clsx/lite'
import {type ComponentProps} from 'react'

import {errorCode} from './QueryErrorDialog.css'

.agents/skills/migrate-styled-components-to-vanilla-extract/SKILL.md:81

  • The Shape A example uses clsx/lite, but clsx is not currently declared in any package.json in this repo. Since this skill is meant to be copy/pasteable, consider using a dependency-free className merge (template literal) or explicitly note that clsx must be added as a dependency.

This issue also appears on line 84 of the same file.

import {Card} from '@sanity/ui'
import {clsx} from 'clsx/lite'
import type {ComponentProps} from 'react'

import {floatingCard} from './FloatingCard.css'

function StyledFloatingCard({className, ...props}: ComponentProps<typeof Card>) {
  return <Card {...props} className={clsx(floatingCard, className)} />
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants