app.component(name, { template, setup }) registers a reusable, named unit
under a kebab-case tag name. Used in markup as a custom tag:
app.component('todo-item', {
template: '<li :class="{ done }">{{ text }} <button @click="handleToggle">✓</button></li>',
setup(props) {
return { handleToggle: () => props.onToggle?.() };
}
});
<todo-item v-for="t in todos" :key="t.id" :text="t.text" :done="t.done" :on-toggle="() => toggle(t.id)"></todo-item>
Every attribute on the tag becomes a prop - :attr ones stay reactively in
sync with the parent, plain ones are static strings. Kebab-case names are
camelCased (:on-toggle → props.onToggle), same as Vue. Props are
already the component's scope, so a plain passthrough prop (text, done
above) needs no setup() at all - only add setup() to compute additional
state or wrap a callback.
The component's scope is isolated - unlike v-if/v-for branches, it does
not inherit the parent's scope. Only props (and whatever setup()
returns) are visible in its template.
This minimal version has no slots or a separate emit API - a
function-valued prop doubles as an emit: the child just calls
props.onToggle?.(), the parent passes :on-toggle="() => toggle(t.id)".
Because there's no build step, a component is just a plain object exported from its own file:
// site-header.js
export const SiteHeader = {
template: `<a href="/">Home</a>`,
setup(props) { return { current: props.current }; }
};
Any page that needs it imports and registers it the same way:
import { createApp } from '@wrium/wrium';
import { SiteHeader } from './site-header.js';
createApp(() => ({ /* ... */ }))
.component('site-header', SiteHeader)
.mount('#app');
Don't name a value returned from setup() the same as an incoming prop it
wraps:
// Wrong - infinite recursion
setup(props) {
return { onClick: () => props.onClick() };
}
props is the live, shared scope object - that assignment overwrites the
incoming callback with a reference to itself, and calling it recurses
forever. Name the wrapper differently:
// Right
setup(props) {
return { handleClick: () => props.onClick() };
}
Continue with Plugins.