Skip to content

Commit bf17991

Browse files
Reset default @theme values for non extend JS theme config (#14672)
Imagine the following setup: ```css /* src/input.css */ @import "tailwindcss"; @config "../tailwind.config.ts"; @theme { --color-red-500: #ef4444; } ``` ```ts /* tailwind.config.ts */ export default { theme: { colors: { red: { 600: '#dc2626' } }, extend: { colors: { 400: '#f87171' } } } } ``` Since the theme object in the JS config contains `colors` in the non-`extends` block, you would expect this to _not pull in all the default colors imported via `@import "tailwindcss";`_. This, however, wasn't the case right now since all theme options were purely _additive_ to the CSS. This PR makes it so that non-`extend` theme keys _overwrite default CSS theme values_. The emphasis is on `default` here since you still want to be able to overwrite your options via `@theme {}` in user space. This now generates the same CSS that our upgrade codemods would also generate as this would apply the new CSS right after the `@import "tailwindcss";` rule resulting in: ```css @import "tailwindcss"; @theme { --color-*: initial; --color-red-400: #f87171; --color-red-600: #dc2626; } @theme { --color-red-500: #ef4444; } ``` ## Keyframes This PR also adds a new core API to unset keyframes the same way. We previously had no option of doing that but while working on the above codemods we noticed that keyframes should behave the same way: ```css @import "tailwindcss"; @theme { --keyframes-*: initial; @Keyframes spin { to { transform: rotate(361deg); } } } ``` To do this, the keyframes bookeeping was moved from the main Tailwind CSS v4 file into the `Theme` class. _I’m not sure super of the API yet but we would need a way for the codemods to behave the same as out interop layer here. Option B is that we don't reset keyframes the same way we reset other theme variables_.
1 parent 0e262a1 commit bf17991

19 files changed

+362
-121
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
- Ensure `theme` values defined outside of `extend` in JS configuration files overwrite all existing values for that namespace ([#14672](https://github.com/tailwindlabs/tailwindcss/pull/14672))
1213
- _Upgrade (experimental)_: Speed up template migrations ([#14679](https://github.com/tailwindlabs/tailwindcss/pull/14679))
1314

1415
## [4.0.0-alpha.27] - 2024-10-15

packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export function migrateMediaScreen({
1515
function migrate(root: Root) {
1616
if (!designSystem || !userConfig) return
1717

18-
let resolvedUserConfig = resolveConfig(designSystem, [{ base: '', config: userConfig }])
19-
let screens = resolvedUserConfig?.theme?.screens || {}
18+
let { resolvedConfig } = resolveConfig(designSystem, [{ base: '', config: userConfig }])
19+
let screens = resolvedConfig?.theme?.screens || {}
2020

2121
let mediaQueries = new DefaultMap<string, string | null>((name) => {
2222
let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`) ?? screens?.[name]

packages/@tailwindcss-upgrade/src/migrate-js-config.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
keyPathToCssProperty,
1010
themeableValues,
1111
} from '../../tailwindcss/src/compat/apply-config-to-theme'
12-
import { applyKeyframesToAst } from '../../tailwindcss/src/compat/apply-keyframes-to-ast'
12+
import { keyframesToRules } from '../../tailwindcss/src/compat/apply-keyframes-to-theme'
1313
import { deepMerge } from '../../tailwindcss/src/compat/config/deep-merge'
1414
import { mergeThemeExtension } from '../../tailwindcss/src/compat/config/resolve-config'
1515
import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types'
@@ -249,7 +249,6 @@ function onlyUsesAllowedTopLevelKeys(theme: ThemeConfig): boolean {
249249
}
250250

251251
function keyframesToCss(keyframes: Record<string, unknown>): string {
252-
let ast: AstNode[] = []
253-
applyKeyframesToAst(ast, { theme: { keyframes } })
252+
let ast: AstNode[] = keyframesToRules({ theme: { keyframes } })
254253
return toCss(ast).trim() + '\n'
255254
}

packages/@tailwindcss-upgrade/src/template/prepare-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ async function createResolvedUserConfig(fullConfigPath: string): Promise<Config>
8383

8484
return resolveConfig(noopDesignSystem, [
8585
{ base: dirname(fullConfigPath), config: unresolvedUserConfig },
86-
]) as any
86+
]).resolvedConfig as any
8787
}
8888

8989
const DEFAULT_CONFIG_FILES = [

packages/tailwindcss/src/compat/apply-compat-hooks.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { rule, toCss, walk, WalkAction, type AstNode } from '../ast'
22
import type { DesignSystem } from '../design-system'
33
import { segment } from '../utils/segment'
44
import { applyConfigToTheme } from './apply-config-to-theme'
5-
import { applyKeyframesToAst } from './apply-keyframes-to-ast'
5+
import { applyKeyframesToTheme } from './apply-keyframes-to-theme'
66
import { createCompatConfig } from './config/create-compat-config'
77
import { resolveConfig } from './config/resolve-config'
88
import type { UserConfig } from './config/types'
@@ -215,12 +215,15 @@ function upgradeToFullPluginSupport({
215215

216216
let userConfig = [...pluginConfigs, ...configs]
217217

218-
let resolvedConfig = resolveConfig(designSystem, [
218+
let { resolvedConfig } = resolveConfig(designSystem, [
219219
{ config: createCompatConfig(designSystem.theme), base },
220220
...userConfig,
221221
{ config: { plugins: [darkModePlugin] }, base },
222222
])
223-
let resolvedUserConfig = resolveConfig(designSystem, userConfig)
223+
let { resolvedConfig: resolvedUserConfig, replacedThemeKeys } = resolveConfig(
224+
designSystem,
225+
userConfig,
226+
)
224227

225228
let pluginApi = buildPluginApi(designSystem, ast, resolvedConfig)
226229

@@ -231,8 +234,8 @@ function upgradeToFullPluginSupport({
231234
// Merge the user-configured theme keys into the design system. The compat
232235
// config would otherwise expand into namespaces like `background-color` which
233236
// core utilities already read from.
234-
applyConfigToTheme(designSystem, resolvedUserConfig)
235-
applyKeyframesToAst(ast, resolvedUserConfig)
237+
applyConfigToTheme(designSystem, resolvedUserConfig, replacedThemeKeys)
238+
applyKeyframesToTheme(designSystem, resolvedUserConfig, replacedThemeKeys)
236239

237240
registerThemeVariantOverrides(resolvedUserConfig, designSystem)
238241
registerScreensConfig(resolvedUserConfig, designSystem)

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

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { expect, test } from 'vitest'
22
import { buildDesignSystem } from '../design-system'
3-
import { Theme } from '../theme'
3+
import { Theme, ThemeOptions } from '../theme'
44
import { applyConfigToTheme } from './apply-config-to-theme'
55
import { resolveConfig } from './config/resolve-config'
66

7-
test('Config values can be merged into the theme', () => {
7+
test('config values can be merged into the theme', () => {
88
let theme = new Theme()
99
let design = buildDesignSystem(theme)
1010

11-
let resolvedUserConfig = resolveConfig(design, [
11+
let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [
1212
{
1313
config: {
1414
theme: {
@@ -54,7 +54,7 @@ test('Config values can be merged into the theme', () => {
5454
base: '/root',
5555
},
5656
])
57-
applyConfigToTheme(design, resolvedUserConfig)
57+
applyConfigToTheme(design, resolvedConfig, replacedThemeKeys)
5858

5959
expect(theme.resolve('primary', ['--color'])).toEqual('#c0ffee')
6060
expect(theme.resolve('sm', ['--breakpoint'])).toEqual('1234px')
@@ -75,11 +75,60 @@ test('Config values can be merged into the theme', () => {
7575
])
7676
})
7777

78-
test('Invalid keys are not merged into the theme', () => {
78+
test('will reset default theme values with overwriting theme values', () => {
7979
let theme = new Theme()
8080
let design = buildDesignSystem(theme)
8181

82-
let resolvedUserConfig = resolveConfig(design, [
82+
theme.add('--color-blue-400', 'lightblue', ThemeOptions.DEFAULT)
83+
theme.add('--color-blue-500', 'blue', ThemeOptions.DEFAULT)
84+
theme.add('--color-red-400', '#f87171')
85+
theme.add('--color-red-500', '#ef4444')
86+
87+
let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [
88+
{
89+
config: {
90+
theme: {
91+
colors: {
92+
blue: {
93+
500: '#3b82f6',
94+
},
95+
red: {
96+
500: 'red',
97+
},
98+
},
99+
extend: {
100+
colors: {
101+
blue: {
102+
600: '#2563eb',
103+
},
104+
red: {
105+
600: '#dc2626',
106+
},
107+
},
108+
},
109+
},
110+
},
111+
base: '/root',
112+
},
113+
])
114+
applyConfigToTheme(design, resolvedConfig, replacedThemeKeys)
115+
116+
expect(theme.namespace('--color')).toMatchInlineSnapshot(`
117+
Map {
118+
"red-400" => "#f87171",
119+
"red-500" => "#ef4444",
120+
"blue-500" => "#3b82f6",
121+
"blue-600" => "#2563eb",
122+
"red-600" => "#dc2626",
123+
}
124+
`)
125+
})
126+
127+
test('invalid keys are not merged into the theme', () => {
128+
let theme = new Theme()
129+
let design = buildDesignSystem(theme)
130+
131+
let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [
83132
{
84133
config: {
85134
theme: {
@@ -92,7 +141,7 @@ test('Invalid keys are not merged into the theme', () => {
92141
},
93142
])
94143

95-
applyConfigToTheme(design, resolvedUserConfig)
144+
applyConfigToTheme(design, resolvedConfig, replacedThemeKeys)
96145

97146
let entries = Array.from(theme.entries())
98147

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,18 @@ function resolveThemeValue(value: unknown, subValue: string | null = null): stri
1919
return null
2020
}
2121

22-
export function applyConfigToTheme(designSystem: DesignSystem, { theme }: ResolvedConfig) {
22+
export function applyConfigToTheme(
23+
designSystem: DesignSystem,
24+
{ theme }: ResolvedConfig,
25+
replacedThemeKeys: Set<string>,
26+
) {
27+
for (let resetThemeKey of replacedThemeKeys) {
28+
let name = keyPathToCssProperty([resetThemeKey])
29+
if (!name) continue
30+
31+
designSystem.theme.clearNamespace(`--${name}`, ThemeOptions.DEFAULT)
32+
}
33+
2334
for (let [path, value] of themeableValues(theme)) {
2435
if (typeof value !== 'string' && typeof value !== 'number') {
2536
continue

packages/tailwindcss/src/compat/apply-keyframes-to-ast.test.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

packages/tailwindcss/src/compat/apply-keyframes-to-ast.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)