diff --git a/CHANGELOG.md b/CHANGELOG.md index 245c0b707461..3801ae9d3972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Discard matched variants with non-string values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799)) - Show suggestions for known `matchVariant` values ([#18798](https://github.com/tailwindlabs/tailwindcss/pull/18798)) - Migrate `aria` theme keys to `@custom-variant` ([#18815](https://github.com/tailwindlabs/tailwindcss/pull/18815)) +- Migrate `data` theme keys to `@custom-variant` ([#18816](https://github.com/tailwindlabs/tailwindcss/pull/18816)) ## [4.1.12] - 2025-08-13 diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index a058f04b9eb5..808b62013f6b 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -1048,6 +1048,80 @@ test( }, ) +test( + 'migrate data theme keys to custom variants', + { + fs: { + 'package.json': json` + { + "dependencies": { + "tailwindcss": "^3", + "@tailwindcss/upgrade": "workspace:^" + } + } + `, + 'tailwind.config.ts': ts` + export default { + content: { + relative: true, + files: ['./src/**/*.html'], + }, + theme: { + extend: { + data: { + // Automatically handled by bare values + foo: 'foo', + + // Not automatically handled by bare values because names differ + bar: 'baz', + + // Custom + checked: 'ui~="checked"', + }, + }, + }, + } + `, + 'src/input.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + }, + async ({ exec, fs, expect }) => { + await exec('npx @tailwindcss/upgrade') + + expect(await fs.dumpFiles('src/*.css')).toMatchInlineSnapshot(` + " + --- src/input.css --- + @import 'tailwindcss'; + + @custom-variant data-bar (&[data-baz]); + @custom-variant data-checked (&[data-ui~="checked"]); + + /* + The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. + */ + @layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } + } + " + `) + }, +) + describe('border compatibility', () => { test( 'migrate border compatibility', diff --git a/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts index 3d45362e18c4..c7fb046f4798 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts @@ -152,6 +152,18 @@ async function migrateTheme( } delete resolvedConfig.theme.aria } + + if ('data' in resolvedConfig.theme) { + for (let [key, value] of Object.entries(resolvedConfig.theme.data ?? {})) { + // Will be handled by bare values if the names match. + // E.g.: `data-foo:flex` should produce `[data-foo]` + if (key === value) continue + + // Create custom variant + variants.set(`data-${key}`, `&[data-${value}]`) + } + delete resolvedConfig.theme.data + } } // Convert theme values to CSS custom properties @@ -223,7 +235,13 @@ async function migrateTheme( if (variants.size > 0) { css += '\n@tw-bucket custom-variant {\n' + + let previousRoot = '' for (let [name, selector] of variants) { + let root = name.split('-')[0] + if (previousRoot !== root) css += '\n' + previousRoot = root + css += `@custom-variant ${name} (${selector});\n` } css += '}\n' @@ -389,7 +407,7 @@ const ALLOWED_THEME_KEYS = [ // Used by @tailwindcss/container-queries 'containers', ] -const BLOCKED_THEME_KEYS = ['supports', 'data'] +const BLOCKED_THEME_KEYS = ['supports'] function onlyAllowedThemeValues(theme: ThemeConfig): boolean { for (let key of Object.keys(theme)) { if (!ALLOWED_THEME_KEYS.includes(key)) {