Skip to content
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add support for `aria`, `supports`, and `data` variants defined in JS config files ([#14407](https://github.com/tailwindlabs/tailwindcss/pull/14407))

### Fixed

- Support `borderRadius.*` as an alias for `--radius-*` when using dot notation inside the `theme()` function ([#14436](https://github.com/tailwindlabs/tailwindcss/pull/14436))
Expand Down
3 changes: 3 additions & 0 deletions packages/tailwindcss/src/compat/apply-compat-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { resolveConfig } from './config/resolve-config'
import type { UserConfig } from './config/types'
import { darkModePlugin } from './dark-mode'
import { buildPluginApi, type CssPluginOptions, type Plugin } from './plugin-api'
import { registerThemeVariantOverrides } from './theme-variants'

export async function applyCompatibilityHooks({
designSystem,
Expand Down Expand Up @@ -185,6 +186,8 @@ export async function applyCompatibilityHooks({
// core utilities already read from.
applyConfigToTheme(designSystem, userConfig)

registerThemeVariantOverrides(resolvedConfig, designSystem)

// Replace `resolveThemeValue` with a version that is backwards compatible
// with dot-notation but also aware of any JS theme configurations registered
// by plugins or JS config files. This is significantly slower than just
Expand Down
4 changes: 2 additions & 2 deletions packages/tailwindcss/src/compat/apply-config-to-theme.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { test } from 'vitest'
import { expect, test } from 'vitest'
import { buildDesignSystem } from '../design-system'
import { Theme } from '../theme'
import { applyConfigToTheme } from './apply-config-to-theme'

test('Config values can be merged into the theme', ({ expect }) => {
test('Config values can be merged into the theme', () => {
let theme = new Theme()
let design = buildDesignSystem(theme)

Expand Down
114 changes: 100 additions & 14 deletions packages/tailwindcss/src/compat/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { describe, test } from 'vitest'
import { describe, expect, test } from 'vitest'
import { compile } from '..'
import plugin from '../plugin'
import { flattenColorPalette } from './flatten-color-palette'

const css = String.raw

test('Config files can add content', async ({ expect }) => {
test('Config files can add content', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand All @@ -18,7 +18,7 @@ test('Config files can add content', async ({ expect }) => {
expect(compiler.globs).toEqual([{ origin: './config.js', pattern: './file.txt' }])
})

test('Config files can change dark mode (media)', async ({ expect }) => {
test('Config files can change dark mode (media)', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand All @@ -38,7 +38,7 @@ test('Config files can change dark mode (media)', async ({ expect }) => {
`)
})

test('Config files can change dark mode (selector)', async ({ expect }) => {
test('Config files can change dark mode (selector)', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand All @@ -58,7 +58,7 @@ test('Config files can change dark mode (selector)', async ({ expect }) => {
`)
})

test('Config files can change dark mode (variant)', async ({ expect }) => {
test('Config files can change dark mode (variant)', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand All @@ -78,7 +78,7 @@ test('Config files can change dark mode (variant)', async ({ expect }) => {
`)
})

test('Config files can add plugins', async ({ expect }) => {
test('Config files can add plugins', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand Down Expand Up @@ -106,7 +106,7 @@ test('Config files can add plugins', async ({ expect }) => {
`)
})

test('Plugins loaded from config files can contribute to the config', async ({ expect }) => {
test('Plugins loaded from config files can contribute to the config', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand All @@ -132,7 +132,7 @@ test('Plugins loaded from config files can contribute to the config', async ({ e
`)
})

test('Config file presets can contribute to the config', async ({ expect }) => {
test('Config file presets can contribute to the config', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand All @@ -158,7 +158,7 @@ test('Config file presets can contribute to the config', async ({ expect }) => {
`)
})

test('Config files can affect the theme', async ({ expect }) => {
test('Config files can affect the theme', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand Down Expand Up @@ -197,7 +197,7 @@ test('Config files can affect the theme', async ({ expect }) => {
`)
})

test('Variants in CSS overwrite variants from plugins', async ({ expect }) => {
test('Variants in CSS overwrite variants from plugins', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
Expand Down Expand Up @@ -334,7 +334,7 @@ describe('theme callbacks', () => {
})

describe('theme overrides order', () => {
test('user theme > js config > default theme', async ({ expect }) => {
test('user theme > js config > default theme', async () => {
let input = css`
@theme default {
--color-red: red;
Expand Down Expand Up @@ -373,7 +373,7 @@ describe('theme overrides order', () => {
`)
})

test('user theme > js config > default theme (with nested object)', async ({ expect }) => {
test('user theme > js config > default theme (with nested object)', async () => {
let input = css`
@theme default {
--color-slate-100: #000100;
Expand Down Expand Up @@ -498,7 +498,7 @@ describe('theme overrides order', () => {
})

describe('default font family compatibility', () => {
test('overriding `fontFamily.sans` sets `--default-font-family`', async ({ expect }) => {
test('overriding `fontFamily.sans` sets `--default-font-family`', async () => {
let input = css`
@theme default {
--default-font-family: var(--font-family-sans);
Expand Down Expand Up @@ -756,7 +756,7 @@ describe('default font family compatibility', () => {
`)
})

test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async ({ expect }) => {
test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async () => {
let input = css`
@theme default {
--default-mono-font-family: var(--font-family-mono);
Expand Down Expand Up @@ -978,3 +978,89 @@ describe('default font family compatibility', () => {
`)
})
})

test('creates variants for `data`, `supports`, and `aria` theme options at the same level as the core utility ', async () => {
let input = css`
@tailwind utilities;
@config "./config.js";
`

let compiler = await compile(input, {
loadConfig: async () => ({
theme: {
extend: {
aria: {
polite: 'live="polite"',
},
supports: {
'child-combinator': 'selector(h2 > p)',
foo: 'bar',
},
data: {
checked: 'ui~="checked"',
},
},
},
}),
})

expect(
compiler.build([
'aria-polite:underline',
'supports-child-combinator:underline',
'supports-foo:underline',
'data-checked:underline',

// Ensure core variants still work
'aria-hidden:flex',
'supports-grid:flex',
'data-foo:flex',

// The `print` variant should still be sorted last, even after registering
// the other custom variants.
'print:flex',
]),
).toMatchInlineSnapshot(`
".aria-polite\\:underline {
&[aria-live="polite"] {
text-decoration-line: underline;
}
}
.aria-hidden\\:flex {
&[aria-hidden="true"] {
display: flex;
}
}
.data-checked\\:underline {
&[data-ui~="checked"] {
text-decoration-line: underline;
}
}
.data-foo\\:flex {
&[data-foo] {
display: flex;
}
}
.supports-child-combinator\\:underline {
@supports selector(h2 > p) {
text-decoration-line: underline;
}
}
.supports-foo\\:underline {
@supports (bar: var(--tw)) {
text-decoration-line: underline;
}
}
.supports-grid\\:flex {
@supports (grid: var(--tw)) {
display: flex;
}
}
.print\\:flex {
@media print {
display: flex;
}
}
"
`)
})
8 changes: 4 additions & 4 deletions packages/tailwindcss/src/compat/config/resolve-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { test } from 'vitest'
import { expect, test } from 'vitest'
import { buildDesignSystem } from '../../design-system'
import { Theme } from '../../theme'
import { resolveConfig } from './resolve-config'

test('top level theme keys are replaced', ({ expect }) => {
test('top level theme keys are replaced', () => {
let design = buildDesignSystem(new Theme())

let config = resolveConfig(design, [
Expand Down Expand Up @@ -52,7 +52,7 @@ test('top level theme keys are replaced', ({ expect }) => {
})
})

test('theme can be extended', ({ expect }) => {
test('theme can be extended', () => {
let design = buildDesignSystem(new Theme())

let config = resolveConfig(design, [
Expand Down Expand Up @@ -164,7 +164,7 @@ test('theme keys can reference other theme keys using the theme function regardl
})
})

test('theme keys can read from the CSS theme', ({ expect }) => {
test('theme keys can read from the CSS theme', () => {
let theme = new Theme()
theme.add('--color-green', 'green')

Expand Down
26 changes: 13 additions & 13 deletions packages/tailwindcss/src/compat/plugin-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { CssInJs, PluginAPI } from './plugin-api'
const css = String.raw

describe('theme', async () => {
test('plugin theme can contain objects', async ({ expect }) => {
test('plugin theme can contain objects', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -68,7 +68,7 @@ describe('theme', async () => {
`)
})

test('plugin theme can extend colors', async ({ expect }) => {
test('plugin theme can extend colors', async () => {
let input = css`
@theme reference {
--color-red-500: #ef4444;
Expand Down Expand Up @@ -208,7 +208,7 @@ describe('theme', async () => {
`)
})

test('plugin theme can have opacity modifiers', async ({ expect }) => {
test('plugin theme can have opacity modifiers', async () => {
let input = css`
@tailwind utilities;
@theme {
Expand Down Expand Up @@ -251,7 +251,7 @@ describe('theme', async () => {
"
`)
})
test('theme value functions are resolved correctly regardless of order', async ({ expect }) => {
test('theme value functions are resolved correctly regardless of order', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -302,7 +302,7 @@ describe('theme', async () => {
`)
})

test('plugins can override the default key', async ({ expect }) => {
test('plugins can override the default key', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -342,7 +342,7 @@ describe('theme', async () => {
`)
})

test('plugins can read CSS theme keys using the old theme key notation', async ({ expect }) => {
test('plugins can read CSS theme keys using the old theme key notation', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -397,7 +397,7 @@ describe('theme', async () => {
`)
})

test('CSS theme values are merged with JS theme values', async ({ expect }) => {
test('CSS theme values are merged with JS theme values', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -448,7 +448,7 @@ describe('theme', async () => {
`)
})

test('CSS theme defaults take precedence over JS theme defaults', async ({ expect }) => {
test('CSS theme defaults take precedence over JS theme defaults', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -492,7 +492,7 @@ describe('theme', async () => {
`)
})

test('CSS theme values take precedence even over non-object JS values', async ({ expect }) => {
test('CSS theme values take precedence even over non-object JS values', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -530,7 +530,7 @@ describe('theme', async () => {
})
})

test('all necessary theme keys support bare values', async ({ expect }) => {
test('all necessary theme keys support bare values', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -768,7 +768,7 @@ describe('theme', async () => {
`)
})

test('theme keys can derive from other theme keys', async ({ expect }) => {
test('theme keys can derive from other theme keys', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -834,7 +834,7 @@ describe('theme', async () => {
expect(fn).toHaveBeenNthCalledWith(3, 'ease-out')
})

test('nested theme key lookups work even for flattened keys', async ({ expect }) => {
test('nested theme key lookups work even for flattened keys', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down Expand Up @@ -889,7 +889,7 @@ describe('theme', async () => {
expect(fn).toHaveBeenCalledWith({}) // Present in the resolved config
})

test('Candidates can match multiple utility definitions', async ({ expect }) => {
test('Candidates can match multiple utility definitions', async () => {
let input = css`
@tailwind utilities;
@plugin "my-plugin";
Expand Down
Loading