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.

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>) for use in your templates.

js

                 
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.

html

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

js

                 
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.

html

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

html

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

js

                 
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.

html

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

js

                 
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.

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