Repository navigation
feat: Support container units - #926
Merged
Merged
Conversation
Custom properties registered with `@property` have their initial value, and aren't inherited if `inherits` is false, like in browsers. Tailwind v4 registers its variables this way, e.g. `--tw-translate-x` with the initial value `0`, so utilities like `-translate-y-1/2` were dropped. Custom properties are inherited in the layout, so the initial values are declared on the root, and on elements whose parent declares another value of a property that isn't inherited.
`ch` was half of the font size, but it's the width of `0` in the font, which is often wider: 0.67em in Geist. A `max-width: 42ch` paragraph of nextjs.org was 378px instead of 508px, so it wrapped into more lines. The width is measured once per font, and stored as a ratio to the font size in the style. Like `em`, an element's own lengths use the ratio of the font it inherits.
Like in browsers, text inputs show their value, or their placeholder in the color of `::placeholder`, on a line that's vertically centered and clipped. By default, the line is `size` average characters of the font wide. Form controls are `inline-block`, like in Chrome. Percentages of heights of atomic inlines are now of the height of the container if it's a length, e.g. an input with `height: 100%`.
Container units like `cqw` in stylesheets are resolved with the size of the nearest query container whose type allows their axis, and like in browsers, of the viewport without one. Trees that use them are laid out twice, like with container queries. Without stylesheets, there are no query containers, so they're of the viewport. Declarations with them are validated as lengths, so they're not dropped like unknown units anymore.
shuding
added this pull request to stack #924
October 15, 2026 07:56
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
shuding
marked this pull request as ready for review
October 15, 2026 07:57
# Conflicts: # src/font.ts # src/handler/stylesheet.ts # src/layout.ts
# Conflicts: # src/handler/stylesheet.ts
The regular expression that found container units backtracked cubically on long numbers without a container unit, e.g. a quoted font name of 4,000 digits took about 17 seconds to validate. Values are now scanned once, like by a CSS tokenizer, which also skips strings and identifiers, and only when they have container units.
shuding
added a commit
that referenced
this pull request
Oct 15, 2026
While fixing the security finding on #926, I checked other regular expressions for the same problem. 15 of them, used for lengths, angles, percentages, image sizes and grid tracks, matched numbers with `\d+\.?\d*` or `\d*\.?\d+`. These can split a run of digits in many ways, so on a long number followed by something they don't expect, they backtrack through all of them. They're anchored, so this is quadratic rather than cubic, but a value of 32,000 digits followed by `!`, in a stylesheet or an inline style, took about 7 seconds to render. The numbers are now matched with: - `\d+(?:\.\d*)?` instead of `\d+\.?\d*` - `(?:\d+(?:\.\d+)?|\.\d+)` instead of `\d*\.?\d+` They match exactly the same values, with the same captures, but the digits before the dot can only be matched one way, so failing matches backtrack in linear time. - I compared each old and new expression on 30,000 random inputs each, including the captures, with no differences, and checked that the new ones take under 50ms on 50,000-digit failing inputs. - A new test renders such values in a stylesheet and inline styles. It took 6.8 seconds before, and takes about 30ms now.
# Conflicts: # test/units.test.tsx
|
🎉 This PR is included in version 0.60.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Container units like
cqwwere unknown units, so declarations with them were dropped with a style error, likeborder-radius: 1.5cqwon the course cards of nextjs.org.Now they're resolved in stylesheets, like in browsers:
cqwandcqiare of the width of the nearest query container,cqhandcqbof the height of the nearestsizecontainer, andcqminandcqmaxthe smaller and larger of them. A pseudo-element can use its element as its container.cascade()andrebuild()now take a context object instead of more and more parameters.New tests cover the units with
inline-sizeandsizecontainers, the fallback to the viewport, pseudo-elements and inline styles. They fail without this change.The cards' radius is now 18.48px like in Chrome, and
nextjs-learn-courseshas no style errors. Its content mismatch stays at 2.19%, as the corners only change a few pixels.Linear-time scanning
A security review found that the first version's regular expression for container units backtracked cubically on long numbers without a container unit: a quoted font name of 4,000 digits took about 17 seconds to validate, blocking the render. Values are now scanned once by a small tokenizer, which also skips strings and identifiers, and only when a quick check finds a container unit. A regression test with 4,000-digit values took 16.5 seconds before and takes about 30ms now.