Skip to content

Canonicalization: prevent inlining CSS-wide keywords - #20417

Merged
RobinMalfait merged 4 commits into
mainfrom
fix/issue-intellisense-1610
Aug 14, 2026
Merged

Canonicalization: prevent inlining CSS-wide keywords#20417
RobinMalfait merged 4 commits into
mainfrom
fix/issue-intellisense-1610

Conversation

@RobinMalfait

@RobinMalfait RobinMalfait commented Aug 14, 2026

Copy link
Copy Markdown
Member

This PR fixes an issue where canonicalization suggestions in intellisense result in 'weird' suggestions.

The class text-foreground/60 can be written as text-default-soft-hover

If we look at the CSS provided by the issue, this doesn't immediately make sense:

@theme {
  --color-foreground: var(--foreground);
  --color-default-soft-hover: color-mix(in oklab, var(--default) 60%, transparent);
}

:root {
  --foreground: oklch(0.2103 0.0059 285.89); /* near-black */
  --default: oklch(94% 0.001 286.375); /* light gray */
}

But it turns out that when you use Uniwind (React Native) with HeroUI, that the setup looks more like this:

@import 'tailwindcss';

@theme {
  --foreground: unset;
  --default: unset;
}

@theme inline {
  --color-foreground: var(--foreground);
  --color-default-soft-hover: color-mix(in oklab, var(--default) 60%, transparent);
}

During the canonicalization step, we inline all the @theme values, the reason for this is that text-[#fff] can be turned into text-white even though they look slightly different:

.text-\[\#fff\] {
  color: #fff;
}
.text-white {
  color: var(--color-white, #fff);
}

But when we inline them, it looks like this:

.text-\[\#fff\] {
  color: #fff;
}
.text-white {
  color: #fff;
}

Internally, we use signatures to make sure that they are safe to be subtituted with eachother. In this case, the signatures will look like this:

.x {
  color: #fff;
}
.x {
  color: #fff;
}

If we now look at the signatures of the original issue, you would see:

/* text-foreground/60 */
.x {
  color: color-mix(in_oklab,unset_60%,transparent);
}

/* text-default-soft-hover */
.x {
  color: color-mix(in_oklab,unset_60%,transparent);
}

That's because the @theme variables were inlined, resulting in the exact same signature, thus we consider them the same.

This PR makes sure to never inline CSS-wide keywords (such as unset) and therefore keeping the CSS variable reference:

/* text-foreground/60 */
.x {
  color: color-mix(in_oklab,var(--foreground)_60%,transparent);
}

/* text-default-soft-hover */
.x {
  color: color-mix(in_oklab,var(--default)_60%,transparent);
}

Fixes: tailwindlabs/tailwindcss-intellisense#1610

Test plan

  1. Existing tests pass
  2. Added a regression test

Checked manually in the regression repo

Before:
image

After:
image

@RobinMalfait
RobinMalfait requested a review from a team as a code owner August 14, 2026 10:00
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (2): Last reviewed commit: "Update packages/tailwindcss/src/canonica..." | Re-trigger Greptile

Comment thread packages/tailwindcss/src/canonicalize-candidates.ts Outdated
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b1535387-d093-4697-8a59-bfc4a8c1ac6a

📥 Commits

Reviewing files that changed from the base of the PR and between c2a25e4 and aa2c4c8.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • packages/tailwindcss/src/canonicalize-candidates.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/tailwindcss/src/canonicalize-candidates.ts

Walkthrough

Canonicalization now recognizes CSS-wide keyword values, including unset, in theme variables. It leaves these variables unresolved instead of inlining them. Comments document variable inlining and fallback handling for custom and inline theme definitions. A regression test verifies that text-foreground/60 and text-default-soft-hover remain separate candidates when their theme variables use unset.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation preserves references for CSS-wide keywords and adds a regression test, addressing issue #1610's false-positive canonicalization suggestion.
Out of Scope Changes check ✅ Passed The implementation, regression test, and changelog entry directly support the linked issue and stated pull request objectives.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing canonicalization from inlining CSS-wide keywords.
Description check ✅ Passed The description directly explains the canonicalization bug, the CSS-wide keyword fix, the regression test, and the expected behavior.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e115e3b1-6469-49db-9556-a777b9c0a646

📥 Commits

Reviewing files that changed from the base of the PR and between f7f58f0 and c2a25e4.

📒 Files selected for processing (2)
  • packages/tailwindcss/src/canonicalize-candidates.test.ts
  • packages/tailwindcss/src/canonicalize-candidates.ts

Comment thread packages/tailwindcss/src/canonicalize-candidates.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant