From 58d1fe3948fdf963d628071ff13d15147e9f712c Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Sun, 22 Feb 2026 13:02:05 +0100
Subject: [PATCH 1/5] Fix missing extracted classes in mdx files (#19711)
---
CHANGELOG.md | 1 +
.../src/extractor/pre_processors/markdown.rs | 22 ++++++++++++++++++-
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bde2966b4c0..dd549036ed0f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Allow trailing dash in functional utility names for backwards compatibility ([#19696](https://github.com/tailwindlabs/tailwindcss/pull/19696))
+- Fix missing extracted classes in mdx files containing `.` ([#19711](https://github.com/tailwindlabs/tailwindcss/pull/19711))
## [4.2.0] - 2026-02-18
diff --git a/crates/oxide/src/extractor/pre_processors/markdown.rs b/crates/oxide/src/extractor/pre_processors/markdown.rs
index d2acc6a58738..488614ee57bd 100644
--- a/crates/oxide/src/extractor/pre_processors/markdown.rs
+++ b/crates/oxide/src/extractor/pre_processors/markdown.rs
@@ -9,6 +9,7 @@ impl PreProcessor for Markdown {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
+ let mut bracket_stack = vec![];
let mut in_directive = false;
@@ -18,11 +19,17 @@ impl PreProcessor for Markdown {
result[cursor.pos] = b' ';
in_directive = true;
}
+ (true, b'(' | b'[' | b'{' | b'<') => {
+ bracket_stack.push(cursor.curr());
+ }
+ (true, b')' | b']' | b'}' | b'>') if !bracket_stack.is_empty() => {
+ bracket_stack.pop();
+ }
(true, b'}') => {
result[cursor.pos] = b' ';
in_directive = false;
}
- (true, b'.') => {
+ (true, b'.') if bracket_stack.is_empty() => {
result[cursor.pos] = b' ';
}
_ => {}
@@ -60,4 +67,17 @@ mod tests {
Markdown::test(input, expected);
}
}
+
+ #[test]
+ fn test_nested_classes_keep_the_dots() {
+ for (input, expected) in [
+ (
+ r#"{}"#,
+ r#" "#,
+ ),
+ (r#"{content-['example.js']}"#, r#" content-['example.js'] "#),
+ ] {
+ Markdown::test(input, expected);
+ }
+ }
}
From 1dce64ee7ec2e414c845b4e268ac3b9b89aaf0c8 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Mon, 23 Feb 2026 11:45:12 +0100
Subject: [PATCH 2/5] 4.2.1 (#19714)
---
CHANGELOG.md | 5 ++++-
crates/node/npm/android-arm-eabi/package.json | 2 +-
crates/node/npm/android-arm64/package.json | 2 +-
crates/node/npm/darwin-arm64/package.json | 2 +-
crates/node/npm/darwin-x64/package.json | 2 +-
crates/node/npm/freebsd-x64/package.json | 2 +-
crates/node/npm/linux-arm-gnueabihf/package.json | 2 +-
crates/node/npm/linux-arm64-gnu/package.json | 2 +-
crates/node/npm/linux-arm64-musl/package.json | 2 +-
crates/node/npm/linux-x64-gnu/package.json | 2 +-
crates/node/npm/linux-x64-musl/package.json | 2 +-
crates/node/npm/wasm32-wasi/package.json | 2 +-
crates/node/npm/win32-arm64-msvc/package.json | 2 +-
crates/node/npm/win32-x64-msvc/package.json | 2 +-
crates/node/package.json | 2 +-
packages/@tailwindcss-browser/package.json | 2 +-
packages/@tailwindcss-cli/package.json | 2 +-
packages/@tailwindcss-node/package.json | 2 +-
packages/@tailwindcss-postcss/package.json | 2 +-
packages/@tailwindcss-standalone/package.json | 2 +-
packages/@tailwindcss-upgrade/package.json | 2 +-
packages/@tailwindcss-vite/package.json | 2 +-
packages/@tailwindcss-webpack/package.json | 2 +-
packages/tailwindcss/package.json | 2 +-
24 files changed, 27 insertions(+), 24 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd549036ed0f..fa0edaacd029 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Experimental_: Add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901))
+## [4.2.1] - 2026-02-23
+
### Fixed
- Allow trailing dash in functional utility names for backwards compatibility ([#19696](https://github.com/tailwindlabs/tailwindcss/pull/19696))
@@ -3946,7 +3948,8 @@ No release notes
- Everything!
-[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v4.2.0...HEAD
+[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v4.2.1...HEAD
+[4.2.1]: https://github.com/tailwindlabs/tailwindcss/compare/v4.2.0...v4.2.1
[4.2.0]: https://github.com/tailwindlabs/tailwindcss/compare/v4.1.18...v4.2.0
[4.1.18]: https://github.com/tailwindlabs/tailwindcss/compare/v4.1.17...v4.1.18
[3.4.19]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.18...v3.4.19
diff --git a/crates/node/npm/android-arm-eabi/package.json b/crates/node/npm/android-arm-eabi/package.json
index cba6fedce0b2..34a1c58a54e0 100644
--- a/crates/node/npm/android-arm-eabi/package.json
+++ b/crates/node/npm/android-arm-eabi/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-android-arm-eabi",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/android-arm64/package.json b/crates/node/npm/android-arm64/package.json
index 0950b7a03e95..909ddf9145f4 100644
--- a/crates/node/npm/android-arm64/package.json
+++ b/crates/node/npm/android-arm64/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-android-arm64",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/darwin-arm64/package.json b/crates/node/npm/darwin-arm64/package.json
index 423333d61dcf..bfeb71630488 100644
--- a/crates/node/npm/darwin-arm64/package.json
+++ b/crates/node/npm/darwin-arm64/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-darwin-arm64",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/darwin-x64/package.json b/crates/node/npm/darwin-x64/package.json
index 81007674ee5c..bf4bfe6524e2 100644
--- a/crates/node/npm/darwin-x64/package.json
+++ b/crates/node/npm/darwin-x64/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-darwin-x64",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/freebsd-x64/package.json b/crates/node/npm/freebsd-x64/package.json
index 8c25927edc39..0619de4ad584 100644
--- a/crates/node/npm/freebsd-x64/package.json
+++ b/crates/node/npm/freebsd-x64/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-freebsd-x64",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/linux-arm-gnueabihf/package.json b/crates/node/npm/linux-arm-gnueabihf/package.json
index b2a4c0281127..cd818b2aa2da 100644
--- a/crates/node/npm/linux-arm-gnueabihf/package.json
+++ b/crates/node/npm/linux-arm-gnueabihf/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-arm-gnueabihf",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/linux-arm64-gnu/package.json b/crates/node/npm/linux-arm64-gnu/package.json
index e1855f2894c3..9dd325f38624 100644
--- a/crates/node/npm/linux-arm64-gnu/package.json
+++ b/crates/node/npm/linux-arm64-gnu/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-arm64-gnu",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/linux-arm64-musl/package.json b/crates/node/npm/linux-arm64-musl/package.json
index c6b96ab8ab14..991a9c2104b3 100644
--- a/crates/node/npm/linux-arm64-musl/package.json
+++ b/crates/node/npm/linux-arm64-musl/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-arm64-musl",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/linux-x64-gnu/package.json b/crates/node/npm/linux-x64-gnu/package.json
index 2d4fa6fe3c84..847484f1dcb5 100644
--- a/crates/node/npm/linux-x64-gnu/package.json
+++ b/crates/node/npm/linux-x64-gnu/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-x64-gnu",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/linux-x64-musl/package.json b/crates/node/npm/linux-x64-musl/package.json
index 5855b5d6a331..66acddc12fe0 100644
--- a/crates/node/npm/linux-x64-musl/package.json
+++ b/crates/node/npm/linux-x64-musl/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-x64-musl",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/wasm32-wasi/package.json b/crates/node/npm/wasm32-wasi/package.json
index 0c1defca3a43..25807493d8d4 100644
--- a/crates/node/npm/wasm32-wasi/package.json
+++ b/crates/node/npm/wasm32-wasi/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-wasm32-wasi",
- "version": "4.2.0",
+ "version": "4.2.1",
"cpu": [
"wasm32"
],
diff --git a/crates/node/npm/win32-arm64-msvc/package.json b/crates/node/npm/win32-arm64-msvc/package.json
index 633a4d12eb9c..2dca490b5005 100644
--- a/crates/node/npm/win32-arm64-msvc/package.json
+++ b/crates/node/npm/win32-arm64-msvc/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-win32-arm64-msvc",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/npm/win32-x64-msvc/package.json b/crates/node/npm/win32-x64-msvc/package.json
index 99dca35d18c4..ddcf1802662e 100644
--- a/crates/node/npm/win32-x64-msvc/package.json
+++ b/crates/node/npm/win32-x64-msvc/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-win32-x64-msvc",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/crates/node/package.json b/crates/node/package.json
index 6da669ced184..f16920029f84 100644
--- a/crates/node/package.json
+++ b/crates/node/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide",
- "version": "4.2.0",
+ "version": "4.2.1",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
diff --git a/packages/@tailwindcss-browser/package.json b/packages/@tailwindcss-browser/package.json
index 8075cfb3fc0c..12442e87e66b 100644
--- a/packages/@tailwindcss-browser/package.json
+++ b/packages/@tailwindcss-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/browser",
- "version": "4.2.0",
+ "version": "4.2.1",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"main": "./dist/index.global.js",
diff --git a/packages/@tailwindcss-cli/package.json b/packages/@tailwindcss-cli/package.json
index af488a701caa..2a91542f1059 100644
--- a/packages/@tailwindcss-cli/package.json
+++ b/packages/@tailwindcss-cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/cli",
- "version": "4.2.0",
+ "version": "4.2.1",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"repository": {
diff --git a/packages/@tailwindcss-node/package.json b/packages/@tailwindcss-node/package.json
index 064b53f802a7..c4d7d48739ad 100644
--- a/packages/@tailwindcss-node/package.json
+++ b/packages/@tailwindcss-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/node",
- "version": "4.2.0",
+ "version": "4.2.1",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"repository": {
diff --git a/packages/@tailwindcss-postcss/package.json b/packages/@tailwindcss-postcss/package.json
index 5180133e8f27..6052a08b070b 100644
--- a/packages/@tailwindcss-postcss/package.json
+++ b/packages/@tailwindcss-postcss/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/postcss",
- "version": "4.2.0",
+ "version": "4.2.1",
"description": "PostCSS plugin for Tailwind CSS, a utility-first CSS framework for rapidly building custom user interfaces",
"license": "MIT",
"repository": {
diff --git a/packages/@tailwindcss-standalone/package.json b/packages/@tailwindcss-standalone/package.json
index 50039f29a459..b25d5bb9e04b 100644
--- a/packages/@tailwindcss-standalone/package.json
+++ b/packages/@tailwindcss-standalone/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/standalone",
- "version": "4.2.0",
+ "version": "4.2.1",
"private": true,
"description": "Standalone CLI for Tailwind CSS",
"license": "MIT",
diff --git a/packages/@tailwindcss-upgrade/package.json b/packages/@tailwindcss-upgrade/package.json
index b8191e23c58e..cf9de79d6a11 100644
--- a/packages/@tailwindcss-upgrade/package.json
+++ b/packages/@tailwindcss-upgrade/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/upgrade",
- "version": "4.2.0",
+ "version": "4.2.1",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"repository": {
diff --git a/packages/@tailwindcss-vite/package.json b/packages/@tailwindcss-vite/package.json
index db0297fc215a..24fac1bd46c6 100644
--- a/packages/@tailwindcss-vite/package.json
+++ b/packages/@tailwindcss-vite/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/vite",
- "version": "4.2.0",
+ "version": "4.2.1",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"repository": {
diff --git a/packages/@tailwindcss-webpack/package.json b/packages/@tailwindcss-webpack/package.json
index 0fcf0d3f7b0c..ed9206a9e7c0 100644
--- a/packages/@tailwindcss-webpack/package.json
+++ b/packages/@tailwindcss-webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@tailwindcss/webpack",
- "version": "4.2.0",
+ "version": "4.2.1",
"description": "A webpack loader for Tailwind CSS v4.",
"license": "MIT",
"repository": {
diff --git a/packages/tailwindcss/package.json b/packages/tailwindcss/package.json
index abe6da365387..f98c80152d65 100644
--- a/packages/tailwindcss/package.json
+++ b/packages/tailwindcss/package.json
@@ -1,6 +1,6 @@
{
"name": "tailwindcss",
- "version": "4.2.0",
+ "version": "4.2.1",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"repository": {
From 097f982d7a112dd9efa9bfc6785d222afd9f4bb2 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Mon, 23 Feb 2026 13:46:49 +0100
Subject: [PATCH 3/5] update changelog
---
CHANGELOG.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa0edaacd029..a2359f1812b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Allow trailing dash in functional utility names for backwards compatibility ([#19696](https://github.com/tailwindlabs/tailwindcss/pull/19696))
-- Fix missing extracted classes in mdx files containing `.` ([#19711](https://github.com/tailwindlabs/tailwindcss/pull/19711))
+- Properly detect classes containing `.` characters within curly braces in MDX files ([#19711](https://github.com/tailwindlabs/tailwindcss/pull/19711))
## [4.2.0] - 2026-02-18
@@ -32,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `inline-*`, `min-inline-*`, `max-inline-*` utilities for `inline-size`, `min-inline-size`, and `max-inline-size` ([#19612](https://github.com/tailwindlabs/tailwindcss/pull/19612))
- Add `block-*`, `min-block-*`, `max-block-*` utilities for `block-size`, `min-block-size`, and `max-block-size` ([#19612](https://github.com/tailwindlabs/tailwindcss/pull/19612))
- Add `inset-s-*`, `inset-e-*`, `inset-bs-*`, `inset-be-*` utilities for `inset-inline-start`, `inset-inline-end`, `inset-block-start`, and `inset-block-end` ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613))
-- Add `font-features-*` utility for `font-feature-settings` ([#19623](https://github.com/tailwindlabs/tailwindcss/pull/19615))
+- Add `font-features-*` utility for `font-feature-settings` ([#19623](https://github.com/tailwindlabs/tailwindcss/pull/19623))
### Fixed
From 9ded4a23de06fb7e8cdc34a3bdf9318e7e8d2bbc Mon Sep 17 00:00:00 2001
From: Adam Wathan
Date: Wed, 25 Feb 2026 10:16:09 -0500
Subject: [PATCH 4/5] Guard object lookups against inherited prototype
properties (#19725)
When user-controlled candidate values like "constructor" are used as
keys to look up values in plain objects (staticValues, plugin values,
modifiers, config), they can match inherited Object.prototype properties
instead of returning undefined. This caused crashes like "V.map is not
a function" when scanning source files containing strings like
"row-constructor".
Use Object.hasOwn() checks before all user-keyed object lookups in:
- utilities.ts (staticValues lookup)
- plugin-api.ts (values, modifiers, and variant values lookups)
- plugin-functions.ts (get() config traversal function)
Fixes #19721
https://claude.ai/code/session_011CYSGw3DLh2Z8xnuyoaCgC
---------
Co-authored-by: Claude
Co-authored-by: Robin Malfait
---
CHANGELOG.md | 4 ++
.../tailwindcss/src/compat/plugin-api.test.ts | 44 +++++++++++++++++++
packages/tailwindcss/src/compat/plugin-api.ts | 22 +++++++---
.../src/compat/plugin-functions.ts | 11 ++---
packages/tailwindcss/src/utilities.test.ts | 7 +++
packages/tailwindcss/src/utilities.ts | 2 +
6 files changed, 78 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2359f1812b0..96297b4d449d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Experimental_: Add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901))
+### Fixed
+
+- Guard object lookups against inherited prototype properties ([#19725](https://github.com/tailwindlabs/tailwindcss/pull/19725))
+
## [4.2.1] - 2026-02-23
### Fixed
diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts
index 4e35afa5a84e..cc410995b442 100644
--- a/packages/tailwindcss/src/compat/plugin-api.test.ts
+++ b/packages/tailwindcss/src/compat/plugin-api.test.ts
@@ -4592,4 +4592,48 @@ describe('config()', () => {
expect(fn).toHaveBeenCalledWith('defaultvalue')
})
+
+ // https://github.com/tailwindlabs/tailwindcss/issues/19721
+ test('matchUtilities does not match Object.prototype properties as values', async ({
+ expect,
+ }) => {
+ let input = css`
+ @tailwind utilities;
+ @plugin "my-plugin";
+ `
+
+ let compiler = await compile(input, {
+ loadModule: async (id, base) => {
+ return {
+ path: '',
+ base,
+ module: plugin(function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value) => ({ '--test': value }),
+ },
+ {
+ values: {
+ foo: 'bar',
+ },
+ },
+ )
+ }),
+ }
+ },
+ })
+
+ // These should not crash or produce output
+ expect(
+ optimizeCss(
+ compiler.build([
+ 'test-constructor',
+ 'test-hasOwnProperty',
+ 'test-toString',
+ 'test-valueOf',
+ 'test-__proto__',
+ ]),
+ ).trim(),
+ ).toEqual('')
+ })
})
diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts
index 5954040017d2..af6e98d770ff 100644
--- a/packages/tailwindcss/src/compat/plugin-api.ts
+++ b/packages/tailwindcss/src/compat/plugin-api.ts
@@ -202,6 +202,9 @@ export function buildPluginApi({
ruleNodes.nodes,
)
} else if (variant.value.kind === 'named' && options?.values) {
+ if (!Object.hasOwn(options.values, variant.value.value)) {
+ return null
+ }
let defaultValue = options.values[variant.value.value]
if (typeof defaultValue !== 'string') {
return null
@@ -223,8 +226,14 @@ export function buildPluginApi({
let aValueKey = a.value ? a.value.value : 'DEFAULT'
let zValueKey = z.value ? z.value.value : 'DEFAULT'
- let aValue = options?.values?.[aValueKey] ?? aValueKey
- let zValue = options?.values?.[zValueKey] ?? zValueKey
+ let aValue =
+ (options?.values && Object.hasOwn(options.values, aValueKey)
+ ? options.values[aValueKey]
+ : undefined) ?? aValueKey
+ let zValue =
+ (options?.values && Object.hasOwn(options.values, zValueKey)
+ ? options.values[zValueKey]
+ : undefined) ?? zValueKey
if (options && typeof options.sort === 'function') {
return options.sort(
@@ -406,10 +415,13 @@ export function buildPluginApi({
value = values.DEFAULT ?? null
} else if (candidate.value.kind === 'arbitrary') {
value = candidate.value.value
- } else if (candidate.value.fraction && values[candidate.value.fraction]) {
+ } else if (
+ candidate.value.fraction &&
+ Object.hasOwn(values, candidate.value.fraction)
+ ) {
value = values[candidate.value.fraction]
ignoreModifier = true
- } else if (values[candidate.value.value]) {
+ } else if (Object.hasOwn(values, candidate.value.value)) {
value = values[candidate.value.value]
} else if (values.__BARE_VALUE__) {
value = values.__BARE_VALUE__(candidate.value) ?? null
@@ -430,7 +442,7 @@ export function buildPluginApi({
modifier = null
} else if (modifiers === 'any' || candidate.modifier.kind === 'arbitrary') {
modifier = candidate.modifier.value
- } else if (modifiers?.[candidate.modifier.value]) {
+ } else if (modifiers && Object.hasOwn(modifiers, candidate.modifier.value)) {
modifier = modifiers[candidate.modifier.value]
} else if (isColor && !Number.isNaN(Number(candidate.modifier.value))) {
modifier = `${candidate.modifier.value}%`
diff --git a/packages/tailwindcss/src/compat/plugin-functions.ts b/packages/tailwindcss/src/compat/plugin-functions.ts
index f311ad1e8c43..40b8c93e89f1 100644
--- a/packages/tailwindcss/src/compat/plugin-functions.ts
+++ b/packages/tailwindcss/src/compat/plugin-functions.ts
@@ -223,8 +223,10 @@ function get(obj: any, path: string[]) {
for (let i = 0; i < path.length; ++i) {
let key = path[i]
- // The key does not exist so concatenate it with the next key
- if (obj?.[key] === undefined) {
+ // The key does not exist so concatenate it with the next key.
+ // We use Object.hasOwn to avoid matching inherited prototype properties
+ // (e.g. "constructor", "toString") when traversing config objects.
+ if (obj === null || obj === undefined || typeof obj !== 'object' || !Object.hasOwn(obj, key)) {
if (path[i + 1] === undefined) {
return undefined
}
@@ -233,11 +235,6 @@ function get(obj: any, path: string[]) {
continue
}
- // We never want to index into strings
- if (typeof obj === 'string') {
- return undefined
- }
-
obj = obj[key]
}
diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts
index 360acd9e29a6..e562cd3be15a 100644
--- a/packages/tailwindcss/src/utilities.test.ts
+++ b/packages/tailwindcss/src/utilities.test.ts
@@ -1646,6 +1646,13 @@ test('row', async () => {
'row-span-full/foo',
'row-[span_123/span_123]/foo',
'row-span-[var(--my-variable)]/foo',
+
+ // Candidates matching Object.prototype properties should not crash or
+ // produce output (see: https://github.com/tailwindlabs/tailwindcss/issues/19721)
+ 'row-constructor',
+ 'row-hasOwnProperty',
+ 'row-toString',
+ 'row-valueOf',
]),
).toEqual('')
diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts
index aa2187887405..62e4510b7309 100644
--- a/packages/tailwindcss/src/utilities.ts
+++ b/packages/tailwindcss/src/utilities.ts
@@ -391,6 +391,8 @@ export function createUtilities(theme: Theme) {
* user's theme.
*/
function functionalUtility(classRoot: string, desc: UtilityDescription) {
+ if (desc.staticValues) desc.staticValues = Object.assign(Object.create(null), desc.staticValues)
+
function handleFunctionalUtility({ negative }: { negative: boolean }) {
return (candidate: Extract) => {
let value: string | null = null
From bf2e2fe08aaf001e7a5dc0ba9872e95c2dbb2d64 Mon Sep 17 00:00:00 2001
From: Robin Malfait
Date: Thu, 26 Feb 2026 12:23:50 +0100
Subject: [PATCH 5/5] Extract classes from interpolated expressions in Ruby
(#19730)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR ensures that interpolated expressions in Ruby syntax are
correctly extracted.
The issue was that we ignore comments in Ruby syntax (which start with
`#`). We already made an exception for locals (`<%# locals: … %>`), but
we also need to handle interpolated expressions (`#{ … }`) in the same
way because they are not comments.
Fixes: #19728
## Test plan
1. Existing tests pass
2. Added a regression test for this scenario
3. Tested using the extractor on the given code snippet:
Notice that the `w-100` gets extracted now.
---
.../oxide/src/extractor/pre_processors/ruby.rs | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/crates/oxide/src/extractor/pre_processors/ruby.rs b/crates/oxide/src/extractor/pre_processors/ruby.rs
index 1f2221414ff4..5a3a0fabbfc7 100644
--- a/crates/oxide/src/extractor/pre_processors/ruby.rs
+++ b/crates/oxide/src/extractor/pre_processors/ruby.rs
@@ -124,7 +124,9 @@ impl PreProcessor for Ruby {
// Except for strict locals, these are defined in a `<%# locals: … %>`. Checking if
// the comment is preceded by a `%` should be enough without having to perform more
// parsing logic. Worst case we _do_ scan a few comments.
- b'#' if !matches!(cursor.prev(), b'%') => {
+ //
+ // We also want to skip interpolation syntax, which look like `#{…}`.
+ b'#' if !matches!(cursor.prev(), b'%') && !matches!(cursor.next(), b'{') => {
result[cursor.pos] = b' ';
cursor.advance();
@@ -388,6 +390,20 @@ mod tests {
Ruby::test_extract_contains(input, vec!["z-1", "z-2", "z-3"]);
}
+ // https://github.com/tailwindlabs/tailwindcss/issues/19728
+ #[test]
+ fn test_interpolated_expressions() {
+ let input = r#"
+ def width_class(width = nil)
+ <<~STYLE_CLASS
+ #{width || 'w-100'}
+ STYLE_CLASS
+ end
+ "#;
+
+ Ruby::test_extract_contains(input, vec!["w-100"]);
+ }
+
// https://github.com/tailwindlabs/tailwindcss/issues/19239
#[test]
fn test_skip_comments() {