The Metadata Protocol
The metadata protocol is how DOM elements declare what they mean to the application. The boundary parses these declarations and feeds them to the runtime.
There are three flavors. They produce the same runtime shape.
Plain DOM attributes
Section titled “Plain DOM attributes”For raw HTML, server-rendered pages, framework-rendered DOM, or anywhere you don’t have custom elements:
<button data-meta-event="checkout.started" data-meta-command="analytics.flush" data-meta-prop-location="hero" data-meta-prop-cart-id="cart_123"> Start checkout</button>The data- prefix is required for ordinary HTML elements (it’s the prefix HTML reserves for app-specific attributes). React, Vue, Svelte, plain HTML — anything that renders elements — can participate.
Custom element attributes
Section titled “Custom element attributes”For Kitsune custom elements, drop the data- prefix:
<kit-button meta-event="checkout.started" meta-command="analytics.flush" meta-prop-location="hero" meta-prop-cart-id="cart_123"> Start checkout</kit-button>Custom element tags can carry whatever attributes they like, so the cleaner form is allowed.
Attribute keys
Section titled “Attribute keys”| Attribute | Maps to |
|---|---|
meta-event / data-meta-event | event.type |
meta-command / data-meta-command | command.type |
meta-payload / data-meta-payload | JSON object merged into payload |
meta-prop-<key> / data-meta-prop-<key> | payload[<key>] (camelCased) |
Property names are kebab-case in the attribute and camelCase in the payload:
<button data-meta-prop-cart-id="cart_123">...</button>Becomes:
{ payload: { cartId: 'cart_123' } }For typed payloads, use JSON:
<button data-meta-event="checkout.started" data-meta-payload='{"lineItems":3,"gift":true}' data-meta-prop-cart-id="cart_123"> Start checkout</button>meta-prop-* values override keys from meta-payload, so the final payload is:
{ cartId: 'cart_123', lineItems: 3, gift: true }What the boundary does
Section titled “What the boundary does”Each <kit-boundary> has a delegated click listener. When a click fires:
- Walk the click’s
composedPath()looking for the nearest element with metadata. - If found, parse:
meta-event→ emit an event of that typemeta-command→ dispatch a command of that typemeta-prop-*→ fold into the payload
- Add the boundary’s accumulated context.
- Add a
source: { tagName: 'kit-button' }for diagnostics. - Emit the event (if any) first, then dispatch the command (if any).
If both meta-event and meta-command are present on the same element, both fire. Event first.
What can carry metadata
Section titled “What can carry metadata”Anything that’s a click target. Most commonly:
<kit-button>and built-in<button><kit-card interactive>(for clickable cards)<kit-disclosure>(when added)- Plain
<a>,<div>, etc. as long as they receive a click
If the element doesn’t normally take focus or receive keyboard activation, you’ll want to add tabindex="0" and role for accessibility — or use a real <button>.
Non-click interactions
Section titled “Non-click interactions”The boundary also handles:
- Native form
submitwhen the form hasmeta-event/data-meta-eventormeta-command/data-meta-command. - Native field
changewhen the field hasmeta-event-on-change/data-meta-event-on-change. - Composed
meta:eventcustom events for programmatic dispatch from inside any element.
Composed custom events
Section titled “Composed custom events”Programmatic dispatch from inside a custom element uses a composed event:
this.dispatchEvent(new CustomEvent('meta:event', { bubbles: true, composed: true, detail: { type: 'editor.cursor_moved', payload: { line: 42 }, },}))The nearest boundary adds its context and emits the runtime event.
Why declarative metadata at all?
Section titled “Why declarative metadata at all?”Three reasons declarative attributes beat imperative onClick={() => emit(...)}:
It keeps UI components free of runtime imports. A <kit-button> doesn’t import the runtime to declare its meaning.
It works across rendering systems. Plain HTML, Lit, React, Vue, server-rendered HTML — they all produce DOM. The DOM has attributes. The boundary parses them.
It’s inspectable. Open DevTools, see what an element means. No need to find the imperative handler in the source.
Caveats
Section titled “Caveats”meta-prop-*payloads are strings. Usemeta-payloadfor JSON values andmeta-prop-*for simple string overrides.- Metadata is captured at click. Setting metadata after a click doesn’t replay. Set it before.
- Boundaries delegate at their root. If a click is
stopPropagation’d below the boundary, metadata won’t be parsed. AvoidstopPropagationin click handlers unless intentional.
Read next
Section titled “Read next”- Reference: Metadata Protocol — the complete attribute table and parsing rules
- Boundaries — how context is added on top of metadata
- Events and Commands — what the parsed metadata becomes