Skip to content

Conversation

@thecrypticace
Copy link
Contributor

@thecrypticace thecrypticace commented Nov 26, 2025

Fixes #19343

This PR makes it so the content-* utilities read from the --content-* theme namespace. This change is purely for backwards compatibility with Tailwind CSS v3. It is recommended you use arbitrary values with the content-* utility instead.

@thecrypticace thecrypticace requested a review from a team as a code owner November 26, 2025 13:06
@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Walkthrough

The patch updates the content functional utility to use the theme key ['--content'] and adds a test verifying content handles environment vars, CSS vars, and theme references (expecting content: var(--tw-content) and a .content-slash rule using var(--content-slash) with the theme value for /). CHANGELOG.md adds an Unreleased → Fixed entry about content theme-key compatibility.

Pre-merge checks

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: handling backwards compatibility for the content theme from JS configs.
Description check ✅ Passed The description accurately relates to the changeset, explaining that the PR makes content-* utilities read from the --content-* theme namespace for backwards compatibility with Tailwind CSS v3.
Linked Issues check ✅ Passed The PR directly addresses issue #19343 by enabling migrated content entries to work as valid utilities, ensuring backwards compatibility with v3 configs and preventing class validation errors.
Out of Scope Changes check ✅ Passed All changes (test addition, utilities.ts modification, and changelog entry) are directly related to the backwards compatibility objective for the content theme key.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1c2b64 and 6297015.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
integrations/upgrade/js-config.test.ts (1)

1843-1923: Solid regression coverage for migrated theme.content utilities

This test exercises the full upgrade path: JS config with theme.content, HTML using after:content-* classes, and the upgraded CSS defining @utility content-* with --tw-content and content: var(--tw-content). That directly guards against the original content-slash/linting regression. As an optional enhancement, you could add a similar case where content lives under theme.extend to make that behavior explicit in the test suite, even though resolveConfig should already normalize it.

packages/tailwindcss/src/compat/content-compat.test.ts (1)

1-33: Runtime compat test correctly validates content-* utility generation

The test cleanly verifies that a JS config with theme.content.slash produces a content-slash utility with the expected --tw-content + content: var(--tw-content) declarations via the compat path. As a minor enhancement, you could add an entry with a non‑string value (e.g. content: { slash: '"/"', foo: 42 }) to assert that non‑string values are skipped, matching the guard in registerContentCompat.

packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts (1)

7-7: Content migration logic is correct but could mirror compat guards for robustness

The new theme.content migration block correctly:

  • Converts each key into a @utility content-<key> rule using --tw-content + content: var(--tw-content).
  • Emits them in the utility bucket and removes theme.content so it’s not also turned into --content-* vars.

That lines up with the comment about no longer reading from --content-* in v4 and with the runtime behavior in registerContentCompat. Two small tweaks would make this more robust and consistent with the compat path:

  1. Guard the shape of resolvedConfig.theme.content
    Right now the if ('content' in resolvedConfig.theme) check will happily accept non‑object values; Object.entries on a string, for example, will “work” but produce nonsense utilities. Mirroring the compat check would avoid that:
-    if ('content' in resolvedConfig.theme) {
-      let rules: AstNode[] = []
-
-      for (let [key, value] of Object.entries(resolvedConfig.theme.content)) {
-        rules.push(
-          atRule('@utility', `content-${key}`, [
-            decl('--tw-content', `${value}`),
-            decl('content', `var(--tw-content)`),
-          ]),
-        )
-      }
+    if (
+      'content' in resolvedConfig.theme &&
+      typeof resolvedConfig.theme.content === 'object' &&
+      resolvedConfig.theme.content !== null
+    ) {
+      let rules: AstNode[] = []
+
+      for (let [key, value] of Object.entries(resolvedConfig.theme.content)) {
+        if (typeof value !== 'string') continue
+
+        rules.push(
+          atRule('@utility', `content-${key}`, [
+            decl('--tw-content', value),
+            decl('content', 'var(--tw-content)'),
+          ]),
+        )
+      }
  1. Filter to string values only
    As shown above, filtering non‑string values matches registerContentCompat and avoids generating obviously invalid content rules from misconfigured theme values.

These are defensive improvements; the current implementation will work for the intended, string‑only theme.content use case.

Also applies to: 145-166

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af48117 and 5ef6ac9.

📒 Files selected for processing (5)
  • integrations/upgrade/js-config.test.ts (1 hunks)
  • packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts (2 hunks)
  • packages/tailwindcss/src/compat/apply-compat-hooks.ts (2 hunks)
  • packages/tailwindcss/src/compat/content-compat.test.ts (1 hunks)
  • packages/tailwindcss/src/compat/content-compat.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/tailwindcss/src/compat/content-compat.ts (2)
packages/tailwindcss/src/design-system.ts (1)
  • DesignSystem (33-65)
packages/tailwindcss/src/ast.ts (1)
  • decl (95-102)
packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts (1)
packages/tailwindcss/src/ast.ts (3)
  • AstNode (68-68)
  • atRule (78-85)
  • decl (95-102)
packages/tailwindcss/src/compat/apply-compat-hooks.ts (1)
packages/tailwindcss/src/compat/content-compat.ts (1)
  • registerContentCompat (5-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Linux
  • GitHub Check: Linux / postcss
  • GitHub Check: Linux / vite
  • GitHub Check: Linux / upgrade
  • GitHub Check: Linux / webpack
  • GitHub Check: Linux / cli
🔇 Additional comments (2)
packages/tailwindcss/src/compat/apply-compat-hooks.ts (1)

13-13: Content compat wiring aligns with existing compat hooks

Importing and invoking registerContentCompat alongside registerContainerCompat/registerScreensConfig using resolvedUserConfig keeps all JS-config‑driven compat in one place and ensures theme.content from JS configs registers content-* utilities during compat compilation. The integration matches the existing pattern and looks solid.

Also applies to: 362-362

packages/tailwindcss/src/compat/content-compat.ts (1)

1-18: Content compat helper correctly maps theme.content to content-* utilities

registerContentCompat sensibly guards theme.content’s shape, skips non‑string values, and registers content-<key> statics that set --tw-content plus content: var(--tw-content). That aligns with the migration behavior and the new tests, and integrates cleanly with the existing DesignSystem.utilities.static pattern.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/tailwindcss/src/compat/content-compat.ts (2)

8-8: Add array check to type validation.

Arrays are also objects in JavaScript, so the current check allows arrays to pass through. If theme.content is an array, Object.entries() would use numeric indices as keys, resulting in utilities like content-0, content-1, which is likely unintended.

Apply this diff:

-  if (typeof content !== 'object' || content === null) return
+  if (typeof content !== 'object' || content === null || Array.isArray(content)) return

17-23: Consider defining @property once to avoid duplication.

Each content-* utility registers the same @property --tw-content declaration. If multiple content utilities are used (e.g., content-slash and content-dot), the CSS output will contain duplicate @property blocks, causing CSS bloat.

Consider one of these approaches:

  • Define the @property globally once when registerContentCompat is called, outside the per-utility registration
  • Use a lazy initialization pattern to register the @property only on first use

This would reduce redundant CSS output while maintaining the same functionality.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef6ac9 and 74673be.

📒 Files selected for processing (4)
  • integrations/upgrade/js-config.test.ts (1 hunks)
  • packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts (2 hunks)
  • packages/tailwindcss/src/compat/content-compat.test.ts (1 hunks)
  • packages/tailwindcss/src/compat/content-compat.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/tailwindcss/src/compat/content-compat.test.ts
  • packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts
  • integrations/upgrade/js-config.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Linux
  • GitHub Check: Linux / upgrade

Comment on lines 1912 to 1924
@utility content-slash {
--tw-content: '/';
content: var(--tw-content);
}
@utility content-slashslash {
--tw-content: '//';
content: var(--tw-content);
}
@property --tw-content {
syntax: '*';
inherits: false;
initial-value: '';
}
Copy link
Member

Choose a reason for hiding this comment

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

It does expose a little bit of internal information, but I don't hate this.
As mentioned before, reading from --content could also work. Not sure what's best 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, those are our two options. The one where we read from --content is a much smaller change. So it depends on which one we want to do.

@thecrypticace
Copy link
Contributor Author

I've updated this to read from --content instead.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/tailwindcss/src/utilities.test.ts (1)

23261-23275: LGTM! Test setup validates core theme-based content functionality.

The test correctly validates the new behavior where content utility supports theme keys. The setup covers both theme-based values (content-slash) and arbitrary values (content-["hello_world"]), which aligns with the PR objectives for handling migration and backwards compatibility.

Consider adding test coverage for variants to ensure the full migration path works as expected:

// Example of additional test coverage
['after:content-slash', 'before:content-slash', 'content-slash']

This would validate that the utilities work correctly with pseudo-element variants, which was part of the original issue context.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7762cc and 2fc57c2.

📒 Files selected for processing (2)
  • packages/tailwindcss/src/utilities.test.ts (2 hunks)
  • packages/tailwindcss/src/utilities.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/tailwindcss/src/utilities.test.ts (2)
packages/tailwindcss/src/test-utils/run.ts (1)
  • compileCss (4-11)
integrations/utils.ts (1)
  • css (519-519)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Linux
  • GitHub Check: Linux / vite
  • GitHub Check: Linux / cli
  • GitHub Check: Linux / upgrade
  • GitHub Check: Linux / postcss
🔇 Additional comments (2)
packages/tailwindcss/src/utilities.ts (1)

4691-4693: LGTM! Backwards compatibility support for theme-based content values.

The change correctly enables theme resolution for the content utility to support migration from v3 configs where theme.content values were defined. The comment appropriately documents this as a backwards compatibility feature and recommends arbitrary values for new usage.

packages/tailwindcss/src/utilities.test.ts (1)

23281-23297: LGTM! Expected output correctly validates backwards compatibility.

The expected output demonstrates that:

  • Theme variable --content-slash is properly hoisted to :root, :host
  • The content-slash utility generates valid CSS using var(--content-slash) via the --tw-content variable
  • Arbitrary values like content-["hello_world"] work correctly with proper escaping and space handling
  • The @property --tw-content declaration ensures proper CSS variable behavior

This aligns perfectly with the PR objectives, ensuring that migrated content theme entries produce valid utility classes.

@thecrypticace thecrypticace changed the title Handle migration and backwards compat for content theme from JS configs Handle backwards compatibility for content theme from JS configs Nov 29, 2025
@thecrypticace thecrypticace merged commit 243615e into main Nov 29, 2025
7 checks passed
@thecrypticace thecrypticace deleted the fix/issue-19343 branch November 29, 2025 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@tailwindcss/upgrade doesn't upgrade extends.content correctly

3 participants