/ component

@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.

/ basic-usage

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>"
/ dependent-initial-value

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).

  • <length>
<length>+

initial-value (live check)

valid
produced syntax string
syntax: "<length>+"
candidate initial-value
valid

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.

/ types

Three usage tiers

From useState-and-go to one CSS string typing another at compile time.

01 casual
string

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> | auto
const [value, setValue] = useState<string>("<length> | auto")
02 intellisense
SyntaxString

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>+")
03 strict
SyntaxLiteral<S> ยท InitialValueLiteral<Syn, V>

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> | auto
cssProperty("<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

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).

PropTypeDescription
valueSyntaxString | (string & {})Current @property syntax descriptor string. Required. An empty / unparseable value seeds an empty editor.
onChange(next: SyntaxString) => voidFires 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's ColorLiteral), 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); and calc()/var() initial values resolve to never in strict (the casual tier + runtime accept them).
  • The inherits descriptor is a UI toggle, serialized only in an example's full @property block, not in the controlled syntax value.
/ install

Drop it in

One command via the shadcn CLI.

$ pnpm dlx shadcn@latest add https://ridiculous.turtlesocks.dev/r/property-syntax-editor.json