Portals
Render components outside the current DOM hierarchy. Essential for Modals, Tooltips, and Hover Cards that need to break out of overflow constraints.
Basic Usage
<!-- 1. Define a Target in your layout (usually in index.html) -->
<div id="portal-root"></div>
<!-- 2. Your Component Location -->
<div id="app">
<p>The content below is technically rendered here in JS, but placed elsewhere in the DOM.</p>
</div>
<script>
const target = document.getElementById('portal-root');
// API: pp.createPortal(htmlString | HTMLElement, targetElement)
pp.createPortal(`
<div class="fixed bottom-4 right-4 bg-blue-600 text-white p-4 rounded shadow-lg">
I am a notification via Portal!
</div>
`, target);
</script>
1. The Target
You need a container (e.g., #portal-root) usually placed at the end of your <body> tag to receive the content.
2. Form Ownership
PulsePoint automatically handles form attribute binding. Inputs inside a portal will still submit with their owner form.
3. Event Bubbling
Even though the DOM node is moved, PulsePoint creates synthetic event delegation so interactions feel seamless.
Interactive Dialog
Best Practice
A real-world example combining pp.state, pp.effect, and Portals to create a modal with an overlay.
<button onclick="setOpen(true)" class="btn-primary">Open Dialog</button>
<!-- The Portal Container -->
<div id="dialog-portal" hidden="true">
<div class="fixed inset-0 z-50 bg-black/50" onclick="setOpen(false)"></div>
<div class="fixed top-[50%] left-[50%] z-50 translate-x-[-50%] translate-y-[-50%] bg-background p-6 rounded-lg border shadow-lg w-full max-w-md">
<h2 class="text-lg font-semibold">Edit Profile</h2>
<p class="text-sm text-muted-foreground mt-2">
Make changes to your profile here. Click save when you're done.
</p>
<div class="flex justify-end gap-2 mt-4">
<button onclick="setOpen(false)" class="px-4 py-2 bg-muted rounded">Cancel</button>
<button class="px-4 py-2 bg-primary text-primary-foreground rounded">Save</button>
</div>
</div>
</div>
<script>
// 1. Initialize State
const [open, setOpen] = pp.state(false);
const portalContent = document.getElementById('dialog-portal');
const root = document.getElementById('portal-root'); // Ensure this exists in body
// 2. Create the Portal instance
pp.createPortal(portalContent, root);
// 3. Reactive Visibility Logic
pp.effect(() => {
if (open) {
portalContent.hidden = false;
document.body.style.overflow = 'hidden'; // Lock scroll
} else {
portalContent.hidden = true;
document.body.style.overflow = ''; // Unlock scroll
}
}, [open]);
</script>