Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Upgrade: Do not migrate declarations that look like candidates in `<style>` blocks ([#18057](https://github.com/tailwindlabs/tailwindcss/pull/18057), [18068](https://github.com/tailwindlabs/tailwindcss/pull/18068))
- Upgrade: Improve `pnpm` workspaces support ([#18065](https://github.com/tailwindlabs/tailwindcss/pull/18065))
- Upgrade: Migrate deprecated `order-none` to `order-0` ([#18126](https://github.com/tailwindlabs/tailwindcss/pull/18126))
- Support Leptos `class:` attributes when extracting classes ([#18093](https://github.com/tailwindlabs/tailwindcss/pull/18093))

## [4.1.7] - 2025-05-15
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ export function parseCandidate(designSystem: DesignSystem, input: string) {
: input,
)
}

export function printUnprefixedCandidate(designSystem: DesignSystem, candidate: Candidate) {
let candidateString = designSystem.printCandidate(candidate)

return designSystem.theme.prefix && candidateString.startsWith(`${designSystem.theme.prefix}:`)
? candidateString.slice(designSystem.theme.prefix.length + 1)
: candidateString
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import type { Writable } from '../../utils/types'
import { baseCandidate, parseCandidate, printUnprefixedCandidate } from './candidates'
import { computeUtilitySignature } from './signatures'

const DEPRECATION_MAP = new Map([['order-none', 'order-0']])

export async function migrateDeprecatedUtilities(
designSystem: DesignSystem,
_userConfig: Config | null,
rawCandidate: string,
): Promise<string> {
let signatures = computeUtilitySignature.get(designSystem)

for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) {
// The below logic makes use of mutation. Since candidates in the
// DesignSystem are cached, we can't mutate them directly.
let candidate = structuredClone(readonlyCandidate) as Writable<typeof readonlyCandidate>

// Create a basic stripped candidate without variants or important flag. We
// will re-add those later but they are irrelevant for what we are trying to
// do here (and will increase cache hits because we only have to deal with
// the base utility, nothing more).
let targetCandidate = baseCandidate(candidate)
let targetCandidateString = printUnprefixedCandidate(designSystem, targetCandidate)

let replacementString = DEPRECATION_MAP.get(targetCandidateString) ?? null
if (replacementString === null) return rawCandidate

let legacySignature = signatures.get(targetCandidateString)
if (typeof legacySignature !== 'string') return rawCandidate

let replacementSignature = signatures.get(replacementString)
if (typeof replacementSignature !== 'string') return rawCandidate

// Not the same signature, not safe to migrate
if (legacySignature !== replacementSignature) return rawCandidate

let [replacement] = parseCandidate(designSystem, replacementString)

// Re-add the variants and important flag from the original candidate
return designSystem.printCandidate(
Object.assign(structuredClone(replacement), {
variants: candidate.variants,
important: candidate.important,
}),
)
}

return rawCandidate
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',

// Promote inferred data type to more specific utility if it exists
['bg-[123px]', 'bg-position-[123px]'],

// Do not migrate bare values or arbitrary values to named values that are
// deprecated
['order-[0]', 'order-0'],
['order-0', 'order-0'],

// Migrate deprecated named values to bare values
['order-none', 'order-0'],
])(testName, async (candidate, result) => {
if (strategy === 'with-variant') {
candidate = `focus:${candidate}`
Expand All @@ -63,4 +71,38 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
let migrated = await migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})

test.each([
['order-[0]', 'order-0'],
['order-0', 'order-0'],

// Do not migrate away `order-none` if it's customly defined and thus not
// safe to migrate to `order-0`
['order-none', 'order-none'],
])(`${testName} with custom implementations`, async (candidate, result) => {
if (strategy === 'with-variant') {
candidate = `focus:${candidate}`
result = `focus:${result}`
} else if (strategy === 'important') {
candidate = `${candidate}!`
result = `${result}!`
} else if (strategy === 'prefix') {
// Not only do we need to prefix the candidate, we also have to make
// sure that we prefix all CSS variables.
candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}`
result = `tw:${result.replaceAll('var(--', 'var(--tw-')}`
}

let localInput = css`
${input}

@utility order-none {
order: none; /* imagine this exists */
}
`

let designSystem = await designSystems.get(__dirname).get(localInput)
let migrated = await migrate(designSystem, {}, candidate)
expect(migrated).toEqual(result)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { migrateBareValueUtilities } from './migrate-bare-utilities'
import { migrateBgGradient } from './migrate-bg-gradient'
import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value'
import { migrateCanonicalizeCandidate } from './migrate-canonicalize-candidate'
import { migrateDeprecatedUtilities } from './migrate-deprecated-utilities'
import { migrateDropUnnecessaryDataTypes } from './migrate-drop-unnecessary-data-types'
import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values'
import { migrateLegacyArbitraryValues } from './migrate-legacy-arbitrary-values'
Expand Down Expand Up @@ -48,6 +49,7 @@ export const DEFAULT_MIGRATIONS: Migration[] = [
migrateLegacyArbitraryValues,
migrateArbitraryUtilities,
migrateBareValueUtilities,
migrateDeprecatedUtilities,
migrateModernizeArbitraryValues,
migrateArbitraryVariants,
migrateDropUnnecessaryDataTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8079,7 +8079,6 @@ exports[`getClassList 1`] = `
"order-12",
"order-first",
"order-last",
"order-none",
"ordinal",
"origin-bottom",
"origin-bottom-left",
Expand Down
2 changes: 2 additions & 0 deletions packages/tailwindcss/src/compat/legacy-utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@ export function registerLegacyUtilities(designSystem: DesignSystem) {
return [decl('flex-grow', candidate.value.value)]
}
})

designSystem.utilities.static('order-none', () => [decl('order', '0')])
}
1 change: 0 additions & 1 deletion packages/tailwindcss/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,6 @@ export function createUtilities(theme: Theme) {
*/
staticUtility('order-first', [['order', '-9999']])
staticUtility('order-last', [['order', '9999']])
staticUtility('order-none', [['order', '0']])
functionalUtility('order', {
supportsNegative: true,
handleBareValue: ({ value }) => {
Expand Down