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.

Wrium v1.0.0 · MIT License