Web Components API
Dathra providesdefineComponent()to create custom elements with encapsulated
Shadow DOM, reactive prop signals, Declarative Shadow DOM (DSD) for SSR, and island
hydration support.
For exact signatures and extension-oriented details, see the Components API Reference.
defineComponent()
Define a custom element with reactive props and scoped styles:
import { defineComponent, css } from "@dathra/components";
import { signal } from "@dathra/core";
const MyCounter = defineComponent(
"my-counter",
({ props }) => {
const count = signal(props.initial.value ?? 0);
return (
<div>
<p>Count: {count.value}</p>
<button onClick={() => count.set(count.value + 1)}>+</button>
</div>
);
},
{
props: {
initial: { type: Number, default: 0 },
},
styles: [css`
:host { display: block; padding: 16px; }
button { border-radius: 8px; }
`],
},
);Count: {count.value}
defineComponentregisters the custom element with the browser via customElements.define(), creates a Shadow Root, applies adoptedStyleSheets, and reflects attributes as reactive signals.
SSR with DSD
When rendered on the server, Dathra generates Declarative Shadow DOM markup — the Shadow
Root content is serialized as a<template shadowrootmode="open">inside
the custom element. The browser recreates the Shadow DOM from the template without any
JavaScript.
import { renderDSD } from "@dathra/components/ssr";
const html = renderDSD(MyCounter, { initial: 5 });
// <my-counter initial="5">
// <template shadowrootmode="open">
// <style>:host { display: block; padding: 16px; } ...</style>
// <div>Count: 5<button>+</button></div>
// </template>
// </my-counter>Hydration
When a DSD-rendered element upgrades on the client, Dathra preserves the existing Shadow DOM
by default. Compiler-generated hydration plans attach supported text, attr, event, and
insert bindings in place, so most components do not need a customhydrate option.
const AppRoot = defineComponent(
"app-root",
({ props }) => {
return <main>{props.route.value}</main>;
},
{
props: {
route: { type: String },
},
styles: [baseStyles],
},
);If a component is intentionally client-rendered after SSR, opt in to the legacy replacement fallback explicitly:
defineComponent("client-only-widget", setup, {
hydration: {
unsupported: "rerender",
},
});CSS Helpers
import { css, adoptGlobalStyles } from "@dathra/components";
const theme = css`
dathra-docs {
--accent: #1e6b55;
}
`;
adoptGlobalStyles(theme);Nested Components & Tree-Shaking
When a Web Component is used as a JSX tag inside another component's template (e.g. <MobileNav />), the SSR renderer detects the hyphenated tag name and
generates DSD for the nested component automatically. The import must be a value reference
in the file, not a side-effect-only import, or the bundler may tree-shake the component
registration.
Component Registration
defineComponentinternally callsregisterComponent()to register
the component in the SSR registry. You can query or clear the registry directly:
import { hasComponent, getComponent, clearRegistry } from "@dathra/components";
hasComponent("my-counter"); // true
getComponent("my-counter"); // ComponentRegistration