Skip to content

Commit 2f1cbbf

Browse files
authored
Merge suggestions when using @utility (#18900)
This PR fixes a bug where custom `@utility` implementations with a name that match an existing utility would override the existing suggestions even though we generate both utilities. With this, we want to make sure that both the custom and the built-in utilities are suggested. We also want to make sure that we don't get duplicate suggestions. E.g.: - `font-` would suggest: - 'font-black' - 'font-bold' - 'font-extrabold' - 'font-extralight' - 'font-light' - 'font-medium' - 'font-mono' - 'font-normal' - 'font-sans' - 'font-semibold' - 'font-serif' - 'font-thin' But if you introduce this little custom utility: ```css @theme { --custom-font-weights-foo: 123; } @Utility font-* { --my-weight: --value(--custom-font-weights- *); } ``` - `font-` would suggest: - 'font-foo' With this fix, we would suggest: - `font-` would suggest: - 'font-black' - 'font-bold' - 'font-extrabold' - 'font-extralight' - 'font-foo' // This is now added - 'font-light' - 'font-medium' - 'font-mono' - 'font-normal' - 'font-sans' - 'font-semibold' - 'font-serif' - 'font-thin' We also make sure that they are unique, so if you have a custom utility that happens to match another existing utility (e.g. `font-bold`), you won't see `font-bold` twice in the suggestions. ```css @theme { --custom-font-weights-bold: bold; --custom-font-weights-normal: normal; --custom-font-weights-foo: 1234; } @Utility font-* { --my-weight: --value(--custom-font-weights-*); } ``` - `font-` would suggest: - 'font-black' - 'font-bold' // Overlaps with existing utility - 'font-extrabold' - 'font-extralight' - 'font-foo' // This is now added - 'font-light' - 'font-medium' - 'font-mono' - 'font-normal' // Overlaps with existing utility - 'font-sans' - 'font-semibold' - 'font-serif' - 'font-thin'
1 parent 77b3cb5 commit 2f1cbbf

File tree

4 files changed

+43
-3
lines changed

4 files changed

+43
-3
lines changed

CHANGELOG.md

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

1212
- Handle `'` syntax in ClojureScript when extracting classes ([#18888](https://github.com/tailwindlabs/tailwindcss/pull/18888))
1313
- Handle `@variant` inside `@custom-variant` ([#18885](https://github.com/tailwindlabs/tailwindcss/pull/18885))
14+
- Merge suggestions when using `@utility` ([#18900](https://github.com/tailwindlabs/tailwindcss/pull/18900))
1415

1516
## [4.1.13] - 2025-09-03
1617

packages/tailwindcss/src/intellisense.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,39 @@ test('Custom functional @utility', async () => {
572572
expect(classMap.get('example-xs')?.modifiers).toEqual(['normal', 'foo', 'bar'])
573573
})
574574

575+
test('Custom utilities sharing a root with built-in utilities should merge suggestions', async () => {
576+
let input = css`
577+
@import 'tailwindcss/utilities';
578+
@theme {
579+
--font-sans: sans-serif;
580+
}
581+
582+
@theme {
583+
--font-weight-custom: 1234;
584+
--font-weight-bold: bold; /* Overlap with existing utility */
585+
}
586+
587+
@utility font-* {
588+
--my-font-weight: --value(--font-weight- *);
589+
}
590+
`
591+
592+
let design = await __unstable__loadDesignSystem(input, {
593+
loadStylesheet: async (_, base) => ({
594+
path: '',
595+
base,
596+
content: '@tailwind utilities;',
597+
}),
598+
})
599+
600+
let classMap = new Map(design.getClassList())
601+
let classNames = Array.from(classMap.keys())
602+
603+
expect(classNames).toContain('font-sans') // Existing font-family utility
604+
expect(classNames).toContain('font-bold') // Existing font-family utility & custom font-weight utility
605+
expect(classNames).toContain('font-custom') // Custom font-weight utility
606+
})
607+
575608
test('Theme keys with underscores are suggested with underscores', async () => {
576609
let input = css`
577610
@import 'tailwindcss/utilities';

packages/tailwindcss/src/intellisense.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ export function getClassList(design: DesignSystem): ClassEntry[] {
5555
item.fraction ||= fraction
5656
item.modifiers.push(...group.modifiers)
5757
}
58+
59+
// Deduplicate modifiers
60+
item.modifiers = Array.from(new Set(item.modifiers))
5861
}
5962
}
6063
}

packages/tailwindcss/src/utilities.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,12 @@ export class Utilities {
124124
}
125125

126126
suggest(name: string, groups: () => SuggestionGroup[]) {
127-
// TODO: We are calling this multiple times on purpose but ideally only ever
128-
// once per utility root.
129-
this.completions.set(name, groups)
127+
let existingGroups = this.completions.get(name)
128+
if (existingGroups) {
129+
this.completions.set(name, () => [...existingGroups?.(), ...groups?.()])
130+
} else {
131+
this.completions.set(name, groups)
132+
}
130133
}
131134

132135
keys(kind: 'static' | 'functional') {

0 commit comments

Comments
 (0)