Skip to content
Merged
Next Next commit
Add ability to force theme function resolution as inline
  • Loading branch information
philipp-spiess committed Mar 17, 2025
commit 1ce8bd972c6d0446acc45f8af30dba6fd2e56b03
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function migrateMediaScreen({
let screens = resolvedConfig?.theme?.screens || {}

let mediaQueries = new DefaultMap<string, string | null>((name) => {
let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`) ?? screens?.[name]
let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`, true) ?? screens?.[name]
if (typeof value === 'string') return `(width >= theme(--breakpoint-${name}))`
return value ? buildMediaQuery(value) : null
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ export async function legacyClasses(

if (fromThemeKey && toThemeKey) {
// Migrating something that resolves to a value in the theme.
let customFrom = designSystem.resolveThemeValue(fromThemeKey)
let defaultFrom = defaultDesignSystem.resolveThemeValue(fromThemeKey)
let customTo = designSystem.resolveThemeValue(toThemeKey)
let customFrom = designSystem.resolveThemeValue(fromThemeKey, true)
let defaultFrom = defaultDesignSystem.resolveThemeValue(fromThemeKey, true)
let customTo = designSystem.resolveThemeValue(toThemeKey, true)
let defaultTo = defaultDesignSystem.resolveThemeValue(toThemeKey)

// The new theme value is not defined, which means we can't safely
Expand Down
10 changes: 5 additions & 5 deletions packages/tailwindcss/src/compat/apply-compat-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ export async function applyCompatibilityHooks({
// compatibility concerns localized to our compatibility layer.
let resolveThemeVariableValue = designSystem.resolveThemeValue

designSystem.resolveThemeValue = function resolveThemeValue(path: string) {
designSystem.resolveThemeValue = function resolveThemeValue(path: string, forceInline?: boolean) {
if (path.startsWith('--')) {
return resolveThemeVariableValue(path)
return resolveThemeVariableValue(path, forceInline)
}

// If the theme value is not found in the simple resolver, we upgrade to the full backward
Expand All @@ -149,7 +149,7 @@ export async function applyCompatibilityHooks({
configs: [],
pluginDetails: [],
})
return designSystem.resolveThemeValue(path)
return designSystem.resolveThemeValue(path, forceInline)
}

// If there are no plugins or configs registered, we don't need to register
Expand Down Expand Up @@ -260,8 +260,8 @@ function upgradeToFullPluginSupport({
// config files are actually being used. In the future we may want to optimize
// this further by only doing this if plugins or config files _actually_
// registered JS config objects.
designSystem.resolveThemeValue = function resolveThemeValue(path: string, defaultValue?: string) {
let resolvedValue = pluginApi.theme(path, defaultValue)
designSystem.resolveThemeValue = function resolveThemeValue(path: string, forceInline?: boolean) {
let resolvedValue = pluginApi.theme(path, forceInline)

if (Array.isArray(resolvedValue) && resolvedValue.length === 2) {
// When a tuple is returned, return the first element
Expand Down
34 changes: 34 additions & 0 deletions packages/tailwindcss/src/css-functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,40 @@ describe('--theme(…)', () => {
`[Error: The --theme(…) function can only be used with CSS variables from your theme.]`,
)
})

test('--theme(…) forces the value to be retrieved as inline when used inside an at rule', async () => {
expect(
await compileCss(css`
@theme {
--breakpoint-md: 48rem;
--breakpoint-lg: 64rem;
}
@custom-media --md (width >= --theme(--breakpoint-md));
@media (--md) {
.blue {
color: blue;
}
}
@media (width >= --theme(--breakpoint-lg)) {
.red {
color: red;
}
}
`),
).toMatchInlineSnapshot(`
"@media (width >= 48rem) {
.blue {
color: #00f;
}
}

@media (width >= 64rem) {
.red {
color: red;
}
}"
`)
})
})

describe('theme(…)', () => {
Expand Down
14 changes: 13 additions & 1 deletion packages/tailwindcss/src/css-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,19 @@ function theme(designSystem: DesignSystem, path: string, ...fallback: string[])
throw new Error(`The --theme(…) function can only be used with CSS variables from your theme.`)
}

return legacyTheme(designSystem, path, ...fallback)
let resolvedValue = designSystem.resolveThemeValue(path)

if (!resolvedValue && fallback.length > 0) {
return fallback.join(', ')
}

if (!resolvedValue) {
throw new Error(
`Could not resolve value for theme function: \`theme(${path})\`. Consider checking if the path is correct or provide a fallback value to silence this error.`,
)
}

return resolvedValue
}

function legacyTheme(designSystem: DesignSystem, path: string, ...fallback: string[]) {
Expand Down
10 changes: 6 additions & 4 deletions packages/tailwindcss/src/design-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { compileAstNodes, compileCandidates } from './compile'
import { substituteFunctions } from './css-functions'
import { getClassList, getVariants, type ClassEntry, type VariantEntry } from './intellisense'
import { getClassOrder } from './sort'
import type { Theme, ThemeKey } from './theme'
import { Theme, ThemeOptions, type ThemeKey } from './theme'
import { Utilities, createUtilities, withAlpha } from './utilities'
import { DefaultMap } from './utils/default-map'
import { extractUsedVariables } from './utils/variables'
Expand All @@ -29,7 +29,7 @@ export type DesignSystem = {
compileAstNodes(candidate: Candidate): ReturnType<typeof compileAstNodes>

getVariantOrder(): Map<Variant, number>
resolveThemeValue(path: string): string | undefined
resolveThemeValue(path: string, forceInline?: boolean): string | undefined
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to make this optional because of IntelliSense unfortunately. Also decided to make a boolean here since don't leak the ThemeOption enum beyond internal APIs right now.


trackUsedVariables(raw: string): void

Expand Down Expand Up @@ -150,7 +150,7 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
return order
},

resolveThemeValue(path: `${ThemeKey}` | `${ThemeKey}${string}`) {
resolveThemeValue(path: `${ThemeKey}` | `${ThemeKey}${string}`, forceInline: boolean = true) {
// Extract an eventual modifier from the path. e.g.:
// - "--color-red-500 / 50%" -> "50%"
let lastSlash = path.lastIndexOf('/')
Expand All @@ -160,7 +160,9 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
path = path.slice(0, lastSlash).trim() as ThemeKey
}

let themeValue = theme.get([path]) ?? undefined
let themeValue =
theme.resolve(null, [path], forceInline ? ThemeOptions.INLINE : ThemeOptions.NONE) ??
undefined

// Apply the opacity modifier if present
if (modifier && themeValue) {
Expand Down
8 changes: 6 additions & 2 deletions packages/tailwindcss/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,18 @@ export class Theme {
value.options |= ThemeOptions.USED
}

resolve(candidateValue: string | null, themeKeys: ThemeKey[]): string | null {
resolve(
candidateValue: string | null,
themeKeys: ThemeKey[],
options: ThemeOptions = ThemeOptions.NONE,
): string | null {
let themeKey = this.#resolveKey(candidateValue, themeKeys)

if (!themeKey) return null

let value = this.values.get(themeKey)!

if (value.options & ThemeOptions.INLINE) {
if ((options | value.options) & ThemeOptions.INLINE) {
return value.value
}

Expand Down