Custom Elements
Build reusable and encapsulated UI blocks with PawaJS custom elements (components).
What are Components?
In PawaJS, a component is a JavaScript function that returns an HTML template string. These functions encapsulate their own logic, state, and markup, making your UI modular, reusable, and easier to manage.
Defining a Component
A component is simply a JavaScript / TypeScript function. It can accept arguments (props)
and should return an html template literal.
import { html, $state, useInsert } from 'pawajs';
const MyButton = ({ label }) => {
const clicks = $state(0);
const handleClick = () => {
clicks.value++;
console.log('Button clicked:', clicks.value);
};
useInsert({ clicks, handleClick, label });
return html`
<button on-click="handleClick()" class="px-4 py-2 bg-blue-500 text-white rounded-md">
@{label()} (@{clicks.value} clicks)
</button>
`;
};
Registering a Component
Before you can use your component in HTML, you need to register it globally using
RegisterComponent. PawaJS automatically converts PascalCase component names
(like MyButton) to kebab-case (<my-button>).
import { RegisterComponent } from 'pawajs';
import { MyButton } from './MyButton.js';
RegisterComponent(MyButton);
Using Components in HTML
Once registered, you can use your component as a custom HTML element.
<my-button></my-button>
Template Elements
PawaJS also works with the native HTML <template> element.
A template is treated as an inert content container which PawaJS immediately inserts
into the live DOM. This is useful for unstyled elements and progressive rendering.
<div class="card" state-name="'Jake'">
<template>
<h2>@{name.value}</h2>
</template>
<p>my paragraph</p>
</div>
You can also use PawaJS directives such as if, else-if,
and for-each on ordinary <template> elements.
Lazy Components
For larger applications you can lazy-load components. The component’s JavaScript is only fetched when the element is actually encountered in the DOM.
import { RegisterComponent } from 'pawajs';
// Single component
RegisterComponent.lazy('LazyWidget', () => import('./LazyWidget.js'));
// Multiple components from one bundle
RegisterComponent.lazy(
['NavComponent', 'FooterComponent'],
() => import('./LayoutBundle.js')
);
The as-child Attribute
When present on a custom element, as-child tells PawaJS to merge the
custom element’s attributes and event listeners onto its first child element instead
of rendering a wrapper. This is ideal for headless UI components.
<!-- class and on-click are applied to the native <button> -->
<my-button as-child class="px-4 py-2 bg-blue-500" on-click="doSomething()">
<button>Submit</button>
</my-button>
Passing Props
Props are passed as getter functions (except for children).
This allows the child to reactively track changes.
<my-button label="Custom Label"></my-button>
<my-button :label="buttonText.value"></my-button>
const MyButton = ({ label }) => {
useInsert({ label });
return html`<button>@{label()}</button>`;
};
For a full explanation of reactive vs static props and attribute fallthrough, see the Props System documentation.
Children (Slots)
Content placed between a component’s tags is received as the children prop.
Unlike other props, children is a raw HTML string.
<my-card>
<h2>Card Title</h2>
<p>This is the card content.</p>
</my-card>
const MyCard = ({ children }) => {
return html`
<div class="border p-4 rounded-lg">
${children}
</div>
`;
};
NamedSlots
For components that need multiple content areas, use named slots with
<template prop="name">. Named slots are received as getter functions.
<my-modal>
<template prop="header">
<h2>Modal Title</h2>
</template>
<p>This content goes into the default children prop.</p>
<template prop="footer">
<button>Close</button>
</template>
</my-modal>
const MyModal = ({ header, footer, children }) => {
return html`
<div class="modal">
<header>${header()}</header>
<main>${children}</main>
<footer>${footer()}</footer>
</div>
`;
};
Putting It All Together
Here’s how a parent component might use a child component with props and children:
<!-- In a parent component's template -->
<my-button :label="buttonText.value">
<span>Dynamic Button</span>
</my-button>
<my-card>
<h3>A Dynamic Card</h3>
<p>Message from parent: @{parentMessage.value}</p>
</my-card>
Page Not Found
We couldn't find the page you're looking for. It might have been moved or deleted.
Go back home