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 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>) for use in your templates.
import { RegisterComponent } from 'pawajs';
import { MyButton } from './MyButton.js'; // Assuming MyButton is in a separate file
RegisterComponent(MyButton);
Using Components in HTML
Once registered, you can use your component as a custom HTML element.
<my-button></my-button>
Lazy Components
For larger applications, PawaJS allows you to lazy-load components. This means the component's JavaScript bundle is only fetched and parsed when the component is actually encountered in the DOM, improving initial load times.
import { RegisterComponent } from 'pawajs';
// Register a single component to be lazy-loaded
RegisterComponent.lazy('LazyWidget', () => import('./LazyWidget.js'));
// Register multiple components from a single bundle
RegisterComponent.lazy(['NavComponent', 'FooterComponent'], () => import('./LayoutBundle.js'));
When a lazy component is registered, PawaJS will automatically observe its presence in the DOM and trigger the import when needed.
The as-child Attribute
The as-child attribute is a powerful pattern for building flexible UI libraries. When present on a custom element, it instructs PawaJS to "merge" the custom element's attributes and event listeners directly onto its first child element, rather than rendering the custom element as a wrapper. This is particularly useful for headless UI components that provide behavior without imposing specific DOM structure.
<!-- The 'class' and 'on-click' from my-button will apply 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 how you pass data from a parent to a child. In PawaJS, props are passed as getter functions (except for children). This allows the child component to reactively track changes to the prop value.
<my-button label="Custom Label"></my-button>
<my-button :label="buttonText.value"></my-button>
Inside the child component, you access the prop by calling it as a function.
const MyButton = ({ label }) => {
useInsert({ label });
return html`<button>@{label()}</button>`;
};
For a detailed explanation of how props and event handlers work, including reactive vs. static props and attribute fallthrough, please refer to the dedicated Props System documentation.
Children (Slots)
Content placed between a component's tags is passed as the children prop. Unlike other props, children is a raw HTML string, not a getter.
<my-card>
<h2>Card Title</h2>
<p>This is the card content.</p>
</my-card>
Access the children in your component function and render them using {children}.
const MyCard = ({ children }) => {
return html`
<div class="border p-4 rounded-lg">
${children}
</div>
`;
};
Named Slots
For more complex components requiring multiple content areas, you can use Named Slots with the <template prop="name"> syntax. Like standard props, 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">
<!-- Children content is not typically used for simple buttons, but shown for illustration -->
<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