Skip to content

Components

A Rivet component connects a piece of server-rendered HTML with behaviour. It is defined with defineComponent, registered on the app, and instantiated on mount().

defineComponent is just a type-safe identity wrapper: it takes a factory function and returns it unchanged. The benefit is type inference for the context and the returned API.

import { defineComponent } from "@fullhaus/rivet";
export const counter = defineComponent(({ part, signal, on, effect }) => {
const output = part("output");
const count = signal(0);
on(part("inc"), "click", () => count.update((v) => v + 1));
effect(() => {
output.textContent = String(count());
});
return { reset: () => count.set(0) };
});

The factory receives one argument: the component context. Whatever it returns becomes the instance’s public API.

import { createRivet } from "@fullhaus/rivet";
import { counter } from "./counter";
const app = createRivet()
.component("counter", counter) // name must match data-rivet="counter"
.mount();

The name given to component(name, …) must match the data-rivet attribute in the HTML. On mount():

  1. all [data-rivet] elements are found,
  2. the matching factory is looked up by name,
  3. a context is created and the factory is called,
  4. the returned API is registered under the data-rivet-id (if present).

Already-mounted elements are skipped — mount() is idempotent and can be called again after loading more HTML.

Inside the component root, child elements are addressed via data-rivet-part:

<div data-rivet="counter" data-rivet-id="c1">
<output data-rivet-part="output">0</output>
<button data-rivet-part="inc">+1</button>
</div>
const output = part("output"); // exactly one — throws if not found
const buttons = parts("inc"); // array, possibly empty
  • part(name) returns exactly one element and throws an error with component context if none exists.
  • parts(name) returns all matching elements as an array.

The same component can appear in the document any number of times. Each root becomes an independent instance with its own state and its own cleanup.

<div data-rivet="counter" data-rivet-id="left"></div>
<div data-rivet="counter" data-rivet-id="right"></div>
app.require("left").reset();
app.require("right").reset();

The factory’s return value is the public API. Components without an ID still work (behaviour runs), their API is just not retrievable from the outside.

const menu = app.require<{ hide(): void }>("main-menu");
menu.hide();

Pass a type parameter to get/require to use the API in a type-safe way.