Skip to content

Improve upgrade migrations #18184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Nothing yet!
### Fixed

- Upgrade: migrate arbitrary modifiers with values without percentage sign to bare values `/[0.16]` -> `/16` ([#18184](https://github.com/tailwindlabs/tailwindcss/pull/18184))
- Upgrade: migrate CSS variable shorthand if fallback value contains function call ([#18184](https://github.com/tailwindlabs/tailwindcss/pull/18184))

## [4.1.8] - 2025-05-27

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ test.each([
['w-[--spacing(5)]', 'w-[--spacing(5)]'],
['bg-[--theme(--color-red-500)]', 'bg-[--theme(--color-red-500)]'],

// Fallback values should be included inside the `var(…)` function
['bg-[--my-color,red]', 'bg-(--my-color,red)'],
// Fallback values can contain CSS functions
['bg-[--my-color,theme(spacing.1)]', 'bg-(--my-color,theme(spacing.1))'],

// Some properties never had var() injection in v3.
['[scroll-timeline-name:--myTimeline]', '[scroll-timeline-name:--myTimeline]'],
['[timeline-scope:--myScope]', '[timeline-scope:--myScope]'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { walk, WalkAction } from '../../../../tailwindcss/src/ast'
import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import * as ValueParser from '../../../../tailwindcss/src/value-parser'

export function migrateAutomaticVarInjection(
designSystem: DesignSystem,
Expand Down Expand Up @@ -73,9 +74,23 @@ export function migrateAutomaticVarInjection(

function injectVar(value: string): { value: string; didChange: boolean } {
let didChange = false
if (value.startsWith('--') && !value.includes('(')) {
value = `var(${value})`
didChange = true
if (value.startsWith('--')) {
// E.g.:
//
// - `--my-color` → `var(--my-color)` Convert variable
// - `--my-color,red` → `var(--my-color,red)` Convert variable with fallback
// - `--theme(color.red)` → `--theme(color.red)` Do not convert functions
//
if (
// No `(` definitely means there is no function
!value.includes('(') ||
// There could be a function call in the fallback value, but it cannot be
// top-level, so we can safely check the first part
ValueParser.parse(value)[0]?.kind !== 'function'
) {
value = `var(${value})`
didChange = true
}
} else if (value.startsWith(' --')) {
value = value.slice(1)
didChange = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
// Use a bare value modifier
['bg-red-500/[25%]', 'bg-red-500/25'],

// Convert 0-1 values to bare values
['bg-[#f00]/[0.16]', 'bg-[#f00]/16'],

// Drop unnecessary modifiers
['bg-red-500/[100%]', 'bg-red-500'],
['bg-red-500/100', 'bg-red-500'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ export function migrateOptimizeModifier(
}
}

// 3. Try to remove the square brackets, but multiply by 100. E.g.: `[0.16]` -> `16`
if (!changed) {
let newModifier: NamedUtilityValue = {
kind: 'named',
value: `${parseFloat(modifier.value) * 100}`,
fraction: null,
}

if (
targetSignature ===
signatures.get(designSystem.printCandidate({ ...candidate, modifier: newModifier }))
) {
changed = true
candidate.modifier = newModifier
}
}

return changed ? designSystem.printCandidate(candidate) : rawCandidate
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals
let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const
if (!designSystem.theme.get([variable])) return null

if (designSystem.theme.prefix) {
return `--${designSystem.theme.prefix}-${variable.slice(2)}`
}

Comment on lines +196 to +199
Copy link
Member Author

Choose a reason for hiding this comment

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

When adding the test to the migrate.test.ts file that runs all the migrations (with variants, prefix, ...) it caught an issue where we didn't handle the --tw- prefix properly when reading from theme values, which is actually necessary when you have a prefix.

See https://play.tailwindcss.com/BlXfDMDtZz

return variable
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
// non-negative version first so we can replace `-mt-[0px]` with `mt-[0px]`.
['mt-[0px]', 'mt-[0px]'],
['-mt-[0px]', 'mt-[0px]'],

// Shorthand CSS Variables should be converted to the new syntax, even if
// the fallback contains functions. The fallback should also be migrated to
// the newest syntax.
['bg-[--my-color,theme(colors.red.500)]', 'bg-(--my-color,var(--color-red-500))'],
])(testName, async (candidate, result) => {
if (strategy === 'with-variant') {
candidate = `focus:${candidate}`
Expand Down