tailwindcss icon indicating copy to clipboard operation
tailwindcss copied to clipboard

[v4] Gradient percentage range not working anymore (e.g. via-[percentage:10%_90%])

Open ramrami opened this issue 9 months ago • 4 comments

What version of Tailwind CSS are you using? v4.0.7

What build tool (or framework if it abstracts the build tool) are you using? gulp v5.0.0 + Postcss

What version of Node.js are you using? v20.18.1

What browser are you using? Brave + Safari

What operating system are you using? macOS

Reproduction URL https://play.tailwindcss.com/16SCposb3z

Describe your issue

Using the following class doesn't give the desired result in v4: bg-gradient-to-r from-sky-500 to-sky-500 via-rose-500 via-[percentage:10%_90%]

v3

Image

v4

Image

The browser considers the property as invalid because it's not a <length-percentage>:

Image

ramrami avatar Feb 20 '25 08:02 ramrami

That's definitely a "pick your poison" kind of situation. If we change the type of the --tw-gradient-via-position variable, you won't be able to animate it anymore but otherwise you can only add one percentage value here. 🤔

A temporary solution for you might be to use arbitrary values instead: https://play.tailwindcss.com/J11BlY9xhK

bg-[linear-gradient(to_right,var(--color-sky-500)_0%,var(--color-rose-500)_10%,var(--color-rose-500)_90%,_var(--color-sky-500)_100%)]

philipp-spiess avatar Feb 21 '25 10:02 philipp-spiess

Yeah, that makes sense.

If I may suggest, maybe we could split this variable in two? since every color stop has one or two positions, we could use --tw-gradient-via-start-position and --tw-gradient-via-end-position

ramrami avatar Feb 21 '25 11:02 ramrami

Nevermind my previous suggestion, just remembered that @property vars need a default value, so it probably won't work because you can't leave the values blank like in v3 🤔

Edit: see #15910

ramrami avatar Feb 21 '25 11:02 ramrami

Hi, inear-gradient(to right, var(--tw-gradient-from), var(--tw-gradient-to), var(--tw-gradient-stops)); where --tw-gradient-stops would effectively be var(--tw-gradient-from), #f43f5e 10%, #f43f5e 90%, var(--tw-gradient-to). (Note: The actual CSS variables and structure might differ in v4, but the intent is for the rose-500 color to start at 10% and end at 90%). HTML Code

In v3 this element showed sky-500 from 0-10%, then rose-500 from 10-90%, then sky-500 from 90-100%.

In v4.0.7 the via-[percentage:10%_90%] seems to be ignored or misinterpreted.

// tailwind.config.js (likely default for this issue) module.exports = { content: ["./src/**/*.{html,js}"], // Or wherever the HTML content is theme: { extend: { // No special extensions needed to demonstrate this bug }, }, plugins: [], }; /* Simplified conceptual output */ .from-sky-500 { --tw-gradient-from: #0ea5e9 var(--tw-gradient-from-position); /* sky-500 */ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); } .to-sky-500 { --tw-gradient-to: #0ea5e9 var(--tw-gradient-to-position); /* sky-500 */ /* --tw-gradient-stops is updated by from-* and to-* as well */ } .via-rose-500 { /* This class sets the color to be used by the via stops */ /* It also provides a default via position if not overridden */ --tw-gradient-via-color: #f43f5e; /* rose-500 */ }

/* The class generated from the arbitrary value: .via-[percentage:10%_90%] / / THIS IS THE CRITICAL PART THAT IS LIKELY BROKEN OR MISGENERATED IN v4.0.7 / .via-[percentage:10%_90%] { / In a working version, this class would modify --tw-gradient-stops or a specific via-stop variable to include the ranged color stop: / --tw-gradient-stops: var(--tw-gradient-from, transparent), var(--tw-gradient-via-color) 10%, / rose-500 at 10% / var(--tw-gradient-via-color) 90%, / rose-500 at 90% */ var(--tw-gradient-to, transparent); }

.bg-gradient-to-r { background-image: linear-gradient(to right, var(--tw-gradient-stops)); } // Highly simplified pseudo-code representing the internal logic that might be faulty:

function parseArbitraryGradientViaPosition(value) { // value is "percentage:10%_90%" if (value.startsWith("percentage:")) { const rangeString = value.substring("percentage:".length); // "10%90%" const positions = rangeString.split(''); // Should be ["10%", "90%"]

    // HYPOTHETICAL BUG: Maybe it's only taking positions[0] or
    // failing if positions.length > 1 in some new v4 logic path.
    if (positions.length === 2 && isValidPercentage(positions[0]) && isValidPercentage(positions[1])) {
        return { type: "range", start: positions[0], end: positions[1] }; // Expected correct parsing
    } else if (positions.length === 1 && isValidPercentage(positions[0])) {
        return { type: "single", position: positions[0] }; // For via-[percentage:50%]
    }
}
return null; // Or throws an error / fallback

}

function generateGradientStopCss(viaColorToken, parsedPosition) { if (parsedPosition && parsedPosition.type === "range") { // CORRECT LOGIC: // return ${viaColorToken} ${parsedPosition.start}, ${viaColorToken} ${parsedPosition.end}; // BUGGY LOGIC MIGHT BE: // return ${viaColorToken} ${parsedPosition.start}; // Forgetting the end part and the second stop // OR mis-concatenating them, or not understanding the "range" type from the parser. } else if (parsedPosition && parsedPosition.type === "single") { // return ${viaColorToken} ${parsedPosition.position}; } return ""; // Fallback or error }

// ... somewhere in the engine, when processing .via-[percentage:10%_90%] in conjunction with .via-rose-500: // const viaColor = resolveColorToken("rose-500"); // -> e.g., "#f43f5e" // const arbitraryValueContent = "percentage:10%_90%"; // const parsedViaPosition = parseArbitraryGradientViaPosition(arbitraryValueContent); // const viaCssStops = generateGradientStopCss(viaColor, parsedViaPosition); // --> This viaCssStops would then be used to set --tw-gradient-stops or a similar variable.

Good luck!

SUMAN SUHAG

sumansuhag avatar May 13 '25 16:05 sumansuhag

Hey, as you've pointed out @property does require a default value (when syntax isn't *). Given that we want these values to be animatable (and explicitly designed stuff around making this possible for v4), I think it's best to close this as "working as intended".

Using a arbitrary value like @philipp-spiess suggested seems like a reasonable workaround.

thecrypticace avatar Nov 07 '25 20:11 thecrypticace