Skip to main content
Light Dark System

Popup

<zn-popup> | ZnPopup
Since 1.0 experimental

Short summary of the component’s intended use.

Popups use Floating UI under the hood to provide robust positioning for floating elements. The popup component exposes all of the underlying positioning features and allows you to anchor any element to any other element.

Popups are designed to work with anchor elements and popup content. The anchor can be provided through the anchor attribute (by ID or element reference) or by slotting an element into the anchor slot.

<div class="popup-overview">
  <zn-popup placement="top" active>
    <span slot="anchor" style="
      display: inline-block;
      width: 150px;
      height: 150px;
      background: var(--zn-color-primary-600);
      border-radius: 4px;
    "></span>

    <div style="
      background: var(--zn-color-neutral-900);
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 4px;
    ">
      I'm a popup!
    </div>
  </zn-popup>
</div>

<style>
  .popup-overview {
    display: flex;
    justify-content: center;
    padding: 3rem;
  }
</style>

Examples

Activating the Popup

Popups are inactive by default. They won’t be positioned until you add the active attribute. The popup logic can be computationally expensive, so only activate popups when needed.

<div class="popup-active">
  <zn-popup placement="top" active>
    <span slot="anchor" style="
      display: inline-block;
      width: 100px;
      height: 100px;
      background: var(--zn-color-success-600);
      border-radius: 4px;
    "></span>

    <div style="
      background: var(--zn-color-neutral-900);
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 4px;
    ">
      Active
    </div>
  </zn-popup>

  <zn-popup placement="top">
    <span slot="anchor" style="
      display: inline-block;
      width: 100px;
      height: 100px;
      background: var(--zn-color-neutral-300);
      border-radius: 4px;
      margin-left: 2rem;
    "></span>

    <div style="
      background: var(--zn-color-neutral-900);
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 4px;
    ">
      Inactive
    </div>
  </zn-popup>
</div>

<style>
  .popup-active {
    display: flex;
    justify-content: center;
    padding: 3rem;
  }
</style>

External Anchors

By default, anchors are slotted into the popup using the anchor slot. If your anchor needs to live outside of the popup, you can pass its id, a DOM reference, or a VirtualElement to the anchor property.

<div class="popup-external">
  <div
    id="external-anchor"
    style="
      display: inline-block;
      width: 100px;
      height: 100px;
      background: var(--zn-color-primary-600);
      border-radius: 4px;
    "
  ></div>

  <zn-popup anchor="external-anchor" placement="top" active>
    <div style="
      background: var(--zn-color-neutral-900);
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 4px;
    ">
      Anchored externally
    </div>
  </zn-popup>
</div>

<style>
  .popup-external {
    padding: 3rem;
  }
</style>

Placement

Use the placement attribute to set the preferred placement of the popup. Note that the actual placement may vary to keep the popup inside the viewport when using positioning features like flip and shift.

Since placement is preferred when the popup is active, the popup will use the data-current-placement attribute on the host element to reflect the actual placement at any given time. This allows you to style the popup based on its current placement if needed.

<div class="popup-placement">
  <zn-popup placement="top-start" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">top-start</span>
  </zn-popup>

  <zn-popup placement="top" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">top</span>
  </zn-popup>

  <zn-popup placement="top-end" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">top-end</span>
  </zn-popup>

  <br><br>

  <zn-popup placement="left-start" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">left-start</span>
  </zn-popup>

  <zn-popup placement="right-start" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">right-start</span>
  </zn-popup>

  <br><br>

  <zn-popup placement="left" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">left</span>
  </zn-popup>

  <zn-popup placement="right" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">right</span>
  </zn-popup>

  <br><br>

  <zn-popup placement="left-end" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">left-end</span>
  </zn-popup>

  <zn-popup placement="right-end" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">right-end</span>
  </zn-popup>

  <br><br>

  <zn-popup placement="bottom-start" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">bottom-start</span>
  </zn-popup>

  <zn-popup placement="bottom" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">bottom</span>
  </zn-popup>

  <zn-popup placement="bottom-end" active>
    <span slot="anchor"></span>
    <span class="popup-placement-label">bottom-end</span>
  </zn-popup>
</div>

<style>
  .popup-placement {
    padding: 3rem;
  }

  .popup-placement zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 75px;
    height: 50px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
    margin: 0.5rem;
  }

  .popup-placement-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Distance

Use the distance attribute to change the distance between the popup and its anchor. A positive value moves the popup farther away, while a negative value moves it closer.

<div class="popup-distance">
  <zn-popup placement="top" distance="0" active>
    <span slot="anchor"></span>
    <span class="popup-distance-label">Distance: 0</span>
  </zn-popup>

  <zn-popup placement="top" distance="10" active>
    <span slot="anchor"></span>
    <span class="popup-distance-label">Distance: 10</span>
  </zn-popup>

  <zn-popup placement="top" distance="20" active>
    <span slot="anchor"></span>
    <span class="popup-distance-label">Distance: 20</span>
  </zn-popup>
</div>

<style>
  .popup-distance {
    display: flex;
    gap: 4rem;
    padding: 3rem;
  }

  .popup-distance zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-distance-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Skidding

Use the skidding attribute to move the popup along the anchor. This is useful for fine-tuning the popup’s position.

<div class="popup-skidding">
  <zn-popup placement="top" skidding="-50" active>
    <span slot="anchor"></span>
    <span class="popup-skidding-label">Skidding: -50</span>
  </zn-popup>

  <zn-popup placement="top" skidding="0" active>
    <span slot="anchor"></span>
    <span class="popup-skidding-label">Skidding: 0</span>
  </zn-popup>

  <zn-popup placement="top" skidding="50" active>
    <span slot="anchor"></span>
    <span class="popup-skidding-label">Skidding: 50</span>
  </zn-popup>
</div>

<style>
  .popup-skidding {
    display: flex;
    gap: 4rem;
    padding: 3rem;
  }

  .popup-skidding zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-skidding-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Arrows

Add an arrow to the popup with the arrow attribute. The arrow can be styled using the --arrow-size and --arrow-color custom properties. You can also target ::part(arrow) in your stylesheet for additional customizations.

<div class="popup-arrow">
  <zn-popup placement="top" arrow active>
    <span slot="anchor"></span>
    <span class="popup-arrow-label">Top</span>
  </zn-popup>

  <zn-popup placement="bottom" arrow active>
    <span slot="anchor"></span>
    <span class="popup-arrow-label">Bottom</span>
  </zn-popup>

  <zn-popup placement="left" arrow active>
    <span slot="anchor"></span>
    <span class="popup-arrow-label">Left</span>
  </zn-popup>

  <zn-popup placement="right" arrow active>
    <span slot="anchor"></span>
    <span class="popup-arrow-label">Right</span>
  </zn-popup>
</div>

<style>
  .popup-arrow {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-arrow zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 80px;
    height: 80px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-arrow-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Arrow Placement

Use the arrow-placement attribute to control how the arrow is positioned. The default is anchor, which aligns the arrow as close to the center of the anchor as possible. You can also use start, end, or center to align the arrow to the start, end, or center of the popup instead.

<div class="popup-arrow-placement">
  <zn-popup placement="top" arrow arrow-placement="start" active>
    <span slot="anchor"></span>
    <span class="popup-arrow-placement-label">start</span>
  </zn-popup>

  <zn-popup placement="top" arrow arrow-placement="anchor" active>
    <span slot="anchor"></span>
    <span class="popup-arrow-placement-label">anchor (default)</span>
  </zn-popup>

  <zn-popup placement="top" arrow arrow-placement="center" active>
    <span slot="anchor"></span>
    <span class="popup-arrow-placement-label">center</span>
  </zn-popup>

  <zn-popup placement="top" arrow arrow-placement="end" active>
    <span slot="anchor"></span>
    <span class="popup-arrow-placement-label">end</span>
  </zn-popup>
</div>

<style>
  .popup-arrow-placement {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-arrow-placement zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 60px;
    height: 60px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-arrow-placement-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Arrow Padding

Use the arrow-padding attribute to add padding between the arrow and the edges of the popup. This prevents the arrow from overflowing corners when the popup has a border radius.

<div class="popup-arrow-padding">
  <zn-popup placement="top" arrow arrow-padding="0" active>
    <span slot="anchor"></span>
    <span class="popup-arrow-padding-label">Padding: 0</span>
  </zn-popup>

  <zn-popup placement="top" arrow arrow-padding="10" active>
    <span slot="anchor"></span>
    <span class="popup-arrow-padding-label">Padding: 10</span>
  </zn-popup>

  <zn-popup placement="top" arrow arrow-padding="20" active>
    <span slot="anchor"></span>
    <span class="popup-arrow-padding-label">Padding: 20</span>
  </zn-popup>
</div>

<style>
  .popup-arrow-padding {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-arrow-padding zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-arrow-padding-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 12px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Flip

When the popup doesn’t have enough room in its preferred placement, it can automatically flip to keep it in view. Use the flip attribute to enable this behavior.

<div class="popup-flip">
  <div class="popup-flip-scroll">
    <zn-popup placement="top" flip active>
      <span slot="anchor"></span>
      <span class="popup-flip-label">Scroll down to see me flip</span>
    </zn-popup>
  </div>
</div>

<style>
  .popup-flip {
    height: 200px;
  }

  .popup-flip-scroll {
    height: 150px;
    overflow: auto;
    padding: 3rem;
    padding-bottom: 200px;
  }

  .popup-flip zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-flip-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Flip Fallback Placements

When using the flip attribute, you can specify fallback placements using flip-fallback-placements. The popup will try each placement in order until it finds one that fits.

<div class="popup-flip-fallback">
  <zn-popup placement="top" flip flip-fallback-placements="right bottom left" active>
    <span slot="anchor"></span>
    <span class="popup-flip-fallback-label">Try: top, right, bottom, left</span>
  </zn-popup>
</div>

<style>
  .popup-flip-fallback {
    padding: 3rem;
  }

  .popup-flip-fallback zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-flip-fallback-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Flip Fallback Strategy

When neither the preferred placement nor the fallback placements fit, the flip-fallback-strategy determines the final placement. Use best-fit (default) to position using the best available space, or initial to use the preferred placement.

<div class="popup-flip-strategy">
  <zn-popup placement="top" flip flip-fallback-strategy="best-fit" active>
    <span slot="anchor"></span>
    <span class="popup-flip-strategy-label">best-fit</span>
  </zn-popup>

  <zn-popup placement="top" flip flip-fallback-strategy="initial" active>
    <span slot="anchor"></span>
    <span class="popup-flip-strategy-label">initial</span>
  </zn-popup>
</div>

<style>
  .popup-flip-strategy {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-flip-strategy zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-flip-strategy-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Shift

When a popup is near the edge of the viewport, part of it may be clipped. Use the shift attribute to move it along the axis and keep it in view.

<div class="popup-shift">
  <div class="popup-shift-scroll">
    <zn-popup placement="top" shift active>
      <span slot="anchor"></span>
      <span class="popup-shift-label">Scroll horizontally to see me shift</span>
    </zn-popup>
  </div>
</div>

<style>
  .popup-shift {
    width: 300px;
  }

  .popup-shift-scroll {
    width: 100%;
    overflow: auto;
    padding: 3rem;
    padding-right: 400px;
  }

  .popup-shift zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-shift-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Auto-size

Use the auto-size attribute to prevent the popup from overflowing when clipped. The available width and/or height will be applied to the popup as CSS custom properties (--auto-size-available-width and --auto-size-available-height). Use these properties to set a max-width and/or max-height on the popup.

<div class="popup-auto-size">
  <div class="popup-auto-size-scroll">
    <zn-popup placement="top" auto-size="both" active>
      <span slot="anchor"></span>
      <div class="popup-auto-size-content">
        <p>This popup will resize to fit the available space.</p>
        <p>Scroll around the container to see how it adapts.</p>
        <p>The content will adjust its size automatically.</p>
      </div>
    </zn-popup>
  </div>
</div>

<style>
  .popup-auto-size {
    height: 250px;
  }

  .popup-auto-size-scroll {
    width: 300px;
    height: 200px;
    overflow: auto;
    padding: 3rem;
    padding-bottom: 300px;
    padding-right: 400px;
  }

  .popup-auto-size zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-auto-size-content {
    max-width: var(--auto-size-available-width);
    max-height: var(--auto-size-available-height);
    overflow: auto;
    padding: 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
  }

  .popup-auto-size-content p {
    margin: 0;
    padding: 0.25rem 0;
  }
</style>

Sync Width and Height

Use the sync attribute to make the popup the same width and/or height as the anchor element. This is useful for dropdowns and select controls where the popup should match the trigger’s dimensions.

<div class="popup-sync">
  <zn-popup placement="bottom" sync="width" active>
    <span slot="anchor" style="width: 200px;"></span>
    <span class="popup-sync-label">sync="width"</span>
  </zn-popup>

  <zn-popup placement="bottom" sync="height" active>
    <span slot="anchor" style="height: 80px;"></span>
    <span class="popup-sync-label">sync="height"</span>
  </zn-popup>

  <zn-popup placement="bottom" sync="both" active>
    <span slot="anchor" style="width: 180px; height: 80px;"></span>
    <span class="popup-sync-label">sync="both"</span>
  </zn-popup>
</div>

<style>
  .popup-sync {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-sync zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 150px;
    height: 60px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-sync-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Positioning Strategy

By default, popups use a fixed positioning strategy. This works well in most situations, but if the popup’s anchor is inside a container with overflow: auto|hidden|scroll, the popup may be clipped. You can change the strategy to absolute to position the popup relative to its containing block.

<div class="popup-strategy">
  <div class="popup-strategy-container">
    <h4>Fixed (Default)</h4>
    <zn-popup placement="bottom" strategy="fixed" active>
      <span slot="anchor"></span>
      <span class="popup-strategy-label">Fixed positioning</span>
    </zn-popup>
  </div>

  <div class="popup-strategy-container">
    <h4>Absolute</h4>
    <zn-popup placement="bottom" strategy="absolute" active>
      <span slot="anchor"></span>
      <span class="popup-strategy-label">Absolute positioning</span>
    </zn-popup>
  </div>
</div>

<style>
  .popup-strategy {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-strategy-container {
    position: relative;
    overflow: hidden;
    border: solid 2px var(--zn-color-neutral-300);
    padding: 2rem;
  }

  .popup-strategy-container h4 {
    margin: 0 0 1rem 0;
  }

  .popup-strategy zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-strategy-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Hover Bridge

When a gap exists between the anchor and the popup, it can be difficult to interact with tooltips that show on hover. The hover-bridge attribute fills the gap with an invisible element, making it easier for the pointer to move from the anchor to the popup without dismissing it.

<div class="popup-hover-bridge">
  <zn-popup placement="top" distance="20" hover-bridge active>
    <span slot="anchor"></span>
    <span class="popup-hover-bridge-label">With hover bridge</span>
  </zn-popup>

  <zn-popup placement="top" distance="20" active>
    <span slot="anchor"></span>
    <span class="popup-hover-bridge-label">Without hover bridge</span>
  </zn-popup>
</div>

<style>
  .popup-hover-bridge {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-hover-bridge zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-hover-bridge-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Virtual Elements

In some cases, you may want to position the popup relative to a non-element coordinate. Virtual elements can be used for this purpose. A virtual element must contain a function called getBoundingClientRect() that returns a DOMRect object as shown below.

<div class="popup-virtual-element">
  <div id="virtual-element-container">
    <zn-popup id="virtual-element-popup" placement="top" active>
      <div style="
        background: var(--zn-color-neutral-900);
        color: white;
        padding: 0.5rem 1rem;
        border-radius: 4px;
      ">
        Click anywhere
      </div>
    </zn-popup>
  </div>
</div>

<script>
  const container = document.getElementById('virtual-element-container');
  const popup = document.getElementById('virtual-element-popup');

  let x = 150;
  let y = 50;

  // Create a virtual element
  const virtualElement = {
    getBoundingClientRect: () => ({
      x: x,
      y: y,
      width: 0,
      height: 0,
      top: y,
      left: x,
      right: x,
      bottom: y,
    })
  };

  // Set the anchor to the virtual element
  popup.anchor = virtualElement;

  // Update the virtual element on click
  container.addEventListener('click', (event) => {
    const rect = container.getBoundingClientRect();
    x = event.clientX - rect.left;
    y = event.clientY - rect.top;

    // Force the popup to reposition
    popup.reposition();
  });
</script>

<style>
  .popup-virtual-element #virtual-element-container {
    position: relative;
    height: 300px;
    background: var(--zn-color-neutral-100);
    border: dashed 2px var(--zn-color-neutral-400);
    border-radius: 4px;
    cursor: crosshair;
  }
</style>

Methods

reposition()

Forces the popup to recalculate and reposition itself. This is useful when the popup’s anchor or content changes dynamically.

<div class="popup-reposition">
  <zn-popup id="reposition-popup" placement="top" active>
    <span id="reposition-anchor" slot="anchor"></span>
    <span class="popup-reposition-label">I'll reposition!</span>
  </zn-popup>

  <br><br>

  <zn-button id="reposition-btn">Change Anchor Size</zn-button>
</div>

<script>
  const popup = document.getElementById('reposition-popup');
  const anchor = document.getElementById('reposition-anchor');
  const button = document.getElementById('reposition-btn');

  let size = 100;

  button.addEventListener('click', () => {
    size = size === 100 ? 150 : 100;
    anchor.style.width = size + 'px';
    anchor.style.height = size + 'px';

    // Force reposition after size change
    popup.reposition();
  });
</script>

<style>
  .popup-reposition {
    padding: 3rem;
  }

  .popup-reposition zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
    transition: all 0.3s ease;
  }

  .popup-reposition-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

Slots

Default Slot

The popup’s content is placed in the default slot.

<div class="popup-slot-default">
  <zn-popup placement="top" active>
    <span slot="anchor"></span>
    <div style="
      background: var(--zn-color-neutral-900);
      color: white;
      padding: 1rem;
      border-radius: 4px;
    ">
      <h4 style="margin: 0 0 0.5rem 0;">Custom Content</h4>
      <p style="margin: 0;">Any HTML can go in here!</p>
    </div>
  </zn-popup>
</div>

<style>
  .popup-slot-default {
    padding: 3rem;
  }

  .popup-slot-default zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }
</style>

Anchor Slot

The anchor element is placed in the anchor slot.

<div class="popup-slot-anchor">
  <zn-popup placement="top" active>
    <zn-button slot="anchor">Anchor Button</zn-button>
    <div style="
      background: var(--zn-color-neutral-900);
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 4px;
    ">
      I'm anchored to the button!
    </div>
  </zn-popup>
</div>

<style>
  .popup-slot-anchor {
    padding: 3rem;
  }
</style>

CSS Parts

The popup container. Useful for styling or applying animations.

<div class="popup-part">
  <zn-popup placement="top" active class="popup-custom">
    <span slot="anchor"></span>
    <span class="popup-part-label">Styled with ::part(popup)</span>
  </zn-popup>
</div>

<style>
  .popup-custom::part(popup) {
    background: var(--zn-color-primary-100);
    padding: 0.5rem;
    border-radius: 8px;
  }

  .popup-part {
    padding: 3rem;
  }

  .popup-part zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-part-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

arrow

The arrow element. Use this to style the arrow with custom colors or shapes.

<div class="popup-part-arrow">
  <zn-popup placement="top" arrow active class="popup-custom-arrow">
    <span slot="anchor"></span>
    <span class="popup-part-arrow-label">Styled with ::part(arrow)</span>
  </zn-popup>
</div>

<style>
  .popup-custom-arrow::part(arrow) {
    background: var(--zn-color-success-600);
  }

  .popup-part-arrow {
    padding: 3rem;
  }

  .popup-part-arrow zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-part-arrow-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

hover-bridge

The hover bridge element. Use this to debug or style the invisible hover bridge.

<div class="popup-part-bridge">
  <zn-popup placement="top" distance="20" hover-bridge active class="popup-bridge-visible">
    <span slot="anchor"></span>
    <span class="popup-part-bridge-label">Hover bridge visible</span>
  </zn-popup>
</div>

<style>
  .popup-bridge-visible::part(hover-bridge) {
    background: rgba(255, 0, 0, 0.1);
  }

  .popup-part-bridge {
    padding: 3rem;
  }

  .popup-part-bridge zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-part-bridge-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

CSS Custom Properties

–arrow-size

Controls the size of the arrow. The default is 6px.

<div class="popup-css-arrow-size">
  <zn-popup placement="top" arrow active style="--arrow-size: 4px;">
    <span slot="anchor"></span>
    <span class="popup-css-label">4px</span>
  </zn-popup>

  <zn-popup placement="top" arrow active style="--arrow-size: 8px;">
    <span slot="anchor"></span>
    <span class="popup-css-label">8px</span>
  </zn-popup>

  <zn-popup placement="top" arrow active style="--arrow-size: 12px;">
    <span slot="anchor"></span>
    <span class="popup-css-label">12px</span>
  </zn-popup>
</div>

<style>
  .popup-css-arrow-size {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-css-arrow-size zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 80px;
    height: 80px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-css-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }
</style>

–arrow-color

Controls the color of the arrow. The default is rgb(0, 0, 0, 0.9).

<div class="popup-css-arrow-color">
  <zn-popup placement="top" arrow active style="--arrow-color: var(--zn-color-success-600);">
    <span slot="anchor"></span>
    <span class="popup-css-arrow-color-label popup-css-arrow-color-label--success">Green arrow</span>
  </zn-popup>

  <zn-popup placement="top" arrow active style="--arrow-color: var(--zn-color-error-600);">
    <span slot="anchor"></span>
    <span class="popup-css-arrow-color-label popup-css-arrow-color-label--error">Red arrow</span>
  </zn-popup>

  <zn-popup placement="top" arrow active style="--arrow-color: var(--zn-color-warning-600);">
    <span slot="anchor"></span>
    <span class="popup-css-arrow-color-label popup-css-arrow-color-label--warning">Orange arrow</span>
  </zn-popup>
</div>

<style>
  .popup-css-arrow-color {
    display: flex;
    gap: 3rem;
    padding: 3rem;
  }

  .popup-css-arrow-color zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 80px;
    height: 80px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-css-arrow-color-label {
    display: inline-block;
    padding: 0.5rem 1rem;
    color: white;
    border-radius: 4px;
    white-space: nowrap;
    font-size: 0.875rem;
  }

  .popup-css-arrow-color-label--success {
    background: var(--zn-color-success-600);
  }

  .popup-css-arrow-color-label--error {
    background: var(--zn-color-error-600);
  }

  .popup-css-arrow-color-label--warning {
    background: var(--zn-color-warning-600);
  }
</style>

–auto-size-available-width and –auto-size-available-height

When using the auto-size attribute, these properties are set automatically with the available width and/or height. Use them to constrain your popup content.

<div class="popup-css-auto-size">
  <div class="popup-css-auto-size-scroll">
    <zn-popup placement="top" auto-size="both" active>
      <span slot="anchor"></span>
      <div class="popup-css-auto-size-content">
        This content will resize based on available space. The max-width and max-height are set using the CSS custom properties.
      </div>
    </zn-popup>
  </div>
</div>

<style>
  .popup-css-auto-size {
    height: 200px;
  }

  .popup-css-auto-size-scroll {
    width: 250px;
    height: 150px;
    overflow: auto;
    padding: 3rem;
    padding-bottom: 250px;
    padding-right: 350px;
  }

  .popup-css-auto-size zn-popup span[slot="anchor"] {
    display: inline-block;
    width: 100px;
    height: 100px;
    background: var(--zn-color-primary-600);
    border-radius: 4px;
  }

  .popup-css-auto-size-content {
    max-width: var(--auto-size-available-width);
    max-height: var(--auto-size-available-height);
    overflow: auto;
    padding: 1rem;
    background: var(--zn-color-neutral-900);
    color: white;
    border-radius: 4px;
  }
</style>

Accessibility

Popups are low-level primitives and do not include built-in accessibility features. It’s your responsibility to ensure proper accessibility when using popups:

  • Add appropriate ARIA attributes to popup content
  • Ensure keyboard navigation works correctly
  • Manage focus as needed
  • Use semantic HTML inside the popup

For accessible overlays, consider using higher-level components like Dropdown or Tooltip, which include built-in accessibility features.

Importing

If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

To import this component from the CDN using a script tag:

<script type="module" src="https://cdn.jsdelivr.net/npm/@kubex/zinc@1.0.75/dist/components/popup/popup.js"></script>

To import this component from the CDN using a JavaScript import:

import 'https://cdn.jsdelivr.net/npm/@kubex/zinc@1.0.75/dist/components/popup/popup.js';

To import this component using a bundler:

import '@kubex/zinc/dist/components/popup/popup.js';

Slots

Name Description
(default) The default slot.
example An example slot.

Learn more about using slots.

Properties

Name Description Reflects Type Default
popup A reference to the internal popup container. Useful for animating and styling the popup with JavaScript. HTMLElement -
anchor The element the popup will be anchored to. If the anchor lives outside of the popup, you can provide the anchor element id, a DOM element reference, or a VirtualElement. If the anchor lives inside the popup, use the anchor slot instead. Element | string | VirtualElement -
active Activates the positioning logic and shows the popup. When this attribute is removed, the positioning logic is torn down and the popup will be hidden. boolean false
placement The preferred placement of the popup. Note that the actual placement will vary as configured to keep the panel inside of the viewport. 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'right' | 'right-start' | 'right-end' | 'left' | 'left-start' | 'left-end' 'top'
strategy Determines how the popup is positioned. The absolute strategy works well in most cases, but if overflow is clipped, using a fixed position strategy can often workaround it. 'absolute' | 'fixed' 'fixed'
distance The distance in pixels from which to offset the panel away from its anchor. number 0
skidding The distance in pixels from which to offset the panel along its anchor. number 0
arrow Attaches an arrow to the popup. The arrow’s size and color can be customized using the --arrow-size and --arrow-color custom properties. For additional customizations, you can also target the arrow using ::part(arrow) in your stylesheet. boolean false
arrowPlacement
arrow-placement
The placement of the arrow. The default is anchor, which will align the arrow as close to the center of the anchor as possible, considering available space and arrow-padding. A value of start, end, or center will align the arrow to the start, end, or center of the popover instead. 'start' | 'end' | 'center' | 'anchor' 'anchor'
arrowPadding
arrow-padding
The amount of padding between the arrow and the edges of the popup. If the popup has a border-radius, for example, this will prevent it from overflowing the corners. number 10
flip When set, placement of the popup will flip to the opposite site to keep it in view. You can use flipFallbackPlacements to further configure how the fallback placement is determined. boolean false
flipFallbackPlacements
flip-fallback-placements
If the preferred placement doesn’t fit, popup will be tested in these fallback placements until one fits. Must be a string of any number of placements separated by a space, e.g. “top bottom left”. If no placement fits, the flip fallback strategy will be used instead. string ''
flipFallbackStrategy
flip-fallback-strategy
When neither the preferred placement nor the fallback placements fit, this value will be used to determine whether the popup should be positioned using the best available fit based on available space or as it was initially preferred. 'best-fit' | 'initial' 'best-fit'
flipBoundary The flip boundary describes clipping element(s) that overflow will be checked relative to when flipping. By default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can change the boundary by passing a reference to one or more elements to this property. Element | Element[] -
flipPadding
flip-padding
The amount of padding, in pixels, to exceed before the flip behavior will occur. number 0
shift Moves the popup along the axis to keep it in view when clipped. boolean false
shiftBoundary The shift boundary describes clipping element(s) that overflow will be checked relative to when shifting. By default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can change the boundary by passing a reference to one or more elements to this property. Element | Element[] -
shiftPadding
shift-padding
The amount of padding, in pixels, to exceed before the shift behavior will occur. number 0
autoSize
auto-size
When set, this will cause the popup to automatically resize itself to prevent it from overflowing. 'horizontal' | 'vertical' | 'both' -
sync Syncs the popup’s width or height to that of the anchor element. 'width' | 'height' | 'both' -
autoSizeBoundary The auto-size boundary describes clipping element(s) that overflow will be checked relative to when resizing. By default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can change the boundary by passing a reference to one or more elements to this property. Element | Element[] -
autoSizePadding
auto-size-padding
The amount of padding, in pixels, to exceed before the auto-size behavior will occur. number 0
hoverBridge
hover-bridge
When a gap exists between the anchor and the popup element, this option will add a “hover bridge” that fills the gap using an invisible element. This makes listening for events such as mouseenter and mouseleave more sane because the pointer never technically leaves the element. The hover bridge will only be drawn when the popover is active. boolean false
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
zn-event-name Emitted as an example. -

Learn more about events.

Methods

Name Description Arguments
reposition() Forces the popup to recalculate and reposition itself. -

Learn more about methods.

Custom Properties

Name Description Default
--example An example CSS custom property.

Learn more about customizing CSS custom properties.

Parts

Name Description
base The component’s base wrapper.

Learn more about customizing CSS parts.

Dependencies

This component automatically imports the following dependencies.

  • <zn-example>