From 7a836d84955ae1a0e8e9c739a7da5fed0ff11cb9 Mon Sep 17 00:00:00 2001 From: Casey Corcoran Date: Mon, 3 Aug 2026 09:11:32 -0500 Subject: [PATCH] Fix negation of theme() in arbitrary values (#19928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Using a negative modifier with `theme()` inside an arbitrary value produces no CSS output. - ✅ `ms-[theme(margin.1)]` — works - ✅ `-ms-[10px]` — works - ❌ `-ms-[theme(margin.1)]` — no output ## Cause `negateValue()` in `src/util/negateValue.js` doesn't recognize `theme()` as a negatable function. It falls through all conditions and returns `undefined`, causing the utility to be silently dropped. ## Fix Added `'theme'` to the `numericFunctions` list so `theme()` values get wrapped in `calc(... * -1)`, which the PostCSS `theme()` resolver then processes downstream. ## Tests - **Unit test** (`negateValue.test.js`): `negateValue('theme(margin.1)')` returns `calc(theme(margin.1) * -1)` - **Integration test** (`arbitrary-values.test.js`): `-mt-[theme(spacing.1)]` produces `margin-top: -0.25rem` --------- Co-authored-by: Casey Corcoran Co-authored-by: Robin Malfait --- CHANGELOG.md | 1 + src/util/negateValue.js | 12 +++++++----- tests/arbitrary-values.test.js | 19 +++++++++++++++++++ tests/negateValue.test.js | 4 ++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 554bad8dd..6416b45c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Ensure classes are extracted when a variant precedes an arbitrary variant containing a quoted attribute selector (e.g. `focus-visible:[&:not([aria-selected="true"])]:bg-red-500`) ([#20374](https://github.com/tailwindlabs/tailwindcss/pull/20374)) +- Fix negation of `theme()` in arbitrary values (e.g. `-m-[theme(margin.1)]`) ([#19928](https://github.com/tailwindlabs/tailwindcss/pull/19928)) ## [3.4.19] - 2025-12-10 diff --git a/src/util/negateValue.js b/src/util/negateValue.js index b1cdf78ea..d3df8d72c 100644 --- a/src/util/negateValue.js +++ b/src/util/negateValue.js @@ -10,11 +10,13 @@ export default function negateValue(value) { return value.replace(/^[+-]?/, (sign) => (sign === '-' ? '' : '-')) } - // What functions we support negating numeric values for - // var() isn't inherently a numeric function but we support it anyway - // The trigonometric functions are omitted because you'll need to use calc(…) with them _anyway_ - // to produce generally useful results and that will be covered already - let numericFunctions = ['var', 'calc', 'min', 'max', 'clamp'] + // What functions we support negating numeric values for var() and theme() + // aren't inherently a numeric function but we support it anyway + // + // The trigonometric functions are omitted because you'll need to use calc(…) + // with them _anyway_ to produce generally useful results and that will be + // covered already + let numericFunctions = ['var', 'theme', 'calc', 'min', 'max', 'clamp'] for (const fn of numericFunctions) { if (value.includes(`${fn}(`)) { diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index 594980803..8e3708ec1 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -354,6 +354,25 @@ it('should be possible to read theme values in arbitrary values (without quotes) }) }) +it('should be possible to negate theme values in arbitrary values with negative modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + spacing: { + 1: '0.25rem', + }, + }, + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .-mt-\[theme\(spacing\.1\)\] { + margin-top: -0.25rem; + } + `) + }) +}) + it('should be possible to read theme values in arbitrary values (with quotes)', () => { let config = { content: [{ raw: html`
` }], diff --git a/tests/negateValue.test.js b/tests/negateValue.test.js index c31741d48..265d2d3d9 100644 --- a/tests/negateValue.test.js +++ b/tests/negateValue.test.js @@ -12,3 +12,7 @@ test('values that cannot be negated become undefined', () => { expect(negateValue('auto')).toBeUndefined() expect(negateValue('cover')).toBeUndefined() }) + +test('it negates theme() function calls', () => { + expect(negateValue('theme(margin.1)')).toEqual('calc(theme(margin.1) * -1)') +})