@property Syntax Editor
Ridiculously typed editor for the CSS @property syntax: descriptor. The namesake types one CSS string with another โ InitialValueLiteral<Syntax, V> checks an initial-value against the validated syntax, so cssProperty("<length>", "red") is a compile error. A chip builder sits beside a live green/red demo.
Edit a @property syntax descriptor
Controlled value + onChange. The controlled value is the syntax STRING. Pick data-type chips (<length>, <color>, โฆ), add literal idents, toggle a + / # multiplier, or flip the * universal switch โ the editor emits the canonical descriptor (<length> | auto). The panel's initial-value field is a live demo of the dependency, not part of the controlled value.
syntax: "<length>"The dependency, live: one CSS string typing another
The hero. Build the syntax descriptor with the chip builder on the left; type a candidate initial-value on the right. The badge flips green / red in real time as matchesSyntax(syntax, value) runs โ the runtime mirror of the dependent type InitialValueLiteral<Syntax, V>. Try <color> with #ff0000 (valid) vs red (rejected โ color-picker forms only).
syntax: "<length>+"At compile time the same dependency is cssProperty(syntax, initialValue) โ a mismatch resolves the call to never. The inherits descriptor is a UI toggle, not part of the controlled syntax value.
Three usage tiers
From useState-and-go to one CSS string typing another at compile time.
Pass any string
useState<string>. No compile-time validation; the runtime parser tokenizes the descriptor into its | alternatives, classifies each component as a <data-type> or a literal ident, and reads its + / # multiplier.
<length> | autoconst [value, setValue] = useState<string>("<length> | auto")
Syntax-shaped hints
State typed as SyntaxString โ a descriptor-shaped union (<length>, <color>#, *, โฆ) and the onChange return type. Every DataTypeName crossed with the three multipliers is suggested, while (string & {}) keeps free-form alternations editable.
<length>+const [value, setValue] = useState<SyntaxString>("<length>+")
One CSS string typing another, at compile time
cssSyntax() validates the full <syntax> grammar (universal, | alternation, +/# multipliers, known data types). The crown jewel is cssProperty(syntax, initialValue) โ the dependent InitialValueLiteral<Syn, V> checks the initial-value AGAINST the syntax, resolving a mismatch (cssProperty("<length>", "red")) to never before you run the code. <color> defers to color-picker's ColorLiteral.
<length> | autocssProperty("<length>+", "0px 4px 8px") // โ every space-token is a <length> cssProperty("<color>", "#ff0000") // โ via color-picker ColorLiteral // @ts-expect-error "red" is not a <length> cssProperty("<length>", "red") // @ts-expect-error <bogus> is unknown cssSyntax("<bogus>")
Note: <color> covers color-picker forms (#ff0000, oklch(0.7 0.15 30)), so a named color like red is (correctly) rejected. <integer> is validated as <number> at the type level; <image>/<url>/<transform-*> and calc()/var() initial values defer to the lenient runtime parser.
API
Public surface โ component props, runtime helpers, and the type exports.
ยง PropertySyntaxEditor / PropertySyntaxEditorPanel
<PropertySyntaxEditor
value: SyntaxString | (string & {})
onChange: (next: SyntaxString) => void
className?: string
aria-label?: string
/>PropertySyntaxEditor is popover-wrapped (a trigger showing a syntax badge + the truncated descriptor); PropertySyntaxEditorPanel renders the same editor inline. Both are controlled and single-valued โ the controlled value is the syntax string. The initial-value field is a live demo of the dependency, held as panel-local state (it is NOT emitted via onChange).
| Prop | Type | Description |
|---|---|---|
| value | SyntaxString | (string & {}) | Current @property syntax descriptor string. Required. An empty / unparseable value seeds an empty editor. |
| onChange | (next: SyntaxString) => void | Fires when the descriptor changes. Emits the canonical syntax string (formatSyntax). The initial-value / inherits demo fields do NOT fire onChange. |
ยง Sub-components
<SyntaxChipBuilder universal components onChange />
A palette of <data-type> buttons + an add-literal-ident input append OR'd components, rendered as pills with a visible | between them. Each chip has a none/+/# multiplier segmented control (labelled buttons with aria-pressed), reorder โ/โ, and remove. A * universal switch is exclusive โ flipping it on clears the chips.
<InitialValueField syntax value inherits onValueChange onInheritsChange />
The live demo of the dependent type: a candidate initial-value <input>, a green/red status badge (role=status) driven by matchesSyntax (the runtime mirror of InitialValueLiteral), and the inherits descriptor toggle. <color> defers to color-picker's isColorString โ #ff0000 valid, red rejected.
<MiniSelect value options onChange />
The local compact <select> chrome. Each component owns its copy (registry self-containment) โ it is not imported from query-builder.
ยง Validators (call-site helpers)
cssSyntax<S>(value: S & SyntaxLiteral<S>): S
Strict validator for a @property syntax descriptor. S if S is a valid <syntax>, else never. Validates universal *, | alternation, single +/# multiplier, and known data types / literal idents.
cssProperty<Syn, Init>(syntax: Syn & SyntaxLiteral<Syn>, initialValue: Init & InitialValueLiteral<Syn, Init>): { syntax; initialValue }The crown jewel โ one CSS string typing another. Type-checks ONLY when the initialValue satisfies the declared syntax; a mismatch resolves the initialValue argument to never. cssProperty("<length>", "red") โ never.
ยง Runtime helpers
parseSyntax(src): { universal; components; error }String โ editor state. The runtime superset of the strict tier: classifies the structure (universal *, the | alternation, each component's <data-type>-or-ident base + optional single +/# multiplier) and surfaces an error.
formatSyntax(universal, components): string
Serialize the editor state back to a canonical syntax string. universal โ '*'; otherwise components join with ' | '.
matchesSyntax(syntax, value): boolean
Whether a candidate initial-value satisfies a syntax โ the runtime mirror of InitialValueLiteral. Uses isColorString (color-picker) for <color>, a real integer check for <integer>, and kit-equivalent dimension regexes for the rest.
dataTypeNames() ยท defaultSyntax() ยท defaultInitialValue(syntax)
The palette option source (every <data-type> name), a sensible seed descriptor, and a representative initial-value for a syntax string.
ยง Types
- SyntaxLiteral<S>
- Strict validator โ S if S is a valid @property syntax descriptor, else never.
- InitialValueLiteral<Syntax, V>
- The dependent validator (the namesake) โ V if V satisfies Syntax, else never. One validated value string constraining another's type.
- SyntaxString / DataTypeName / MultiplierToken
- The suggestion union (descriptor-shaped string, the onChange return), the data-type name union, and the multiplier token ('' | '+' | '#').
- ComponentsOf<S> / SatisfiesBase<Tok, Base>
- The syntax components (split on |), and the per-token predicate. Exported for advanced use.
- SyntaxComponent / PropertySyntaxState
- A single chip ({ base, isType, multiplier }) and the internal editor state ({ universal, components, initialValue, inherits }).
ยง Strict-tier scope (validated vs deferred)
- Validated: the full
<syntax>grammar (universal*,|alternation,+/#multipliers, known data types, literal idents); and the initial-value against the syntax for the dimensional types,<color>(via color-picker'sColorLiteral), literal idents,|alternation, and+/#multipliers. - Deferred (lenient โ runtime parser):
<integer>is checked as<number>at the type level (runtime is integer-strict);<image>/<url>/<transform-function>/<transform-list>accept any token (large / undecidable grammars); andcalc()/var()initial values resolve toneverin strict (the casual tier + runtime accept them). - The
inheritsdescriptor is a UI toggle, serialized only in an example's full@propertyblock, not in the controlledsyntaxvalue.
Drop it in
One command via the shadcn CLI.
$ pnpm dlx shadcn@latest add https://ridiculous.turtlesocks.dev/r/property-syntax-editor.json