[types] Validate CSSProperties against the CSS grammar, Baseline, and Flow - #1883
Draft
henryqdineen wants to merge 1 commit into
Draft
henryqdineen wants to merge 1 commit into
henryqdineen wants to merge 1 commit into
Conversation
This was referenced Sep 11, 2026
workflow: benchmarks/perfComparison of performance test results, measured in operations per second. Larger is better.
|
henryqdineen
added this pull request to stack #1887
September 11, 2026 03:13
workflow: benchmarks/sizeComparison of minified (terser) and compressed (brotli) size results, measured in bytes. Smaller is better.
|
Five things about `CSSProperties` are hand-maintained and can drift silently.
`StyleXCSSTypes.d.ts` duplicates `StyleXCSSTypes.js` so that TypeScript can use
`(string & {})` where Flow only has `string`. Nothing stops the two files
diverging, so the first test reads both, reduces each property to the keywords,
numbers and strings it accepts, and asserts the two agree. `string & {}` reads
as an arbitrary string, which is exactly what it is.
`@babel/parser` reads both dialects -- the Flow types with the `flow` plugin,
the definitions with `typescript` in declaration mode -- so one reader covers
both files. The two ASTs use disjoint node names, so a single switch handles
either.
The CSS-wide keywords are valid on every property and appear in no grammar, so
the second test is the only thing that can check them. Properties compose them
from the `all` alias.
The property values are written by hand, so `css-tree`'s value definition
grammar checks them in both directions. The third test asserts every keyword in
the types is one the grammar accepts, catching typos and invented values. The
fourth asserts the reverse for closed unions -- that every keyword the grammar
allows is typed -- catching values a user simply cannot write. Both follow
`<'other-property'>` references, which is how logical properties and shorthands
are defined.
The reverse check is restricted to closed unions on purpose. Where a property
accepts an arbitrary string, every keyword is already accepted and completeness
means nothing; asserting it everywhere demands over ten thousand additions,
because shorthands like `background` transitively pull in every named colour.
Restricted, it finds 125 real omissions across 33 properties.
The set of properties is also hand-maintained, so the fifth test asserts every
Baseline-available property is typed. `web-features` is the data behind MDN's
"Widely available" / "Newly available" banners; css-tree carries no support
data at all, and mdn-data's `status` describes the spec rather than browsers --
it calls `font-stretch` obsolete though every browser has supported it since
2020, while marking its replacement `font-width` experimental. Using Baseline
rather than "css-tree knows about it" keeps the exclusion list to real
decisions: 25 entries rather than 82.
All five pass as-is. Each list entry carries a reason, and each failure says
what to do about it:
- 6 properties accepting none of the CSS-wide keywords, written
`null | 'a' | 'b'` rather than composing `all`. They are also the only
properties in the file with no named type alias -- inlining the union at the
property is what hid the slip.
- 17 properties css-tree has no grammar for: abandoned drafts (`motion-*` became
`offset-*`, the CSS Display Level 3 longhands were dropped), `@font-face`
descriptors that were never properties (`src`, `unicode-range`), and names
that never shipped unprefixed.
- 18 properties keeping a keyword the grammar has dropped, where real
stylesheets still use it. `marginTrim` is the clearest: the spec was rewritten
to `none | in-flow | all`, but Safari ships the original `block`/`inline`
syntax.
- 33 properties whose closed union is missing keywords the grammar allows --
`display` cannot take `flow` or `table-caption`, `overflow` cannot take
`overlay`, `mixBlendMode` cannot take `plus-darker`. `marginTrim` appears in
both keyword lists, which is honest: it keeps values the spec dropped and is
missing values the spec added.
- 25 Baseline-available properties that are not typed: 7 that should be, 17 SVG
presentation attributes, and `all`.
`web-features` is pinned exactly rather than by range, because its whole
purpose is to change over time: a floating version would let a routine install
flip a property to Baseline and fail the build with no code change. Pinned, the
test only fires on a deliberate bump -- which is when the news is wanted.
Verified by injecting each failure: a typo'd keyword trips the grammar and
parity tests and is reported by name; editing one type file without the other
trips only parity; removing an allowlist entry reports what to add, and
satisfying one reports the entry as stale.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
henryqdineen
force-pushed
the
hqd-stylex-css-types-validate
branch
from
September 11, 2026 03:15
8b7f11f to
95bf702
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
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.
What changed / motivation ?
Stacked on #1882, which made
StyleXCSSTypes.d.tsa hand-maintained duplicate of the Flow types.CSSPropertiesis 522 properties and roughly 2,500 keywords, all written by hand, and nothing checks it against CSS. At that size review cannot catch a keyword that is misspelled, one that was never in any spec, or one that is missing — and after #1882 there are two copies that can drift apart.CSS is machine-readable where it matters: the value definition grammar states what each property accepts, and Baseline states what is safe to use. These tests ask those sources rather than asking a reviewer to know CSS, so the types become checkable instead of trusted.
Deliberately validating, not generating. The types stay hand-written and hand-editable; the tests only report where they disagree with CSS. Where they intentionally differ — browsers and specs disagree too — it goes in an allowlist with a reason. Today a missing keyword and a deliberate omission look identical; afterwards they do not. That turns out to be 38 decisions and 66 gaps, all currently indistinguishable from each other and from correct code.
The six tests
initial,inherit,unset,revert,revert-layer) — valid everywhere, in no grammar, so nothing else can check them.urlvalue forcursorproperty #1463.Built so an agent can close the loop
Each allowlist entry is work still to do, and each failure names the property, prints its grammar or Baseline status, and states the two valid fixes. Remove an entry, run the test, read the failure, make the change, re-run — no CSS knowledge required beyond the output.
That is deliberate, because what remains is bulk cleanup rather than design. I had a small model do three of them unattended from the test output alone and it got them right. #1886 is five worked examples; what makes them trustworthy is not care in writing them but that the tests check each against the grammar and confirm both files still agree.
Linked PR/Issues
Stacked on #1882. Test 5 surfaces #1463, fixed in #1885.
Additional Context
The allowlists are two different things, and it is worth separating them. Some entries are decisions that should stay, because specs and browsers disagree and StyleX sides with browsers. The rest are a to-do list.
Deliberate deviations (38) — expected to stay:
KEPT_KEYWORDSmarginTrimis the clearest: the spec was rewritten tonone | in-flow | all, but Safari ships the originalblock/inlineNOT_TYPEDfill,strokeandstrokeWidth, so excluding the rest is arbitrary — butx,y,dandrread oddly as style keysNOT_TYPEDall, which resets every property at once and cannot work with styleq's key-based override modelNO_GRAMMARWebkitBoxOrient, which css-tree simply lacks but the-webkit-line-clampidiom needsKnown gaps (66) — expected to reach zero:
INCOMPLETE_UNIONSdisplaycannot takeflowortable-caption,overflowcannot takeoverlayNO_GRAMMARmotion-*becameoffset-*),@font-facedescriptors that were never properties (src,unicode-range)CLOSED_DESPITE_GRAMMARcolorSchemeandfontStyleaccept neither strings nor numbers; the rest accept a bare number, so onlycalc(),var()and units are out of reachNOT_TYPEDwhiteSpaceCollapseis literally commented out inStyleXCSSTypes.jsawaiting thismarginTrimappears in both halves, which is honest: it keeps values the spec dropped and is missing values the spec added.Scoping test 4 is what makes it useful: restricted to closed unions it finds 125 real omissions across 32 properties, where unrestricted it demands over ten thousand additions, because shorthands like
backgroundtransitively pull in every named colour.Two new devDependencies, both test-only.
css-treesupplies the grammar and its parser — a patched fork ofmdn-data, and the same parsercsstypeuses.web-featuressupplies Baseline;mdn-data'sstatusfield looks like it would do instead, but it describes the spec rather than browsers, callingfont-stretchobsolete though every browser has supported it since 2020 while marking its replacementfont-widthexperimental. It is pinned exactly so that a version bump lands inpackage.jsonrather than as a lockfile line inside Dependabot's weekly grouped PR — worth knowing, because when it moves, test 6 can legitimately fail: a property reaching Baseline is the news it exists to deliver.Why not codegen, or
csstype. Generating these types from the grammar was the first thing I tried; it worked, but produced a ~4,000-line diff, replaced a file people read with generated output, and put css-tree in the build path of a public API — with no existing codegen in the repo to follow.csstypeis spec-current in ways that clash: it would break roughly 24 of the deviations above, and wrapping it for StyleX's semantics (nullto unset, bare numbers meaning pixels,theme, comma-list arrays) is unpleasant in Flow. The dual Flow/TypeScript requirement is what makes most off-the-shelf options awkward.The omissions are not deliberate — I checked whether StyleX prefixes or normalises values in a way that made them redundant, and it does not.
Follow-ups. No TSDoc, unlike csstype, though most of the data is available here. And
packages/@stylexjs/eslint-plugin/src/reference/cssProperties.jsis a second, independent encoding of this knowledge that could use the same treatment — worth knowing before leaning on it that its conversion misassigned values, sostylex/valid-stylescurrently acceptszIndex: 'ideographic'andtransform: 'border-box'.Each failure was verified by injecting it. The suite runs in ~60ms.
Pre-flight checklist
Contribution Guidelines
🤖 Generated with Claude Code