Skip to content

Popup beta

Popup is a utility that lets you declaratively anchor “popup” containers to another element.

<ch-popup>
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

The element the popup will be anchored to by adding a reference to the anchor attribute.

<span id="trigger"></span>
<ch-popup anchor="trigger">
<div class="box"></div>
</ch-popup>

Control the popup open/close behavior by including the open attribute.

<ch-popup open>
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

Specifying the placement attribute allows to control the location of the popup in relation to its anchor. Note that the actual placement will vary as configured to keep the panel inside of the viewport.

The values are top | top-start | top-end | bottom | bottom-start | bottom-end | right | right-start | right-end | left | left-start | left-end;

<ch-popup placement="bottom-start">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

By default, the popup is positioned using an absolute positioning strategy. However, if your anchor is fixed or exists within a container that has overflow: auto |hidden, the popup risks being clipped. To work around this, you can use a fixed positioning strategy by setting the strategy attribute to fixed.

The fixed positioning strategy reduces jumpiness when the anchor is fixed and allows the popup to break out containers that clip. When using this strategy, it’s important to note that the content will be positioned relative to its containing block, which is usually the viewport unless an ancestor uses a transform, perspective, or filter.

<div style="contain: layout">
<div style="position: relative; overflow: hidden;">
<ch-popup strategy="fixed">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>
</div>
</div>

Adding the distance attribute sets the pixels from which to offset the panel away from its anchor.

<ch-popup distance="5">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

The skidding attribute is similar to distance, but instead allows you to offset the popup along the anchor’s axis. Both positive and negative values are allowed.

<ch-popup skidding="5">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

Attaches an arrow to the popup. The arrow’s size and color can be customized using the --popup-arrow-size and --popup-arrow-color custom properties. For additional customizations, you can also target the arrow using ::part(arrow) in your stylesheet.

<ch-popup>
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>
<style>
:host {
'--popup-arrow-color':'blue';
}
span[slot='anchor'] {
display: inline-block;
width: 150px;
height: 150px;
border: dashed 2px black;
margin: 50px;
background: black;
}
.box {
width: 100px;
height: 50px;
background: blue;
}
</style>

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.

Options are start | end | center | anchor.

<ch-popup arrow-placement="end">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

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.

<ch-popup arrow-padding="5">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

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.

<ch-popup flip>
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

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.

<ch-popup flip flip-fallback-placements="top bottom left">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

When neither the preferred placement nor the fallback placements fit, this value will be used to determine whether the popup should be positioned as it was initially preferred or using the best available fit based on available space.

<ch-popup flip flip-fallback-strategy="best-fit">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

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.

<div id="parent">
<ch-popup id="my-pop-up" flip>
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>
</div>
<script>
const el = document.querySelector('#my-pop-up');
el.flipBoundary = document.querySelector('#parent');
</script>

Adding the flip-padding attribute dictates the amount of padding, in pixels, to exceed before the flip behavior will occur.

<ch-popup flip flip-padding="5">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

When a popup is longer than its anchor, it risks being clipped by an overflowing container. In this case, use the shift attribute to shift the popup along its axis and back into view. You can customize the shift behavior using shiftBoundary and shift-padding.

<ch-popup shift>
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

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.

<div id="parent">
<ch-popup id="my-pop-up" shift>
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>
</div>
<script>
const el = document.querySelector('#my-pop-up');
el.shiftBoundary = document.querySelector('#parent');
</script>

The amount of padding, in pixels, to exceed before the shift behavior will occur.

<ch-popup shift shift-padding="5">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

Use the auto-size attribute to tell the popup to resize when necessary to prevent it from getting clipped. Possible values are horizontal, vertical, and both. You can use autoSizeBoundary and auto-size-padding to customize the behavior of this option. Auto-size works well with flip, but if you’re using auto-size-padding make sure flip-padding is the same value.

When using auto-size, one or both of --auto-size-available-width and --auto-size-available-height will be applied to the host element. These values determine the available space the popover has before clipping will occur. Since they cascade, you can use them to set a max-width/height on your popup’s content and easily control its overflow.

<ch-popup auto-size="vertical">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

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.

<ch-popup auto-size="both">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>
<script>
const el = document.querySelector('#my-pop-up');
el.autoSizeBoundary = document.querySelector('#parent');
</script>

The amount of padding, in pixels, to exceed before the auto-size behavior will occur.

<ch-popup auto-size-padding="5" auto-size="both">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>

Use the --popup-show-transition and --popup-hide-transition CSS properties to define the transition for the popup when it is shown or hidden.

<ch-popup class="animation">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>
<style>
.animation {
--popup-show-transition: opacity 1s;
--popup-hide-transition: opacity 1s;
--popup-arrow-color: blue;
}
</style>

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 mouseover and mouseout more sane because the pointer never technically leaves the element. The hover bridge will only be drawn when the popover is active. For demonstration purposes, the bridge in this example is shown in orange.

<ch-popup hover-bridge distance="20" skidding="20">
<span slot="anchor"></span>
<div class="box"></div>
</ch-popup>
<script>
const popups = document.querySelectorAll('[popup]');
popups.forEach(popup =>
popup.querySelector('[slot="anchor"]')?.addEventListener('mouseover', () => {
popup.open = true;
})
);
popups.forEach(popup =>
popup.querySelector('[slot="anchor"]')?.addEventListener('mouseout', () => {
popup.open = false;
})
);
</script>
<style>
span[slot='anchor'] {
display: inline-block;
width: 150px;
height: 150px;
border: dashed 2px black;
margin: 50px;
background: black;
}
.box {
width: 100px;
height: 50px;
background: blue;
}
[popup]::part(popup-hover-bridge) {
background: tomato;
opacity: 0.5;
}
</style>