Compatibility Matrix

This matrix reports compile-time compatibility: for every component under ui/, whether compileJSX plus each adapter's generate() step produces diagnostics-free output. Compile-time compatibility (compileJSX + adapter generate() diagnostics). NOT render identity — rendered-output parity is owned by the adapter conformance suite and the eval vector corpus (spec/testing.md Layer 3).

62 components × 9 adapters — 558 / 558 cells compile clean

#Matrix

Each component name links to its component page (or its source, for components without one); the Description column says what it is.

Component Description hono blade erb go-template jinja minijinja mojolicious twig xslate
accordion A vertically stacked set of interactive headings that each reveal an associated section of content.
alert Displays a callout for important content.
alert-dialog A modal dialog that interrupts the user with important content and expects a response. Unlike…
aspect-ratio Displays content within a desired ratio.
avatar An image element with a fallback for representing the user.
badge Displays a badge or a component that looks like a badge.
breadcrumb Displays the path to the current resource using a hierarchy of links.
button Displays a button or a component that looks like a button.
button-group Container for grouping related buttons with merged borders and corners.
calendar A date picker component with month navigation and single or range selection.
card Displays a card with header, content, and footer.
carousel A carousel with motion and swipe built on top of Embla Carousel.
chart Every chart component (containers, primitives, and tooltips) is JSX-native:
checkbox A control that allows the user to toggle between checked and not checked.
collapsible An interactive component which expands/collapses a panel.
combobox Autocomplete input with searchable dropdown.
command A command menu with search, keyboard navigation, and filtering. Use it as an inline menu or inside…
context-menu A menu triggered by right-click, displayed at the cursor position.
data-table Sortable column headers and pagination controls for data tables.
date-picker A date picker component with calendar popup.
dialog A modal dialog that displays content in a layer above the page. Supports ESC key, overlay click,…
direction A provider for setting text direction (LTR/RTL) on child content.
drawer A panel that slides in from the edge of the screen, typically from the bottom. Ideal for…
dropdown-menu A menu of actions triggered by a button.
empty Empty state placeholder with icon, title, and action.
field Form field wrapper with label, description, and error message.
hover-card A floating card that appears on hover to display rich content.
icon SVG icons based on Lucide icon definitions.
input Displays an input field for user text entry.
input-group Input with addons, prefixes, and suffixes.
input-otp An accessible one-time password input component with copy-paste support.
item A generic list/menu item component with composable sub-components for building notification feeds,…
kbd Displays a keyboard key or keyboard shortcut.
label Renders an accessible label associated with controls.
menubar A visually persistent menu common in desktop applications.
native-select A styled native HTML select element.
navigation-menu A collection of links for navigating websites, with hover-activated content panels.
pagination Pagination with page navigation, next and previous links.
popover A floating panel that appears relative to a trigger element.
portal Renders children into a different part of the DOM tree. Useful for modals, tooltips, and dropdowns.
progress Displays an indicator showing the completion progress of a task.
radio-group A set of checkable buttons where only one can be checked at a time.
resizable Accessible resizable panel groups and layouts with drag and keyboard support.
scroll-area Augments native scroll functionality for custom, cross-browser styling.
select Displays a list of options for the user to pick from, triggered by a button.
separator Visually or semantically separates content.
sheet A panel that slides in from the edge of the screen. Extends the Dialog pattern with side-based…
sidebar A composable, collapsible sidebar component with responsive mobile support
skeleton Use to show a placeholder while content is loading.
slider An input where the user selects a value from within a given range.
slot A polymorphic component that merges props with child element
spinner An animated loading indicator for async operations.
switch A control that allows the user to toggle between checked and not checked.
table A responsive table component with composable sub-components.
tabs A set of layered sections of content—known as tab panels—that are displayed one at a time.
textarea Displays a multi-line text input field.
toast A non-blocking notification that displays brief messages to users. Supports auto-dismiss, multiple…
toggle A two-state button that can be either on or off.
toggle-group A set of two-state buttons that can be toggled on or off.
tooltip A popup that displays contextual information on hover or focus.
typography Styled text elements for headings, paragraphs, and prose content.
xyflow Signal-based graph editor — Flow, Background, Controls, MiniMap, Handle, NodeWrapper, SimpleEdge

#Legend

Each diagnostic code below links to the known-limitation issue(s) tracking it. Codes with no linked issue fall back to the repo-wide known-limitation label.

No diagnostics were reported — every component compiles cleanly on every adapter.

#Render Conformance (fixture corpus)

The component matrix above is compile-time only. This section answers the question that actually matters: does the construct work, on each adapter's real backend (Go, Ruby, Perl, PHP, Python, Rust), byte-compared against the Hono reference — not just whether it compiles. A construct works either as written () or with a documented /* @client */ comment (✓†, a verified, supported escape — most refusals have one). Fixtures absent from the table below work on every adapter — most as written, some via that documented escape; the headline says how many of each.

The table lists only fixtures that need attention: at least one adapter where nothing works today. Each cell's meaning is in the legend below it; diagnostic codes link to their known-limitation issue, and the reason a code is led by is in that fixture's detail entry. Escape verification is per adapter, so the same fixture can show ✓† in one column and a code needing attention in another — see escape-coverage.ts.

337 / 345 fixtures work on every adapter (14 of those need a /* @client */ comment on at least one adapter). The 8 below need attention:

Fixture hono blade erb go-template jinja minijinja mojolicious twig xslate
date-method-uncatalogued BF021 BF021 BF021 BF021 BF021 BF021 BF021 BF021 BF021
jsx-element-prop-array BF021 BF021 BF021 BF021 BF021 BF021 BF021 BF021 BF021
jsx-element-prop-fragment-conditional
jsx-element-prop-ternary BF021 BF021 BF021 BF021 BF021 BF021 BF021 BF021 BF021
preamble-cells ✗BF021 ✗BF021 ✗BF021 ✗BF021 ✗BF021 ✗BF021 ✗BF021 ✗BF021
rich-prop-client-read BF049 BF049 BF049 BF049 BF049 BF049 BF049 BF049 BF049
signal-object-spread-init
tag-cloud ✗BF021 ✗BF021 ✗BF021 ✗BF021 ✗BF021 ✗BF021 ✗BF021 ✗BF021

✓ Works

  • — as written
  • ✓† — with a /* @client */ comment (the details below say which diagnostic it silences)

☐ TODO — fix planned, issue linked

  • — compiles, but renders differently from the Hono reference
  • BF021 — a bare diagnostic code: build error, no escape yet

✗ Doesn't work — by design, not a gap

  • ✗BF021 — a code led by : refused on purpose, no escape will ever be offered (why: in the details below)
The 14 fixtures that need a /* @client */ comment — which diagnostic each silences

#Divergence details

Per adapter, for the 8 fixtures in the table above.

  • date-method-uncatalogued — Method call on a Date-typed prop refuses with BF021 (no catalogued lowering)
    • blade, erb, go-template, hono, jinja, minijinja, mojolicious, twig, xslate — refused at build time with BF021
  • jsx-element-prop-array — Array literal wrapping JSX at a non-children prop position refuses with BF021
    • blade, erb, go-template, hono, jinja, minijinja, mojolicious, twig, xslate — refused at build time with BF021
  • jsx-element-prop-fragment-conditional — Conditional inside a fragment-wrapped non-children prop (#2702)
    • go-template — Go-specific half of the fragment-wrapped-conditional prop shape, tracked as https://github.com/piconic-ai/barefootjs/issues/2703 (distinct from #2702, the cross-adapter hydrate-time bug). #2702 is a hydrate-time-only bug (SSR is correct on every adapter, confirmed for Go by direct adapter.generate() inspection of the raw template TEXT, which does contain {{if .Cond}}<a bf-c="^s0">x</a>{{else}}...{{end}}-shaped markup). But the REAL Go render of this exact fixture (go-template-adapter.test.ts, real go run) produces an EMPTY <header><header bf="s1"><!--bf:s0--><!--/--></header> instead of the reference <a>x</a> — i.e. the header prop's jsx-children payload never reaches the child (Card) component's slot construction (.CardSlot1) at all for this NAMED-prop shape, even though the same conditional-in-fragment payload renders correctly when passed via children instead (jsx-element-prop-children-escape). Root cause not yet isolated to a specific emitter function — only the reproducible symptom is pinned here. Every other adapter (Hono, ERB, Jinja, Mojolicious, MiniJinja, Twig, Xslate, Blade) renders this fixture correctly; verified by running each adapter's own JSX Conformance Tests for this fixture id.
  • jsx-element-prop-ternary — Ternary wrapping JSX at a non-children prop position refuses with BF021
    • blade, erb, go-template, hono, jinja, minijinja, mojolicious, twig, xslate — refused at build time with BF021
  • preamble-cells — keyed .map() row with a preamble-built leaf child — patched in place on same-key update, not frozen
    • blade, erb, go-template, jinja, minijinja, mojolicious, twig, xslate — refused at build time with BF021no escape owed (✗): By design: this fixture is the SSR-adopting-hydration + patch-on-update regression pin for #2389. A '/* @client */' escape twin would SSR an empty region, eliminating the adopted DOM node the fixture exists to test patching — collapsing it to the already-covered client-only-loop case with zero new coverage. It is also deliberately kept out of integrations/shared/components (per this file's own docstring) because every DSL integration app's bf build compiles that directory wholesale, so no DSL escape variant is wanted there either.
  • rich-prop-client-read — A Map-typed prop used by client code (even via a method call, since BF021 never sees a handler…
    • blade, erb, go-template, hono, jinja, minijinja, mojolicious, twig, xslate — refused at build time with BF049
  • signal-object-spread-init — Signal initializer spreads a prop-derived object directly, in value position
    • go-template — PRE-EXISTING, unrelated to the #2696 Step 2 spread work this fixture pins: a derived-classified signal/memo whose value is an OBJECT literal has no live-template-expression lowering on Go — unlike the other six template-stash backends (e.g. minijinja emits {% set merged = dict(base, done=true) %}), Go always bakes an object-typed signal/memo field into Go SOURCE at NewXxxProps constructor time (convertInitialValue/parsedLiteralToGo), and that baker is STATIC-only (identifier/call/member operands defer, parsed-literal-to-go.ts's own docstring) — it cannot reference a live prop at all. Reproduced identically with the spread REMOVED (createSignal({ id: base.id, done: true })), confirming the gap predates and is independent of spread: the signal seeds nil and every field read (.Merged.ID/.Merged.Done) reads the Go zero value regardless of initialTodos. Graduate by teaching the baker to emit prop-referencing Go expressions (https://github.com/piconic-ai/barefootjs/issues/2700).
  • tag-cloud — flatMap block-body tag list — hydration adoption, in-place leaf patch, add, and remove
    • blade, erb, go-template, jinja, minijinja, mojolicious, twig, xslate — refused at build time with BF021no escape owed (✗): By design: this fixture pins hydration ADOPTING pre-existing SSR flatMap leaves (interaction 1) plus in-place patch/add/remove on top of that adopted DOM. A '/* @client */' escape twin would SSR zero leaves, so there would be nothing to adopt — the adoption regression this fixture exists for would be untestable, collapsing it to the already-covered client-only-loop case. Also deliberately kept out of integrations/shared/components (per this file's own docstring) since every DSL integration app's bf build compiles that directory wholesale, so no DSL escape variant is wanted there either.

#Construct Support (kind × axis)

Construct-level support, derived from the same fixture-granular conformancePins/renderDivergences as the compat matrix. Pins are attributed to a FIXTURE, not to a construct — a gapped fixture is usually a complex component that incidentally exercises many common kinds/axes alongside the one shape it exists to pin down. Read each cell as a pass/total RATIO over the fixtures that exercise that construct, never as a binary verdict: a construct can show a healthy ratio (e.g. 250/253) with a handful of gapped fixtures — those are real, just narrow. Per-fixture detail for a gap lives in the gaps array (and in the full compat matrix, ui/compat.lock.json); the known-limitation label below is the per-issue source of truth for anything without a linked tracking issue.

Read this as a ratio, not a binary. conformancePins and renderDivergences are attributed to a whole fixture (a component), not to the individual construct — a gapped fixture is usually a complex component that happens to exercise many common kinds and axes alongside the one shape it exists to pin down. A construct showing 137/138 has exactly one gapped fixture contributing to it, not 137 broken cases. A construct with total: 0 (currently just regex) has no fixture exercising it yet.

Each construct name links to a conformance fixture exercising it — a small self-contained component showing the case in real code — and the Example column is that fixture's description. Constructs with no covering fixture yet link to where they're registered in the compiler instead.

#By kind

Construct Example hono blade erb go-template jinja minijinja mojolicious twig xslate
array-literal []
Client-only directive suppresses SSR for a bare loop, keeping boundary markers
80/80 68/80 (#2320) 69/80 (#2320) 68/80 (#2320) 68/80 (#2320) 68/80 (#2320) 69/80 (#2320) 68/80 (#2320) 68/80 (#2320)
array-method items.at()
.at() with no argument returns the first element
71/71 68/71 (known limitation) 68/71 (known limitation) 68/71 (known limitation) 68/71 (known limitation) 68/71 (known limitation) 68/71 (known limitation) 68/71 (known limitation) 68/71 (known limitation)
arrow (a, b) => a - b
.sort((a,b) => a - b) sorts a primitive array by item value
71/71 62/71 (#2320) 63/71 (#2320) 62/71 (#2320) 62/71 (#2320) 62/71 (#2320) 63/71 (#2320) 62/71 (#2320) 62/71 (#2320)
binary MODE === 'a'
Nested ternary chain under a non-reactive (module-const) outer condition (#2470)
94/94 85/94 (#2320, #2321) 86/94 (#2320, #2321) 85/94 (#2320, #2321) 85/94 (#2320, #2321) 85/94 (#2320, #2321) 86/94 (#2320, #2321) 85/94 (#2320, #2321) 85/94 (#2320, #2321)
call count()
Aliased prop's caller-facing key collides with a same-named signal getter (#2525)
228/229 (#2356) 214/229 (#2320, #2321, #2356) 215/229 (#2320, #2321, #2356) 213/229 (#2320, #2321, #2356) 214/229 (#2320, #2321, #2356) 214/229 (#2320, #2321, #2356) 215/229 (#2320, #2321, #2356) 214/229 (#2320, #2321, #2356) 214/229 (#2320, #2321, #2356)
conditional active() ? 2 : 1
Ternary expressions in title and data attribute positions
27/27 25/27 (known limitation) 25/27 (known limitation) 25/27 (known limitation) 25/27 (known limitation) 25/27 (known limitation) 25/27 (known limitation) 25/27 (known limitation) 25/27 (known limitation)
identifier text
Aliased destructured prop ({ n: count }) keeps the rename (#2524)
320/323 (#2356, #2667) 304/323 (#2320, #2321, #2356, #2667) 305/323 (#2320, #2321, #2356, #2667) 302/323 (#2320, #2321, #2356, #2667) 304/323 (#2320, #2321, #2356, #2667) 304/323 (#2320, #2321, #2356, #2667) 305/323 (#2320, #2321, #2356, #2667) 304/323 (#2320, #2321, #2356, #2667) 304/323 (#2320, #2321, #2356, #2667)
index-access classes[variant]
Object literal indexed by a prop, passed to a child component prop
14/14 14/14 14/14 14/14 14/14 14/14 14/14 14/14 14/14
literal true
Static boolean attributes: bare, ={true}, and ={false} omission
259/260 (#2667) 248/260 (#2321, #2667) 248/260 (#2321, #2667) 246/260 (#2321, #2667) 248/260 (#2321, #2667) 248/260 (#2321, #2667) 248/260 (#2321, #2667) 248/260 (#2321, #2667) 248/260 (#2321, #2667)
logical row.done || props.highlight
Loop row preamble computes a conditional-condition value from item + prop
48/48 46/48 (#2321) 46/48 (#2321) 46/48 (#2321) 46/48 (#2321) 46/48 (#2321) 46/48 (#2321) 46/48 (#2321) 46/48 (#2321)
member props.text
Three-level composition threading a prop through each level
183/186 (#2356, #2667) 167/186 (#2320, #2321, #2356, #2667) 168/186 (#2320, #2321, #2356, #2667) 165/186 (#2320, #2321, #2356, #2667) 167/186 (#2320, #2321, #2356, #2667) 167/186 (#2320, #2321, #2356, #2667) 168/186 (#2320, #2321, #2356, #2667) 167/186 (#2320, #2321, #2356, #2667) 167/186 (#2320, #2321, #2356, #2667)
object-literal { __html: html }
dangerouslySetInnerHTML with a prop-derived value renders on Hono/CSR, refuses on template adapters
63/63 60/63 (#2321) 60/63 (#2321) 59/63 (#2321) 60/63 (#2321) 60/63 (#2321) 60/63 (#2321) 60/63 (#2321) 60/63 (#2321)
regex 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0
template-literal `${it.id}:${tag}`
JSX-returning .flatMap() projection — neutral nested-loop IR on every adapter
30/30 29/30 (known limitation) 29/30 (known limitation) 29/30 (known limitation) 29/30 (known limitation) 29/30 (known limitation) 29/30 (known limitation) 29/30 (known limitation) 29/30 (known limitation)
unary -1
.at(-1) returns the last element
24/24 22/24 (#2320) 23/24 (known limitation) 22/24 (#2320) 22/24 (#2320) 22/24 (#2320) 23/24 (known limitation) 22/24 (#2320) 22/24 (#2320)
unsupported BF021 escape: /* @client */ with an explicit new Date(...) revival wrapper, hydrate-safe unlike a bare @client call 40/40 30/40 (#2321) 30/40 (#2321) 30/40 (#2321) 30/40 (#2321) 30/40 (#2321) 30/40 (#2321) 30/40 (#2321) 30/40 (#2321)

#By axis

Construct Example hono blade erb go-template jinja minijinja mojolicious twig xslate
array-method:at items.at()
.at() with no argument returns the first element
2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2
array-method:concat left.concat(right)
.concat(other) merges two arrays in order
2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2
array-method:endsWith value.endsWith(suffix)
.endsWith(suffix) renders the matching branch
2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2
array-method:flat rows.flat()
.flat() flattens one level
4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4
array-method:includes items.includes(target)
.includes(x) on an array prop renders the matching branch
12/12 12/12 12/12 12/12 12/12 12/12 12/12 12/12 12/12
array-method:indexOf items.indexOf(target)
.indexOf(x) returns the 0-based position
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:join items.join()
.join() with no argument joins with the default comma
43/43 41/43 (known limitation) 41/43 (known limitation) 41/43 (known limitation) 41/43 (known limitation) 41/43 (known limitation) 41/43 (known limitation) 41/43 (known limitation) 41/43 (known limitation)
array-method:lastIndexOf items.lastIndexOf(target)
.lastIndexOf(x) returns the position of the last match
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:padEnd value.padEnd(5, '.')
.padEnd(target, pad) right-pads to the target width
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:padStart value.padStart(5, '0')
.padStart(target, pad) left-pads to the target width
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:repeat value.repeat(3)
.repeat(n) concatenates the string n times
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:replace value.replace('o', '0')
.replace(old, new) swaps the first occurrence
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:replaceAll path.replaceAll('/', ' > ')
.replaceAll replaces every occurrence, not just the first
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:reverse items.reverse()
.reverse() emits the array in reverse order
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:slice items.slice()
.slice() with no argument copies the whole array
5/5 4/5 (known limitation) 4/5 (known limitation) 4/5 (known limitation) 4/5 (known limitation) 4/5 (known limitation) 4/5 (known limitation) 4/5 (known limitation) 4/5 (known limitation)
array-method:split value.split(',')
.split(sep) splits a string into an array of substrings
2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2
array-method:startsWith value.startsWith(prefix)
.startsWith(prefix) renders the matching branch
3/3 3/3 3/3 3/3 3/3 3/3 3/3 3/3 3/3
array-method:toFixed price.toFixed(2)
.toFixed(2) formats a numeric prop with rounding and zero-padding
2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2
array-method:toLowerCase value.toLowerCase()
.toLowerCase() lowercases the receiver string
4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4
array-method:toReversed items.toReversed()
.toReversed() emits the array in reverse order
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:toUpperCase value.toUpperCase()
.toUpperCase() uppercases the receiver string
3/3 2/3 (known limitation) 2/3 (known limitation) 2/3 (known limitation) 2/3 (known limitation) 2/3 (known limitation) 2/3 (known limitation) 2/3 (known limitation) 2/3 (known limitation)
array-method:trim value.trim()
.trim() strips leading and trailing whitespace
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:trimEnd value.trimEnd()
.trimStart() and .trimEnd() strip only their own side
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
array-method:trimStart value.trimStart()
.trimStart() and .trimEnd() strip only their own side
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
binary:!= Nullish coalescing with JSX fallback in JSX child position 2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2 2/2
binary:!== n() !== 0
Comparison operators (>=, ===, !==) as ternary conditions
9/9 9/9 9/9 9/9 9/9 9/9 9/9 9/9 9/9
binary:* count() * 2
Derived value with createMemo
12/12 12/12 12/12 12/12 12/12 12/12 12/12 12/12 12/12
binary:+ 'Hello, ' + name
String concatenation via the + operator
21/21 20/21 (#2321) 20/21 (#2321) 20/21 (#2321) 20/21 (#2321) 20/21 (#2321) 20/21 (#2321) 20/21 (#2321) 20/21 (#2321)
binary:- a - b
.sort((a,b) => a - b) sorts a primitive array by item value
12/12 12/12 12/12 12/12 12/12 12/12 12/12 12/12 12/12
binary:/ sortedData().length / pageSize
site/ui DataTable demo — keyed-loop reorder on sort: column-header clicks resort the memo and the runtime reorders rows in place
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
binary:< aStr < bStr
site/ui DataTable demo — keyed-loop reorder on sort: column-header clicks resort the memo and the runtime reorders rows in place
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
binary:=== MODE === 'a'
Nested ternary chain under a non-reactive (module-const) outer condition (#2470)
49/49 41/49 (#2320) 42/49 (#2320) 41/49 (#2320) 41/49 (#2320) 41/49 (#2320) 42/49 (#2320) 41/49 (#2320) 41/49 (#2320)
binary:> count() > 0
Top-level ternary return preserves conditional (#968)
11/11 11/11 11/11 11/11 11/11 11/11 11/11 11/11 11/11
binary:>= n() >= 10
Comparison operators (>=, ===, !==) as ternary conditions
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
literal:boolean true
Static boolean attributes: bare, ={true}, and ={false} omission
55/56 (#2667) 54/56 (#2667) 54/56 (#2667) 52/56 (#2667) 54/56 (#2667) 54/56 (#2667) 54/56 (#2667) 54/56 (#2667) 54/56 (#2667)
literal:null Falsy-zero {n && jsx} renders nothing (documented React divergence) 23/23 23/23 23/23 23/23 23/23 23/23 23/23 23/23 23/23
literal:number 2
Inline SVG: viewBox casing and camelCase → kebab-case presentation attrs
106/106 101/106 (known limitation) 101/106 (known limitation) 101/106 (known limitation) 101/106 (known limitation) 101/106 (known limitation) 101/106 (known limitation) 101/106 (known limitation) 101/106 (known limitation)
literal:string ' '
Explicit {" "} joiners and multi-line text trimming
183/183 175/183 (#2321) 175/183 (#2321) 174/183 (#2321) 175/183 (#2321) 175/183 (#2321) 175/183 (#2321) 175/183 (#2321) 175/183 (#2321)
logical:&& ready() && visible()
Chained a() && b() && conditional
7/7 7/7 7/7 7/7 7/7 7/7 7/7 7/7 7/7
logical:?? size ?? 1
Nullish coalescing over destructured optional props seeds SSR correctly
42/42 40/42 (#2321) 40/42 (#2321) 40/42 (#2321) 40/42 (#2321) 40/42 (#2321) 40/42 (#2321) 40/42 (#2321) 40/42 (#2321)
logical:|| row.done || props.highlight
Loop row preamble computes a conditional-condition value from item + prop
14/14 14/14 14/14 14/14 14/14 14/14 14/14 14/14 14/14
lowering:date Date-typed prop: toISOString() and getUTCFullYear() render via the catalogued date helper 4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4
lowering:formatDate formatDate(date, pattern, timeZone) renders via the catalogued format_date helper 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
lowering:queryHref queryHref(base, {…}) lowers to the query helper via the builtin plugin registry 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
lowering:toLocaleDateString toLocaleDateString dateStyle options lower to name-token patterns + shipped name tables 4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4 4/4
member:computed strokePaths['x']
site/ui Pagination demo — one signal drives data-active across seven links; href="#" handlers must preventDefault
8/8 8/8 8/8 8/8 8/8 8/8 8/8 8/8 8/8
member:optional user?.name
Optional chaining into an object prop with nullish fallback
1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
sort-direction:asc .sort((a,b) => a - b) sorts a primitive array by item value 7/7 7/7 7/7 7/7 7/7 7/7 7/7 7/7 7/7
sort-direction:desc .sort((a,b) => b.f - a.f) sorts by field, descending 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
sort-key:auto .toSorted((a,b) => a.rank > b.rank ? 1 : -1) sorts ascending by rank 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
sort-key:numeric .sort((a,b) => a - b) sorts a primitive array by item value 7/7 7/7 7/7 7/7 7/7 7/7 7/7 7/7 7/7
sort-key:string .sort((a,b) => a.price - b.price || a.name.localeCompare(b.name)) sorts by price, then name 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
sort-target:field .sort((a,b) => a.f - b.f) sorts by field, ascending 7/7 7/7 7/7 7/7 7/7 7/7 7/7 7/7 7/7
sort-target:self .sort((a,b) => a - b) sorts a primitive array by item value 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
sort:multi-key .sort((a,b) => a.price - b.price || a.name.localeCompare(b.name)) sorts by price, then name 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
unary:! !accepted()
Terms-acceptance form — reactive data-state / aria-checked / disabled from one signal
16/16 14/16 (#2320) 15/16 (known limitation) 14/16 (#2320) 14/16 (#2320) 14/16 (#2320) 15/16 (known limitation) 14/16 (#2320) 14/16 (#2320)
unary:- -1
.at(-1) returns the last element
11/11 11/11 11/11 11/11 11/11 11/11 11/11 11/11 11/11

#Data Source

This table is generated from the committed ui/compat.lock.json, regenerated with bun run compat:lock and drift-checked in CI. Render-level divergences are declared per adapter in packages/adapter-*/src/render-divergences.ts (each adapter's conformance suite derives its skip list from the same declaration, so this page and the tests cannot drift apart). Tracked limitations carry the known-limitation label.

Component and fixture descriptions are computed into the same lock at regen time (component-docs.ts) — component copy is taken from the public component page's own description (falling back to the ui/registry.json catalogue one-liner, then the component's JSDoc tagline), and the component name links to that page when one exists; fixture descriptions come from the corpus itself. All of it is drift-checked with everything else and can't go stale independently.

The construct-support section above is generated from the committed ui/support-matrix.lock.json, regenerated with bun run support-matrix:lock and drift-checked in CI alongside the component matrix. Each construct's fixture link and example description are computed the same way — the exemplar is mechanically chosen as the most narrowly targeted covering fixture (construct-examples.ts), and definition-site fallback links come from a TS AST walk over the compiler's construct catalogues (construct-source-links.ts) — so they are regenerated and drift-checked on the same schedule: a link can't silently go stale, because moving the code it points at changes what the next regen commits, and CI fails if that regen was forgotten.

Every issue link on this page is freshness-checked on a schedule (bun run compat:issues, the compat-issue-freshness workflow): if a referenced issue is closed while its pin still points at it, an alert issue is opened automatically so the link is re-pointed or removed — the matrix cannot silently link a live limitation to a completed ticket.