diff --git a/CHANGELOG.md b/CHANGELOG.md index 9915642962a7..b18f6887eb81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure spacing utilities with no value (e.g. `px` or `translate-y`) don't generate CSS ([#14911](https://github.com/tailwindlabs/tailwindcss/pull/14911)) - Don't override user-agent background color for input elements in Preflight ([#14913](https://github.com/tailwindlabs/tailwindcss/pull/14913)) - Don't attempt to convert CSS variables (which should already be percentages) to percentages when used as opacity modifiers ([#14916](https://github.com/tailwindlabs/tailwindcss/pull/14916)) +- Ensure custom utilities registered with the plugin API can start with `@` ([#14793](https://github.com/tailwindlabs/tailwindcss/pull/14793)) - _Upgrade (experimental)_: Install `@tailwindcss/postcss` next to `tailwindcss` ([#14830](https://github.com/tailwindlabs/tailwindcss/pull/14830)) - _Upgrade (experimental)_: Remove whitespace around `,` separator when print arbitrary values ([#14838](https://github.com/tailwindlabs/tailwindcss/pull/14838)) - _Upgrade (experimental)_: Fix crash during upgrade when content globs escape root of project ([#14896](https://github.com/tailwindlabs/tailwindcss/pull/14896)) diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index 73745e837412..6a3062ad224f 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -2943,6 +2943,50 @@ describe('matchUtilities()', () => { ).toEqual('') }) + test('custom functional utilities can start with @', async () => { + async function run(candidates: string[]) { + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + + { + async loadModule(id, base) { + return { + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { '@w': (value) => ({ width: value }) }, + { + values: { + 1: '1px', + }, + }, + ) + }, + } + }, + }, + ) + + return compiled.build(candidates) + } + + expect(optimizeCss(await run(['@w-1','hover:@w-1'])).trim()) + .toMatchInlineSnapshot(` + ".\\@w-1 { + width: 1px; + } + + @media (hover: hover) { + .hover\\:\\@w-1:hover { + width: 1px; + } + }" + `) + }) + test('custom functional utilities can return an array of rules', async () => { let compiled = await compile( css` diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts index 04fb8d43a59d..e348da5c0c5c 100644 --- a/packages/tailwindcss/src/compat/plugin-api.ts +++ b/packages/tailwindcss/src/compat/plugin-api.ts @@ -75,7 +75,7 @@ export type PluginAPI = { prefix(className: string): string } -const IS_VALID_UTILITY_NAME = /^[a-z][a-zA-Z0-9/%._-]*$/ +const IS_VALID_UTILITY_NAME = /^[a-z@][a-zA-Z0-9/%._-]*$/ export function buildPluginApi( designSystem: DesignSystem,