Browser Elements

QIP provides three custom elements organized around the user's relationship to a component. Use <qip-view> to consume a result, <qip-edit> to author inputs, and <qip-play> to interact with a running component.

view    consume
edit    author
play    interact

These names describe the user experience, not implementation concepts such as rendering, pipelines, transforms, or static generation.

ElementUser relationshipInput or state ownerLifecyclePre-rendering
<qip-view>ConsumeThe page or applicationFinite renderingThe complete output can be rendered at build time or on the server; client activation can be optional
<qip-edit>AuthorUser-editable form controlsReactive finite renderingThe initial output can be pre-rendered, but client activation is required for editing
<qip-play>InteractThe running QIP componentPersistent, stateful sessionA fallback, poster, or initial snapshot can be pre-rendered, but interaction requires activation

Choosing An Element #

  1. Use <qip-view> when the user consumes a result whose source is owned by the page or application.
  2. Use <qip-edit> when the user changes source inputs and QIP reactively updates the outputs.
  3. Use <qip-play> when the component owns an ongoing stateful session and responds to interaction over time.

The distinction is the overall user experience, not the presence of a particular form-control attribute:

<qip-view> #

<qip-view> is the finite, consumption-oriented element. Its standalone module currently duplicates <qip-edit>'s wiring, rendering, and module policy. Keeping one entrypoint per element makes loading and replacement predictable; shared implementation can be introduced later without changing the public routes.

The page or application owns the canonical input, and the user consumes the resulting representation rather than editing it. Inputs may be supplied with hidden or read-only controls:

<qip-view>
  <source src="/text/markdown/commonmark.0.31.2.wasm" type="application/wasm" />
  <input type="hidden" name="input" value="# Rendered ahead of time" />
  <output name="output"><h1>Rendered ahead of time</h1></output>
</qip-view>

A view performs a finite rendering operation. Its output may be generated at build time or on the server, and a pre-rendered view stays useful without client-side execution merely to display existing output. The browser may render or rerender the view when the application explicitly asks for it.

Rendering Times #

Finite rendering can observe values from three different eras:

build time → request time → interaction time

Here, time is logical: it says which value came after another, not how many seconds passed. This remains useful when values are synchronized without requiring clocks to agree.

Each later era may override a value without destroying the earlier value it came from. This lets the same markup provide a static fallback, a complete server-rendered response, and optional client-side enhancement.

EnvironmentInitial valuesWhere QIP runsWhat remains possible
Static page without JavaScriptBuild-time fallbacksDuring the buildThe complete fallback result is readable and form submission can navigate normally
Server with no JavaScriptBuild-time fallbacks plus request-time valuesWhile serving the requestThe response contains the complete result; a form can submit another request
Static page with JavaScriptBuild-time fallbacks, then values connected from the current URLDuring the build and locally in the browserWasm can rerender after URL or form-control changes without a server renderer
Server with JavaScriptRequest-time values in the initial response, followed by live control valuesOn the server initially and locally thereafterThe page works before activation and can rerender without another request after activation

Preserving Value Eras

One proposed finite-rendering element is <qip-connect-search-params>. It explicitly connects permitted URL search parameters to ordinary hidden form controls:

<qip-connect-search-params>
  <input type="hidden" name="language" value="en">
  <input type="hidden" name="currency" value="AUD">
</qip-connect-search-params>

A build transform can preserve each authored value as data-qip-fallback. A request-time renderer then writes the resolved value to the normal value attribute:

<qip-connect-search-params>
  <input
    type="hidden"
    name="language"
    value="fr"
    data-qip-fallback="en"
  >
</qip-connect-search-params>

The three representations have distinct ownership:

EraRepresentationMeaning
Build timedata-qip-fallbackAuthored fallback snapshot retained by the build
Request timeThe serialized value attributeValue selected for the server-rendered response, or the fallback when no request value exists
Interaction timeThe live input value DOM propertyCurrent browser value, which may differ from the serialized request snapshot

The browser custom element can restore data-qip-fallback when a search parameter disappears, update the live value when the URL changes, and notify the enclosing finite renderer. Without JavaScript, the hidden inputs remain successful form controls and carry the server-selected or fallback values into the next submission.

This is a proposed API rather than part of the current browser-element contract. Its server and browser implementations should share fixtures that verify they select the same parameters and produce byte-identical application/x-www-form-urlencoded input.

<qip-edit> #

<qip-edit> is the finite, authoring-oriented element. The user authors one or more declared inputs; QIP performs finite renders in response to relevant input changes and writes the result to the declared outputs. Use "edit element" when a noun is needed in prose.

The element is intentionally small: the page provides input controls, Wasm sources, and output views; the runtime wires them together.

<form aria-label="Markdown to HTML">
  <qip-edit>
    <source src="/text/markdown/commonmark.0.31.2.wasm" type="application/wasm" />
    <source src="/text/html/html-code-syntax-highlight-tsx.wasm" type="application/wasm" />

    <textarea name="input" rows="5" placeholder="Write some Markdown"># A Markdown renderer that works identically cross-platform! Try typing…</textarea>

    <output name="output"><pre><code></code></pre></output>
    <output name="output"><iframe title="Rendered HTML preview" sandbox></iframe></output>
  </qip-edit>
</form>

Initial output may be rendered ahead of time, but client activation is still required for the element to respond to later edits. A temporarily read-only authoring experience is still an edit rather than a view.

Contract

Each edit element needs at least one QIP component, one input, and one output:

<qip-edit>
  <source src="/text/markdown/commonmark.0.31.2.wasm" type="application/wasm" />
  <textarea name="input"># Hello</textarea>
  <output name="output"><pre><code></code></pre></output>
</qip-edit>

The runtime reads from the first child with name="input". It runs each <source type="application/wasm"> as a pipeline stage in document order. It then writes the same output bytes into every <output name="output">.

Multiple outputs are multiple views of one result. They are not multiple return values from the component.

Output Views

<output name="output"> is the semantic binding. Its child element declares how the page wants to show the bytes.

<output name="output"><pre><code></code></pre></output>
<output name="output"><iframe title="Rendered HTML preview" sandbox></iframe></output>
<output name="output"><img alt="Generated image preview" /></output>

For UTF-8 output, the runtime looks inside each output for pre > code or iframe.

For image/* output, an img child receives an object URL for the rendered image. This is the right view for SVG, PNG, JPEG, GIF, BMP, ICO, or WebP output. If there is no img, SVG and other UTF-8 image formats can still be shown as text with pre > code.

Binary output without a matching view falls back to a short hex listing in the <output>.

Binary Input

Text inputs cover UTF-8 transforms; binary payloads have two HTML-native forms.

A <source name="input"> declares the input. The runtime fetches it, and its type attribute becomes the pipeline's initial content type:

<qip-edit>
  <source name="input" src="/data/countries.sqlite" type="application/vnd.sqlite3" />
  <source src="/application/vnd.sqlite3/sqlite-schema.wasm" type="application/wasm" />
  <output name="output"><pre><code></code></pre></output>
</qip-edit>

Every other <source> is a pipeline stage. The naming follows the element's existing name="output" and name="uniform-*" wiring, and the input can itself be a wasm module — for example, feeding one into wasm-strict-profile.wasm:

<qip-edit>
  <source name="input" src="/text/luhn.wasm" type="application/wasm" />
  <source src="/application/wasm/wasm-strict-profile.wasm" type="application/wasm" />
  <output name="output"><pre><code></code></pre></output>
</qip-edit>

With an input <source> present, a separate input control is optional. data: URLs work for inlining small payloads.

An <input type="file" name="input"> supplies the chosen file's bytes and content type instead. With no file selected it falls back to the input <source>, so a page can ship default data that visitors override with their own file:

<qip-edit>
  <source name="input" src="/data/countries.sqlite" type="application/vnd.sqlite3" />
  <source src="/application/vnd.sqlite3/sqlite-schema.wasm" type="application/wasm" />
  <input type="file" name="input" accept=".sqlite,application/vnd.sqlite3" />
  <output name="output"><pre><code></code></pre></output>
</qip-edit>

Precedence: a chosen file wins, then the input <source>, then the input element's text.

Module Policy

Components use fixed memory by default. Add a byte cap when the page needs a tighter resource boundary:

<qip-edit max-memory="67108864">
  <source src="/text/markdown/commonmark.0.31.2.wasm" type="application/wasm" />
  <textarea name="input"># Hello</textarea>
  <output name="output"><pre><code></code></pre></output>
</qip-edit>

These checks run after the module bytes are fetched and before WebAssembly.compile. <qip-view> and <qip-play> accept the same attributes.

<qip-play> #

<qip-play> is the persistent, interaction-oriented element. The component remains instantiated over time, owns or participates in an evolving stateful session, and user events can affect subsequent output.

<qip-play>
  <source src="/interactive/snake.wasm" type="application/wasm" />
</qip-play>

A direct <source> is shorthand for one interactive step. Use ordered <qip-step> wrappers to pass each rendered frame through finite Content post-processing components:

<qip-play debug>
  <qip-step name="desktop">
    <source src="/interactive/macintosh-1bit.wasm" type="application/wasm" />
  </qip-step>
  <qip-step name="duotone">
    <source src="/image/ktx2/ktx2-duotone-to-ktx2-rgba32float-display-p3-linear.wasm"
            type="application/wasm"
            data-uniform-highlight_r="2.4"
            data-uniform-highlight_g="0.42"
            data-uniform-highlight_b="0.015" />
  </qip-step>
</qip-play>

This example maps the light end of the image to HDR orange. Change the three highlight uniforms to author another linear Display P3 color without changing the Interactive component.

The first step must implement Interactive. Later steps must implement finite Content, accept and emit image/ktx2, and must not export Timed or Eventful capabilities. <qip-play> sends pointer and keyboard events only to the first step. It copies each KTX2 result into the next component's bounded input memory and presents only the last result. Timed post-processing is not supported yet.

The first step selects its first matching <source> child through normal media selection. A later step treats matching sources as ordered executable alternatives. On the first frame, <qip-play> calls each candidate until one accepts the input with a successful Content result. It retains that component instance for later frames. A recoverable rejection tries the next source; a trap stops the pipeline because the component instance is no longer safe to reuse.

Every alternative in one step must declare identical, non-empty input and output content types. The input type must also match the preceding step's declared output type. The host loads and validates every candidate before it starts. All downloaded Wasm counts toward the step's byte total. After a source accepts the first frame, the host releases its references to the unselected instances so their fixed memories can be collected. For example, a resize step can choose its direction without putting image-size policy in JavaScript:

<qip-step name="resize">
  <source src="/image/ktx2/ktx2-r8g8b8a8-srgb-resize-down-lanczos3.wasm"
          type="application/wasm"
          data-uniform-width="1200" />
  <source src="/image/ktx2/ktx2-r8g8b8a8-srgb-resize-up-mitchell.wasm"
          type="application/wasm"
          data-uniform-width="1200" />
</qip-step>

Both components declare image/ktx2 -> image/ktx2. The reduction component accepts when 1200 pixels does not enlarge an axis. Otherwise it returns a recoverable rejection and the Mitchell component gets the same input. Do not mix direct <source> children and <qip-step> children. With debug, the statistics include all alternatives' Wasm size, retained memory, the selected source, latest render time, and render count for each named step.

The distinction from <qip-edit> is not merely that both accept interaction. Editing changes declared source inputs and produces finite results; playing interacts with a running component whose state persists between events. A fallback, poster, or initial snapshot may be rendered ahead of time, but the experience requires client activation to become interactive.

The element supports the [Timed and Eventful contract](/docs/timed-and-eventful-components): begin_update_at, complete update uniforms, timestamp-free events, finish_update, separate presentation, and canonical KTX2 output. calculator uses the application-style path. snake combines events with fixed-step scheduled wakes. See also Interactive Component Contract and Interactive Rendering Performance.

Pre-Rendering #

Prefer the terms pre-rendered, build-time rendered, or server-rendered over calling the consumption element "static".

A view is not necessarily unchanging: application code may explicitly rerender it when externally owned data changes. Likewise, an edit may begin with pre-rendered output even though it later becomes reactive. The distinction stays:

view    consume a finite result
edit    author inputs and produce finite results
play    interact with a persistent session

— not static versus dynamic.

Externally Managed Data #

<qip-view> is not intended to own application data loading. Authentication, network requests, caching, retries, and invalidation belong in application code or a developer-defined custom element, which can invoke QIP's rendering functions and control when a component is loaded or rerendered:

<account-usage-report account="123"></account-usage-report>

A custom element such as <account-usage-report> can own authenticated fetching and caching while using QIP internally for deterministic rendering.