jixoai-ui
the terminal design language
loading the style sheet…
Skip to content
docs · print medium: screen

grammar

The grammar — one law, eight axes

Every component accepts the same eight style axes. Each axis takes a NAMED step (theme aliases, developer-remappable), the reserved literal auto (inherit the ambient context — the default, stamps nothing), an exact NUMBER (the unit is per-axis: px · coefficient · dp · hue), or query() wrapping any lane for responsive and container-conditional values. Values resolve to CSS expressions stamped as custom properties on the component root — never new classes.

size · shape · radius · density · color · theme · elevation · motion

lanes

The three lanes + the wrapper

named | auto | ${number} on every axis, plus the orthogonal query() wrapper — the reserved literals (auto, numbers) can never be remapped or shadowed.

PropertyTypeDefaultDescription
named 'small' | 'medium' | 'large' | …per axisA theme alias step — resolves through the plugin alias table as var(--jx-<axis>-<alias>), developer-remappable by overriding the var (pure CSS, no resolver runtime). The documented vocabulary per axis lives in the table below.
auto 'auto''auto'RESERVED LITERAL — inherit the resolved ambient context (explicit prop > ambient > the family default). The DEFAULT of every axis; an auto resolution stamps nothing, the context flows. May not be remapped or shadowed.
${number} numberper axisRESERVED LITERAL — the exact-value escape. The unit is per axis: px for size/radius, a coefficient for density/motion, dp for elevation, hue degrees for color. shape/theme have no number lane.
query() QueryResult<T>baseThe orthogonal wrapper — responsive and container-conditional values on ANY axis, wrapping ANY lane. See the query() section: media keys bare (sm), container keys carry @ (@sm), named containers @sm/card.

axes

The eight axes

The frozen lane table (the docs vocabulary; the legacy density spellings keep working as aliases).

PropertyTypeDefaultDescription
size 'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopethe base scale — root font-size; parts size in em
shape 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto' · ambient scopecorner geometry (CSS corner-shape; §14 degrade table)
radius 'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopecorner size; auto = the concentric broadcast (§3)
density 'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopespacing/leading scale over the kernel channels (§4)
color 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto' · ambient scopethe hue axis of the fixed oklch primary system (§5)
theme 'light' | 'dark' | 'system' | 'auto''auto' · ambient scopelight/dark profile; system = the JS-mutable global (§6)
elevation 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto' · ambient scopeofficial M3 levels over the surface ladder (§7)
motion 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto' · ambient scopeintensity across the motion kernels (§8)

query()

query() — conditional values

query({ [condition]: <lane value> }) wraps ANY lane of ANY axis. Media conditions are bare (sm · md · lg — xs|sm|md|lg are RESERVED for this use); container conditions carry @ (@sm) on their OWN --container-* scale — @sm = 24rem while viewport sm = 40rem, the two never collapse. A NAMED container addresses as @sm/card: size first, then /, then the name (Tailwind v4's own order). The ladder is min-width, later keys override at wider matches; the desugarer emits blocks in REGISTERED-SCALE order so authoring order never matters ({lg:'a', sm:'b'} and {sm:'b', lg:'a'} compile identically). Containers resolve against the NEAREST qualifying ancestor — a component cannot query itself — and trees that use @ keys need an ancestor with container-type (the broadcast supply duty; the canvas's scroll layer is one).

PropertyTypeDefaultDescription
sm 'xs' | 'sm' | 'md' | 'lg'—MEDIA keys are bare — the viewport vocabulary, xs|sm|md|lg RESERVED for this use. The registered default table: xs 30rem · sm 40rem · md 48rem · lg 64rem (plugin-remappable thresholds).
@sm '@3xs' | '@2xs' | '@xs' | '@sm' | '@md' | '@lg' | '@xl'—CONTAINER keys carry @ (the Tailwind v4 @container convention) and ride their OWN --container-* scale: @sm = 24rem ≠ viewport sm = 40rem. Resolves against the NEAREST qualifying ancestor container — a component cannot query itself (CSS law).
@sm/card `@${scale}/${string}`—A NAMED container — size first, then /, then the container name. Contributors of containers stamp container-name + container-type (the §11 supply duty); a missing name never matches and the build warns.
base T | undefined'auto'The unconditional lane rendered at SSR first paint and when no case matches. Semantics: min-width ladder, later keys override at wider matches — blocks emit in REGISTERED-SCALE order (narrow → wide), never raw insertion order.
query() usage
<!-- media keys are BARE; the ladder is narrow → wide, insertion order never matters -->
<Card size={query({ sm: 'large' })}>…</Card>

<!-- container keys carry @; a NAMED container is size/name -->
<PressButton radius={query({ '@sm': 20, '@md/sidebar': 8 })}>…</PressButton>

<!-- any lane wraps: named steps, numbers, and a base -->
<Card radius={query({ sm: 'large' }, 'small')}>…</Card>

law

Resolution, supply & the carrier law — 吃也供

An axis resolves explicit prop → ambient context → the family's own default. A component that CONSUMES an axis also SUPPLIES its resolved value downward (the Svelte context carries the INTENT, the CSS custom properties carry the RENDER — two channels of one resolution), so a nested component inherits by default: an inner radius='auto' computes max(0px, R − P) off the ancestor's --jx-radius-effective/--jx-inset-effective (the concentric broadcast; the var() fallbacks are load-bearing). Every resolved value is a CSS EXPRESSION — vars are the degenerate case, calc()/max()/color-mix() the general case — assigned to custom properties on the component root: SSR/zero-JS by construction, zero new class identities, stylex never sees instance values. On native-element wrappers the family consumes size and color — the native input never receives them; everything the family does not own still rides through rest props.

Every migrated family's docs page carries the live Universal props section and demo — the native form families (input, native-select, textarea, checkbox, radio, range, color-picker, file-input, number-input, cascader, tags-input, input-otp, combobox, input-group, date-picker, ghostty-term) carry the surface first, and the component-canvas wires the per-axis CONTROLS (enum select · number spinner · query() editor) onto its dock.

degrade

The degrade tables — designed states, never silent luck

Any new-CSS capability gets a plugin-level @supports verdict, one global treatment, a documented fallback table. corner-shape is the first instance: the verdict stamps VARS, never classes — the ladder ships twice gated by CSS itself, families read the ladder and never the capability. squircle's ×2 law rides the per-shape factor var (--jx-radius-factor-squircle: 2 supported · 1 degraded) so its reversal is automatic; scoop/bevel/notch factor to their degrade target's geometry (square ⇒ factor 0).

corner-shape unsupported →

PropertyTypeDefaultDescription
scoop 'scoop'squareDegrades to square geometry — the radius factor zeroes so the corner actually squares (a non-zero radius under a square corner-shape still rounds).
bevel 'bevel'squareDegrades to square geometry (factor 0).
notch 'notch'squareDegrades to square geometry (factor 0) — the only safe target (§15.1).
squircle 'squircle'roundDegrades to round AND the ×2 law reverses: --jx-radius-factor-squircle steps 2 → 1, the same var, zero branches.
round 'round'roundNative border-radius semantics — itself in both worlds, factor 1/1.
square 'square'squareItself; the radius lane is inert by definition (factor 0).

Existing component-level probes are REGISTERED EXCEPTIONS absorbed through the ladder vars (press-effect-runtime, avatar) or filed in the gate's exemption ledger with reasons (the squircle percentage boundary) — a degrade is a DESIGNED state with receipts.

dogfood

Live — the fleet on the axes

The migrated components, live: the card is the §3 concentric ANCHOR (radius 20 + the 14px ruler inset → descendants at auto compute 6px), the media-key query() demo steps the whole card family at the 40rem boundary, and the site's own docs infra (batch D4) rides the axes below.

radius 20 · the concentric anchor

children at radius="auto" compute max(0px, 20 − 14) = 6px

size query({ sm: 'large' })

below 40rem the base (auto) applies — radius too returns to the root 0px invariants (the square corners ARE the auto state); at ≥40rem the root font-size steps to the large rung — resize the window and watch the WHOLE family follow

shape squircle · radius 20

the §14 factor doubles the resolved corner — and reverses to ×1 on degrade, the same var.

site

The site itself — docs infra on the axes

The docs infrastructure families (token-table, props-table, a11y-table, density-demo, docs-pager, docs-sections-nav, search-palette) joined the same surface with batch D4 — their universality is site-side only (no registry item), demonstrated here.

TokenDefaultSource
--jx-size-effective18px (this table)component
--jx-density-coefficient1 (named rung)density
xs
density-demo dogfoods the axis — the demo's own chrome at the explicit lane
sm
density-demo dogfoods the axis — the demo's own chrome at the explicit lane
default
density-demo dogfoods the axis — the demo's own chrome at the explicit lane
lg
density-demo dogfoods the axis — the demo's own chrome at the explicit lane
PropertyTypeDefaultDescription
size SizeLane | QueryResult<SizeLane>autoThis table renders at the named medium step — var(--jx-size-medium) through the alias ladder, zero inline px.
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h