Skip to content

portal()

The portal() function renders content into a different DOM location — useful for modals, tooltips, and overlays that need to escape their parent’s overflow or z-index context.

portal()(child)
portal(target)(child)

target (optional) : Where to render the content. Can be:

  • A DOM element
  • A function returning a DOM element
  • A reactive value (signal) containing an element
  • Omitted to use document.body

child : The element spec to render at the target location.

A spec for use with mount() or as a child of other elements.

Portals let you render content outside the normal DOM hierarchy while keeping it logically connected to its parent component:

el('div').props({ className: 'container' })(
el('button')('Open Modal'),
portal()(
el('div').props({ className: 'modal' })('Modal content')
)
)

The modal is a child of the container in your code, but renders to document.body in the DOM.

Combine with match() for conditional portals:

const showModal = signal(false);
match(showModal, (show) =>
show
? portal()(ModalContent())
: null
)
const App = () => {
const showModal = signal(false);
return el('div')(
el('button').props({
onclick: () => showModal(true)
})('Open Modal'),
match(showModal, (show) =>
show
? portal()(
el('div').props({ className: 'modal-backdrop' })(
el('div').props({ className: 'modal' })(
el('h2')('Modal Title'),
el('p')('Content here...'),
el('button').props({
onclick: () => showModal(false)
})('Close')
)
)
)
: null
)
);
};
// Render to a specific element
portal(() => document.getElementById('tooltip-container'))(
el('div').props({ className: 'tooltip' })('Tooltip text')
)
const targetRef = signal<HTMLElement | null>(null);
el('div')(
// Capture the target element
el('div').props({ id: 'dropdown-anchor' }).ref(
(node) => {
targetRef(node);
return () => targetRef(null);
}
)(),
// Portal renders next to the anchor
portal(targetRef)(
el('ul').props({ className: 'dropdown' })(
el('li')('Option 1'),
el('li')('Option 2')
)
)
)
const Tooltip = (anchor: HTMLElement, text: string) => {
const position = signal({ x: 0, y: 0 });
// Calculate position based on anchor
const rect = anchor.getBoundingClientRect();
position({ x: rect.left, y: rect.bottom + 8 });
return portal()(
el('div').props({
className: 'tooltip',
style: computed(() =>
`position: fixed; left: ${position().x}px; top: ${position().y}px;`
),
})(text)
);
};
  • match() — Conditional rendering (often used with portals)
  • mount() — Alternative for one-off DOM insertion