No description
Find a file
Kirk Ouimet 51aa9d799c
fix(canonicalize): handle utilities with empty property maps in collapse (#19727)
## Problem

`canonicalizeCandidates` crashes when called with `collapse: true` and
the candidate list includes utilities whose CSS output contains no
standard declaration properties (only `@property` rules and CSS custom
properties).

This is reproducible with vanilla Tailwind CSS and no custom
configuration:

```js
designSystem.canonicalizeCandidates(['shadow-sm', 'border'], { collapse: true })
// TypeError: X is not iterable
```

```js
designSystem.canonicalizeCandidates(['shadow-sm', 'border'], { collapse: true })
// TypeError: Cannot read properties of null (reading 'has')
```

All shadow utilities (`shadow-sm`, `shadow-md`, `shadow-lg`,
`shadow-xl`) crash when combined with any other utility and `collapse:
true`.

This was discovered via `eslint-plugin-better-tailwindcss`, which calls
`canonicalizeCandidates` with `collapse: true` for its
`enforce-canonical-classes` rule. The crash brings down ESLint entirely.

## Root cause

In `collapseGroup`, the `otherUtilities` array is built by mapping over
each candidate's property values:

```ts
let otherUtilities = candidatePropertiesValues.map((propertyValues) => {
  let result: Set<string> | null = null
  for (let property of propertyValues.keys()) {
    // ... builds result ...
  }
  return result! // returns null if propertyValues has no keys
})
```

When a utility like `shadow-sm` generates CSS with `@property` rules and
custom property declarations but no standard CSS properties,
`propertyValues.keys()` is empty, the loop never executes, and `result`
stays `null`. The non-null assertion `result!` returns `null` into the
array.

Downstream code then crashes when iterating or calling `.has()` on the
null entry:

```ts
for (let i = 0; i < otherUtilities.length; i++) {
  let current = otherUtilities[i]     // null
  for (let property of current) {     // "X is not iterable"
    if (other.has(property)) {        // "Cannot read properties of null"
```

## Fix

Return an empty `Set` instead of `null` when a utility has no property
keys:

```ts
return result ?? new Set<string>()
```

This is semantically correct: a utility with no standard properties
cannot be linked to or collapsed with any other utility, which is
exactly what an empty Set represents in the linking algorithm. It won't
cause false collapses or suppress valid collapses of other utilities.

## Test plan

- Added test: `collapse does not crash when utilities with no standard
properties are present`
- Verifies `shadow-sm + border`, `shadow-md + p-4`, and `shadow-sm +
shadow-md` don't throw
- Verifies the candidates are returned uncollapsed (correct behavior)
- All 1218 existing tests continue to pass

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-03-10 16:48:07 +01:00
.github chore: Update outdated GitHub Actions versions (#19577) 2026-02-18 12:27:13 +00:00
crates Extract classes from interpolated expressions in Ruby (#19730) 2026-02-26 12:23:50 +01:00
integrations Fallback to config.createResolver for client and ssr environments in @tailwindcss/vite (#19679) 2026-02-17 19:59:57 +00:00
packages fix(canonicalize): handle utilities with empty property maps in collapse (#19727) 2026-03-10 16:48:07 +01:00
patches Bump dependencies (#19608) 2026-02-04 12:38:50 +01:00
playgrounds Update bun 1.3.7 → 1.3.9 (patch) (#19678) 2026-02-18 12:00:11 +01:00
scripts Make TypeScript a bit more happy (#19124) 2025-10-14 19:52:46 +00:00
.gitignore Fix slow unit test (#17465) 2025-03-31 15:26:01 +02:00
.prettierignore Bump dependencies (#19608) 2026-02-04 12:38:50 +01:00
Cargo.lock Remove unused crossbeam dependency in oxide (#19256) 2025-11-04 10:37:21 -05:00
Cargo.toml Hoist oxide/crates to just crates (#13333) 2024-03-23 09:00:48 -04:00
CHANGELOG.md fix(canonicalize): handle utilities with empty property maps in collapse (#19727) 2026-03-10 16:48:07 +01:00
LICENSE Add README, LICENSE, and CONTRIBUTING (#13088) 2024-03-05 14:45:39 -05:00
package.json Bump dependencies (#19608) 2026-02-04 12:38:50 +01:00
pnpm-lock.yaml Update bun 1.3.7 → 1.3.9 (patch) (#19678) 2026-02-18 12:00:11 +01:00
pnpm-workspace.yaml Bump dependencies (#19608) 2026-02-04 12:38:50 +01:00
README.md docs: fix GitHub links to tailwindlabs org (#19686) 2026-02-17 13:06:49 +01:00
rust-toolchain.toml Improve Oxide candidate extractor [0] (#16306) 2025-03-05 11:55:24 +01:00
turbo.json Fix segmentation fault when loading @tailwindcss/oxide in a Worker thread (#17276) 2025-03-18 16:28:20 -04:00
vitest.config.ts Bump Vitest to v4 (#19216) 2025-11-20 18:16:20 -05:00

Tailwind CSS

A utility-first CSS framework for rapidly building custom user interfaces.

Build Status Total Downloads Latest Release License


Documentation

For full documentation, visit tailwindcss.com.

Community

For help, discussion about best practices, or feature ideas:

Discuss Tailwind CSS on GitHub

Contributing

If you're interested in contributing to Tailwind CSS, please read our contributing docs before submitting a pull request.