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.

js

                 
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>).

js

                 
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.

html

                 
<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.

html

                 
<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.

js

                 
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.

html

                 
<!-- 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.

html

                 
<my-button label="Custom Label"></my-button>
<my-button :label="buttonText.value"></my-button> 

        
js

                 
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.

html

                 
<my-card>
    <h2>Card Title</h2>
    <p>This is the card content.</p>
</my-card> 

        
js

                 
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.

Usage Example
html

                 
<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> 

        
js

                 
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:

html

                 
<!-- 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>