Skip to content

Commit 642b9b8

Browse files
Don’t unconditionally convert config keys to kebab-case (#19337)
Fixes #18114 Closes #18115 This PR changes JS config handling such that we always preserve casing for theme keys (when possible). Now there are *two* exceptions to this rule: 1. Top-level keys in the theme *do* get converted to kebab-case. As CSS variables are not generated from these this shouldn't be a big issue. All of our internal plugins look for kebab-case keys. So, for example, take the path `backgroundColor.red.500`. This must translate to `--background-color-red-500`. But if you had something like `backgroundColor.lightBlue` it would be perfectly fine for that to translate to `--background-color-lightBlue` internally (and thus the utility be written as `bg-lightBlue`. 2. Tuple object keys are converted to kebab-case as well. These keys are converted to "nested" key syntax internally and typtically represent CSS property names. For example: ```js export default { theme: { fontSize: { xs: ["1.5rem", { lineHeight: "1.3" }] }, fontFamily: { sans: ["Potato Mono", { fontVariationSettings: '"XHGT" 0.7' }] } } } ``` The `lineHeight` key here must be converted to `line-height` because it represents a CSS property name. The theme key that represents this value is `--text-xs--line-height`. The same situation applies for the `fontVariationSettings` where the theme key is `--font-sans--font-variation-settings`.
1 parent 5a8e878 commit 642b9b8

File tree

5 files changed

+82
-13
lines changed

5 files changed

+82
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Skip comments in Ruby files when checking for class names ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243))
1515
- Skip over arbitrary property utilities with a top-level `!` in the value ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243))
1616
- Support environment API in `@tailwindcss/vite` ([#18970](https://github.com/tailwindlabs/tailwindcss/pull/18970))
17+
- Preserve case of theme keys from JS configs and plugins ([#19337](https://github.com/tailwindlabs/tailwindcss/pull/19337))
1718
- Upgrade: Handle `future` and `experimental` config keys ([#19344](https://github.com/tailwindlabs/tailwindcss/pull/19344))
1819

1920
### Added

integrations/upgrade/js-config.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ test(
167167
"
168168
--- src/index.html ---
169169
<div
170-
class="tracking-super-wide leading-super-loose"
170+
class="tracking-superWide leading-superLoose"
171171
></div>
172-
<div class="text-super-red/super-opaque leading-super-loose"></div>
172+
<div class="text-superRed/superOpaque leading-superLoose"></div>
173173
174174
--- src/input.css ---
175175
@import 'tailwindcss';
@@ -187,12 +187,12 @@ test(
187187
--color-red-500: #ef4444;
188188
--color-red-600: #dc2626;
189189
190-
--color-super-red: #ff0000;
190+
--color-superRed: #ff0000;
191191
--color-steel: rgb(70 130 180);
192192
--color-smoke: rgba(245, 245, 245, var(--smoke-alpha, 1));
193193
194194
--opacity-*: initial;
195-
--opacity-super-opaque: 95%;
195+
--opacity-superOpaque: 95%;
196196
197197
--text-*: initial;
198198
--text-xs: 0.75rem;
@@ -265,9 +265,9 @@ test(
265265
--animate-spin-clockwise: spin-clockwise 1s linear infinite;
266266
--animate-spin-counterclockwise: spin-counterclockwise 1s linear infinite;
267267
268-
--tracking-super-wide: 0.25em;
268+
--tracking-superWide: 0.25em;
269269
270-
--leading-super-loose: 3;
270+
--leading-superLoose: 3;
271271
272272
@keyframes spin-clockwise {
273273
0% {

packages/tailwindcss/src/compat/apply-config-to-theme.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ test('config values can be merged into the theme', () => {
118118
])
119119
expect(theme.resolve('2xl', ['--text'])).toEqual('2rem')
120120
expect(theme.resolveWith('2xl', ['--text'], ['--line-height'])).toEqual(['2rem', {}])
121-
expect(theme.resolve('super-wide', ['--tracking'])).toEqual('0.25em')
122-
expect(theme.resolve('super-loose', ['--leading'])).toEqual('3')
121+
expect(theme.resolve('superWide', ['--tracking'])).toEqual('0.25em')
122+
expect(theme.resolve('superLoose', ['--leading'])).toEqual('3')
123123
expect(theme.resolve('1/2', ['--width'])).toEqual('60%')
124124
expect(theme.resolve('0.5', ['--width'])).toEqual('60%')
125125
expect(theme.resolve('100%', ['--width'])).toEqual('100%')

packages/tailwindcss/src/compat/apply-config-to-theme.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,27 @@ export function keyPathToCssProperty(path: string[]) {
185185
.map((path, idx, all) => (path === '1' && idx !== all.length - 1 ? '' : path))
186186

187187
// Resolve the key path to a CSS variable segment
188-
.map((part) =>
189-
part
190-
.replaceAll('.', '_')
191-
.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`),
192-
)
188+
.map((part, idx) => {
189+
part = part.replaceAll('.', '_')
190+
191+
let shouldConvert =
192+
// The first "namespace" part should be converted to kebab-case
193+
// This converts things like backgroundColor to `background-color`
194+
idx === 0 ||
195+
// Any tuple nested key should be converted to kebab-case
196+
// These are identified with a leading `-`
197+
// e.g. `fontSize.xs.1.lineHeight` -> `font-size-xs--line-height`
198+
part.startsWith('-') ||
199+
// `lineHeight` is a bit of a special case in which it does not
200+
// always begin with a leading `-` even when as a nested tuple key
201+
part === 'lineHeight'
202+
203+
if (shouldConvert) {
204+
part = part.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`)
205+
}
206+
207+
return part
208+
})
193209

194210
// Remove the `DEFAULT` key at the end of a path
195211
// We're reading from CSS anyway so it'll be a string

packages/tailwindcss/src/compat/config.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,3 +1719,55 @@ test('The theme() function does not try indexing into strings', async () => {
17191719
"
17201720
`)
17211721
})
1722+
1723+
test('camel case keys are preserved', async () => {
1724+
let compiler = await compile(
1725+
css`
1726+
@tailwind utilities;
1727+
@theme {
1728+
--color-blue-green: slate;
1729+
}
1730+
@config "./plugin.js";
1731+
`,
1732+
{
1733+
loadModule: async () => {
1734+
return {
1735+
base: '/',
1736+
path: '',
1737+
module: {
1738+
theme: {
1739+
extend: {
1740+
backgroundColor: {
1741+
lightGreen: '#c0ffee',
1742+
},
1743+
},
1744+
},
1745+
},
1746+
}
1747+
},
1748+
},
1749+
)
1750+
1751+
expect(
1752+
compiler.build([
1753+
// From CSS
1754+
'bg-blue-green', // should be output
1755+
'bg-blueGreen', // should not
1756+
1757+
// From JS config
1758+
'bg-light-green', // should not be output
1759+
'bg-lightGreen', // should be
1760+
]),
1761+
).toMatchInlineSnapshot(`
1762+
".bg-blue-green {
1763+
background-color: var(--color-blue-green);
1764+
}
1765+
.bg-lightGreen {
1766+
background-color: #c0ffee;
1767+
}
1768+
:root, :host {
1769+
--color-blue-green: slate;
1770+
}
1771+
"
1772+
`)
1773+
})

0 commit comments

Comments
 (0)