diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dcb564a2163..1fba8eaf94a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Skip comments in Ruby files when checking for class names ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243)) - Skip over arbitrary property utilities with a top-level `!` in the value ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243)) - Support environment API in `@tailwindcss/vite` ([#18970](https://github.com/tailwindlabs/tailwindcss/pull/18970)) +- Preserve case of theme keys from JS configs and plugins ([#19337](https://github.com/tailwindlabs/tailwindcss/pull/19337)) - Upgrade: Handle `future` and `experimental` config keys ([#19344](https://github.com/tailwindlabs/tailwindcss/pull/19344)) ### Added diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index 22da265c5e68..eaa04df96bca 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -167,9 +167,9 @@ test( " --- src/index.html ---
- + --- src/input.css --- @import 'tailwindcss'; @@ -187,12 +187,12 @@ test( --color-red-500: #ef4444; --color-red-600: #dc2626; - --color-super-red: #ff0000; + --color-superRed: #ff0000; --color-steel: rgb(70 130 180); --color-smoke: rgba(245, 245, 245, var(--smoke-alpha, 1)); --opacity-*: initial; - --opacity-super-opaque: 95%; + --opacity-superOpaque: 95%; --text-*: initial; --text-xs: 0.75rem; @@ -265,9 +265,9 @@ test( --animate-spin-clockwise: spin-clockwise 1s linear infinite; --animate-spin-counterclockwise: spin-counterclockwise 1s linear infinite; - --tracking-super-wide: 0.25em; + --tracking-superWide: 0.25em; - --leading-super-loose: 3; + --leading-superLoose: 3; @keyframes spin-clockwise { 0% { diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts index 83fc2f2c94e9..f36e5ab15a7c 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts @@ -118,8 +118,8 @@ test('config values can be merged into the theme', () => { ]) expect(theme.resolve('2xl', ['--text'])).toEqual('2rem') expect(theme.resolveWith('2xl', ['--text'], ['--line-height'])).toEqual(['2rem', {}]) - expect(theme.resolve('super-wide', ['--tracking'])).toEqual('0.25em') - expect(theme.resolve('super-loose', ['--leading'])).toEqual('3') + expect(theme.resolve('superWide', ['--tracking'])).toEqual('0.25em') + expect(theme.resolve('superLoose', ['--leading'])).toEqual('3') expect(theme.resolve('1/2', ['--width'])).toEqual('60%') expect(theme.resolve('0.5', ['--width'])).toEqual('60%') expect(theme.resolve('100%', ['--width'])).toEqual('100%') diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.ts index a430f9faa332..ce3958eb3a24 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.ts @@ -185,11 +185,27 @@ export function keyPathToCssProperty(path: string[]) { .map((path, idx, all) => (path === '1' && idx !== all.length - 1 ? '' : path)) // Resolve the key path to a CSS variable segment - .map((part) => - part - .replaceAll('.', '_') - .replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`), - ) + .map((part, idx) => { + part = part.replaceAll('.', '_') + + let shouldConvert = + // The first "namespace" part should be converted to kebab-case + // This converts things like backgroundColor to `background-color` + idx === 0 || + // Any tuple nested key should be converted to kebab-case + // These are identified with a leading `-` + // e.g. `fontSize.xs.1.lineHeight` -> `font-size-xs--line-height` + part.startsWith('-') || + // `lineHeight` is a bit of a special case in which it does not + // always begin with a leading `-` even when as a nested tuple key + part === 'lineHeight' + + if (shouldConvert) { + part = part.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`) + } + + return part + }) // Remove the `DEFAULT` key at the end of a path // We're reading from CSS anyway so it'll be a string diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index d86d5304b3e7..25bd893d18d4 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -1719,3 +1719,55 @@ test('The theme() function does not try indexing into strings', async () => { " `) }) + +test('camel case keys are preserved', async () => { + let compiler = await compile( + css` + @tailwind utilities; + @theme { + --color-blue-green: slate; + } + @config "./plugin.js"; + `, + { + loadModule: async () => { + return { + base: '/', + path: '', + module: { + theme: { + extend: { + backgroundColor: { + lightGreen: '#c0ffee', + }, + }, + }, + }, + } + }, + }, + ) + + expect( + compiler.build([ + // From CSS + 'bg-blue-green', // should be output + 'bg-blueGreen', // should not + + // From JS config + 'bg-light-green', // should not be output + 'bg-lightGreen', // should be + ]), + ).toMatchInlineSnapshot(` + ".bg-blue-green { + background-color: var(--color-blue-green); + } + .bg-lightGreen { + background-color: #c0ffee; + } + :root, :host { + --color-blue-green: slate; + } + " + `) +})