Skip to content

Upgrade codemod rewrites variant="outline" to "outline-solid" inside conditional expressions #20435

Description

@carlos-cubas

What version of Tailwind CSS are you using?

@tailwindcss/upgrade v4.3.3, migrating a project from v3.4.17 to v4.3.3.

What build tool (or framework if it abstracts the build tool) are you using?

postcss 8.5.26 (the reproduction is a bare PostCSS project; no framework). The bug is in
the upgrade codemod's template migration, so the build tool is incidental.

What version of Node.js are you using?

v25.1.0 (npm 11.6.2)

What browser are you using?

N/A

What operating system are you using?

macOS 26.6.2 (Darwin 25.6.0, arm64)

Reproduction URL

https://github.com/carlos-cubas/tailwind-upgrade-outline-repro

npm install
npx @tailwindcss/upgrade@4.3.3 --force
git diff

Seven files, fourteen lines of TSX. It contains four control cases (the forms #18922
guards, which correctly survive) and four conditional cases plus one comment (which do
not). Verified from a clean clone.

Describe your issue

@tailwindcss/upgrade rewrites variant="outline" to variant="outline-solid" when a
conditional expression sits between variant and the string literal. These are React prop
values, not class names, so the rewrite silently breaks them.

PR #18922 fixed this for the direct forms, and those still work. The guard it added is a
look-behind regex in is-safe-migration.ts:

/variant\s*[:=]\s*\{?['"`]$/

Because it only inspects the text immediately preceding the candidate, any conditional
between variant and the literal defeats it.

From the reproduction — the four control cases are untouched, the four conditional cases
are all rewritten:

Input After upgrade
<Button variant="outline" /> unchanged
<Button variant={"outline"} /> unchanged
Button({ variant: "outline" }) unchanged
function c4({ variant = "outline" }) unchanged
Button({ variant: isActive ? "outline" : "ghost" }) "outline-solid"
<Button variant={first ? "default" : "outline"} /> "outline-solid"
<Button variant={variant ?? "outline"} /> "outline-solid"
<Button variant={required ? 'secondary' : 'outline'} /> "outline-solid"

Comments are also not excluded from candidate extraction. This line is rewritten too, and
outline-solid: none is not valid CSS:

-// this comment mentions `outline: none` as CSS prose
+// this comment mentions `outline-solid: none` as CSS prose

How loud it is depends on the call site. In our project the codemod hit four call
sites in shadcn/ui-derived components. Three flowed into a cva-typed variant prop and
tsc did reject them:

error TS2322: Type '"default" | "outline-solid"' is not assignable to type
'"link" | "default" | "destructive" | "outline" | "secondary" | "ghost"'.

So for typed variants this is a noisy break rather than a silent one, which is worth
saying plainly. The fourth was a plain data attribute:

<div data-variant={variant ?? "outline"} />   // becomes "outline-solid"

Nothing type-checks that, and the sibling group-data-[variant=ghost]/… selectors that
read it keep working, so the wrong value just sits there. Comment prose is silent for the
same reason. A project on JavaScript, or with a looser variant type, gets no signal on
any of the four.

Expected behaviour. A string literal that is the value of a variant prop should not
be migrated regardless of what expression it sits inside, and candidates inside comments
should not be migrated at all.

I appreciate the immediate look-behind cannot see through an arbitrary expression, so this
may want a different check rather than a longer regex — happy to open a PR against
is-safe-migration.ts with cases added to the existing table in
is-safe-migration.test.ts if you would like it in that form.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions