Components
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>
How Props Work
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.
Scope Isolation
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.
No Slots, No Emit
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)".
Sharing a Component Across Pages
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');
Footgun to Avoid
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.