Build an HTML Next component
Write a workshop check-in counter once in HTML, then use it natively or in Vue, React, Preact, Svelte, or Angular.
Write the component
Save this as counter.html: each click counts a guest, and Reset starts the next session.
<template component="x-counter">
<defs>
<state name="count" type="number" value="0"></state>
<handler name="increment">
<set name="count" expr:value="$count + 1"></set>
</handler>
<handler name="reset">
<set name="count" expr:value="0"></set>
</handler>
</defs>
<div>
<button type="button" on:click="increment">
Count: {$count}
</button>
<button type="button" on:click="reset">Reset</button>
</div>
</template>Nothing to convert: the browser runtime loads counter.html as written, or Vite builds it into native DOM factories. Set up HTML Next.
The Vue adapter generates this component when your app builds. Set up Vue.
<!-- Generated by HTML Next 1.0.0-alpha.94 for Vue 3.5. Do not edit. -->
<script setup lang="ts">
import { ref } from 'vue'
import { checkHydratedRoot, decimalAdd, nativeAttrs, rootMetadata } from './host'
import { forwardedAttributes } from './attributes'
defineOptions({ inheritAttrs: false })
const count = ref(0)
checkHydratedRoot('div', 'x-counter')
function increment(): void {
count.value = decimalAdd(count.value, 1)
}
function reset(): void {
count.value = 0
}
</script>
<template>
<div
data-hn
v-bind="{ ...rootMetadata($attrs), ...forwardedAttributes(nativeAttrs($attrs)) }"
><button type="button" @click="increment">Count: {{ count }}</button>{{ ' ' }}<button
type="button"
@click="reset"
>Reset</button></div>
</template>The React adapter generates this component when your app builds. Set up React.
// Generated by HTML Next 1.0.0-alpha.94 for React 19. Do not edit.
import React from "react";
import type { ReactNode } from "react";
import { styleObject, applyStylePriority } from "./style/style.js";
import { forwardedAttributes } from "./attributes";
import { useNativeEvents } from "./events";
import { decimalAdd, rememberParent, rootMetadata, text, useLiveState, useRootHandle } from "./render";
export type XCounterProps = Omit<React.ComponentPropsWithoutRef<"div">, "style" | "children"> & {
style?: React.CSSProperties | (React.CSSProperties & {
[name: `--${string}`]: string | number | undefined;
});
children?: ReactNode | (() => ReactNode);
slots?: Readonly<Record<string, ReactNode | ((props: Record<string, any>) => ReactNode)>>;
ref?: React.Ref<HTMLDivElement>;
[name: string]: unknown;
};
export default function XCounter(props: XCounterProps) {
const { children: _children, slots: _slots, ...nativeAttrs } = props;
const renderedIdentity = nativeAttrs["component"] ?? nativeAttrs["data-component"];
delete nativeAttrs["component"];
delete nativeAttrs["data-component"];
const [count, setCount, getCount] = useLiveState<number>(0);
const rootRef = React.useRef<HTMLDivElement | null>(null);
useRootHandle(rootRef, props.ref);
function increment(): void {
setCount((count) => decimalAdd(count, 1));
}
function reset(): void {
setCount(0);
}
const styleRefattachEvents0 = React.useRef<Element | null>(null);
React.useLayoutEffect(() => { applyStylePriority(styleRefattachEvents0.current, (Object.hasOwn(forwardedAttributes(nativeAttrs), "style") ? styleObject(nativeAttrs.style) : undefined)); });
const attachEvents0 = React.useCallback((element: Element | null, paths?: readonly (((() => readonly (string | number)[]) | undefined))[]) => { const restoreParent = rememberParent(element); rootRef.current = element as HTMLDivElement | null; styleRefattachEvents0.current = element; return () => { restoreParent(); rootRef.current = null; styleRefattachEvents0.current = null; }; }, []);
const incrementEvents = useNativeEvents([{ type: "click", modifiers: [], handler: increment }]);
const resetEvents = useNativeEvents([{ type: "click", modifiers: [], handler: reset }]);
return (<div {...rootMetadata(nativeAttrs)} {...forwardedAttributes(nativeAttrs, ["style"])} style={((Object.hasOwn(forwardedAttributes(nativeAttrs), "style") ? styleObject(nativeAttrs.style) : undefined)) as React.CSSProperties} ref={attachEvents0} data-html-next-renderer="react" {...{ component: [renderedIdentity, "x-counter"].filter(Boolean).join(" ") }}><button type="button" ref={incrementEvents}>{`Count: ${text(count)}`}</button>{" "}<button type="button" ref={resetEvents}>Reset</button></div>);
}The Preact adapter generates TSX using Preact's runtime and public types. Set up Preact, then import the definition:
/** @jsxImportSource preact */
import { XCounter } from "./counter.html";
export default function App() {
return <XCounter />;
}The Svelte adapter generates this component when your app builds. Set up Svelte.
<!-- Generated by HTML Next 1.0.0-alpha.94 for Svelte 5. Do not edit. -->
<script lang="ts">
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import { forwardedAttributes } from './attributes';
import { decimalAdd, nativeAttributes, rootAttributes } from './render.svelte';
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'slots'> & {
children?: Snippet;
slots?: Record<string, Snippet<[Record<string, any>]> | null>;
[key: string]: unknown;
};
let { children, slots, ...restProps }: Props = $props();
const rest = restProps as Record<string, unknown>;
const rootAttrs = rootAttributes(() => rest, { metadataOnly: true });
let count = $state<number>(0);
function increment(): void {
count = decimalAdd(count, 1);
}
function reset(): void {
count = 0;
}
</script>
<div
{...nativeAttributes({ ...forwardedAttributes(rest), "data-hn": "", ...rootAttrs() })}
><button type="button" onclick={increment}>Count: {count}</button>
<button type="button" onclick={reset}>Reset</button
></div>The Angular adapter generates a standalone component with a native attribute selector. The counter's root is a div, so keep that element in your template. Set up Angular, including its AOT compiler and generated type configuration:
import { Component } from "@angular/core";
import { XCounter } from "./counter.html";
@Component({
selector: "app-root",
standalone: true,
imports: [XCounter],
template: `<div x-counter></div>`,
})
export class App {}Keep learning
The proposal's component model, bindings, reactivity, and styles chapters are the full authoring reference, including scoped <style> and controller modules.