Overview

portal mounts a reactive template outside the component tree. popup anchors a floating panel to an element.

Both clean up when the host disconnects. Call them from the template (inner) function so they stay reactive.

Pick an overlay

API
Role
Package
portal Any host node Fixed UI, badges, and custom overlays.
popup Anchored panel Menus, autocomplete, and pickers.

portal() to document.body

Fixed badge renders outside the component subtree via portal — still reactive and removed when hidden.

import { html, atom, portal, nothing, renderApp } from 'mates';

const App = () => {
const show = atom(true);
return () => html`
<div class="m-col m-gap m-items-center">
<button @click=${() => show.set(v => !v)}>
${show() ? 'Hide' : 'Show'} portal badge
</button>
${show()
? portal(html`
<div class="card card-body" style="position:fixed;right:16px;bottom:16px;z-index:50">
Portaled to body
</div>
`)
: nothing}
</div>
`;
};
renderApp(App, document.getElementById('app'));

popup() teaser

Controlled popup on a button. Deep options live on the popup leaf.

import { html, atom, popup, renderApp } from 'mates';

const App = () => {
const open = atom(false);
const menu = html`
<div class="card card-body">
<button class="btn-ghost btn-block m-t-10" @click=${() => open.set(false)}>Close</button>
</div>
`;
return () => html`
<button class="btn-primary"
${popup(menu, { open, position: 'bottom-start', gap: 8 })}
@click=${() => open.set(v => !v)}>Menu ▾</button>
`;
};
renderApp(App, document.getElementById('app'));