Skip to content

Conversation

@laurakwhit
Copy link
Collaborator

@laurakwhit laurakwhit commented Jan 22, 2026

Description & motivation 💭

This PR introduces a new Portal component to the Holocene design system and integrates it with the existing Menu component via an opt-in usePortal prop.

Portal renders children at a specific position relative to an anchor element with advanced positioning features:

  • Renders content in document.body to escape overflow/z-index constraints
  • Has 8 position options (top, bottom, left, right, top-left, top-right, bottom-left, bottom-right)
  • Automatically flips position when content would overflow viewport or scroll container if flipOnCollision is enabled
  • Tracks scrolling in custom containers, not just window with the optional scrollContainer prop
  • Hides portal content when the anchor element scrolls out of view if hideWhenAnchorHidden is enabled
<script>
  import { Portal } from '$lib/holocene/portal';
  import Button from '$lib/holocene/button.svelte';

  let isOpen = $state(false);
</script>

<Button id="portal-trigger" on:click={() => (isOpen = !isOpen)}>Toggle Portal</Button>

<Portal anchor="portal-trigger" open={isOpen}>
  <div>Portal content</div>
</Portal>

Screenshots (if applicable) 📸

Design Considerations 🎨

Testing 🧪

How was this tested 👻

  • Manual testing
  • E2E tests added
  • Unit tests added
  • Storybook stories

Steps for others to test: 🚶🏽‍♂️🚶🏽‍♀️

Checklists

Draft Checklist

Merge Checklist

Issue(s) closed

Related to DT-3325

Docs

Any docs updates needed?

@vercel
Copy link

vercel bot commented Jan 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
holocene Ready Ready Preview, Comment Jan 23, 2026 5:42pm

Request Review

Copy link
Contributor

@andrewzamojc andrewzamojc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just curious about the positioning props on the portal.

let {
anchor,
open = true,
position = 'bottom',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this mixing concepts between portal and overlay? I haven't gone this deep on portals but I didn't think they had a position like a popover...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is a positioned portal. We could consider having just a portal component and then handling the positioning separately, but I figured for 99% of our use cases we're going to want to position it.

In my experience an overlay wouldn't need to be positioned since it's usually the same (centered) in every instance. Is that not your experience? Curious if you have any more insights into patterns for portals you've seen and liked!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya I see. Basically this is practical and always how we're going to use it 👍

@andrewzamojc
Copy link
Contributor

This is great stuff! Pretty cool to see the guts of a Portal.

Copy link
Contributor

@andrewzamojc andrewzamojc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants