From ef296579a68f8f8d0600451f4644de9ff2aa4ffb Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 15 Oct 2026 15:45:39 +0800 Subject: [PATCH 1/6] feat: Support @property in stylesheets 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. --- README.md | 3 +- crates/layout/src/css.rs | 20 ++++++- layout.wasm | Bin 520180 -> 520763 bytes src/handler/stylesheet.ts | 118 ++++++++++++++++++++++++++++++++++---- test/stylesheet.test.tsx | 60 +++++++++++++++++++ 5 files changed, 187 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5fdf919c..4336afaa 100644 --- a/README.md +++ b/README.md @@ -518,10 +518,11 @@ Note: #### Stylesheets -The CSS of ` + , + { width: 200, height: 100, fonts } + ) + + it('should render the value or the placeholder of text inputs', async () => { + expect(await render()).not.toBe( + await render() + ) + // Placeholders are gray, or styled with `::placeholder`. + expect(await render()).toBe( + await render() + ) + expect( + await render( + , + 'input::placeholder { color: red }' + ) + ).toBe(await render()) + expect(await render()).toBe( + await render() + ) + expect(await render()).toBe( + await render() + ) + // Other inputs have no content. + expect(await render()).toBe( + await render() + ) + }) + + it('should size inputs like browsers', async () => { + const boxes: Record = {} + await satori( +
+
+ +
+
+ +
+
, + { + width: 400, + height: 100, + fonts, + onNodeDetected: (node) => { + if (node.type === 'input') boxes[node.props.id] = node + expect(Object.keys(node.props)).not.toContain('__satoriPlaceholder') + }, + } + ) + // `size` characters wide, 20 by default. + expect(boxes.default.width).toBeGreaterThan(100) + expect(boxes.size.width).toBeCloseTo(boxes.default.width / 2, -1) + // Percentages of heights are of the definite height of the container. + expect(boxes.default.height).toBe(40) + }) + + it('should render styled inputs', async () => { + const svg = await satori( +
+ + +
, + { width: 200, height: 100, fonts } + ) + expect(await toImage(svg, 200)).toMatchImageSnapshot() + }) +}) From b88f5844f4ca2edd84591da0bdd0ade1315d236e Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 15 Oct 2026 15:56:00 +0800 Subject: [PATCH 4/6] feat: Support container units 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. --- README.md | 2 +- src/handler/stylesheet.ts | 119 ++++++++++++++++++++++++++++++-------- src/utils.ts | 8 +++ test/stylesheet.test.tsx | 35 +++++++++++ test/units.test.tsx | 13 +++++ 5 files changed, 152 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 100ea01a..9ac208f1 100644 --- a/README.md +++ b/README.md @@ -521,7 +521,7 @@ Note: The CSS of `