Skip to content

Flyout Examples

The examples below assume that you have imported the components and set up the theme.

Basic

The first element assigned to the trigger slot toggles the flyout on click, and the default slot provides the popover content. Escape or a click outside closes it again, so nothing beyond the markup is needed.

Shipping information

Orders normally arrive within three to five business days.

Express delivery can be picked at checkout.

<div class="flyout-basic">
<sv-flyout>
<button type="button" class="trigger" slot="trigger">Shipping information</button>
<section class="panel" aria-labelledby="shipping-heading">
<h2 id="shipping-heading">Shipping information</h2>
<p>Orders normally arrive within three to five business days.</p>
<p>Express delivery can be picked at checkout.</p>
</section>
</sv-flyout>
</div>
<style>
.flyout-basic {
display: grid;
justify-items: center;
padding-block: 2em;
.trigger {
padding: 0.5em 0.75em;
font: inherit;
color: var(--sv-gray-1);
cursor: pointer;
background-color: var(--sv-gray-6);
border: solid var(--sv-bdr-sz-sm) var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
.panel {
max-inline-size: 16rem;
padding: 0.25em;
h2 {
margin: 0;
font-size: 1rem;
color: var(--sv-gray-1);
}
p {
margin: 0.5em 0 0;
font-size: 0.875rem;
line-height: 1.5;
color: var(--sv-gray-2);
}
}
}
</style>

Positions

position-order lists the position areas to try, where each entry names the side, the alignment against the trigger, and the alignment of the flyout itself. An area that cannot fit is passed over for the next one, so the first flyout below opens above its trigger and drops back under it when the room above runs out.

The vertical and horizontal presets expand to full orders, and any valid areas missing from a custom order are appended after it.

Opens above the trigger, and drops back below it when the room above runs out.

top center center, bottom center center

Sits below the trigger with both start edges lined up, flipping above when needed.

bottom start start, top start start

Sits below the trigger with both end edges lined up, flipping above when needed.

bottom end end, top end end

Opens to the right of the trigger, taking the left side as fallback.

horizontal
<div class="flyout-positions">
<div class="variant">
<sv-flyout position-order="top center center, bottom center center">
<button type="button" class="trigger" slot="trigger">Above, centered</button>
<p class="panel">Opens above the trigger, and drops back below it when the room above runs out.</p>
</sv-flyout>
<span>top center center, bottom center center</span>
</div>
<div class="variant">
<sv-flyout position-order="bottom start start, top start start">
<button type="button" class="trigger" slot="trigger">Below, start-aligned</button>
<p class="panel">Sits below the trigger with both start edges lined up, flipping above when needed.</p>
</sv-flyout>
<span>bottom start start, top start start</span>
</div>
<div class="variant">
<sv-flyout position-order="bottom end end, top end end">
<button type="button" class="trigger" slot="trigger">Below, end-aligned</button>
<p class="panel">Sits below the trigger with both end edges lined up, flipping above when needed.</p>
</sv-flyout>
<span>bottom end end, top end end</span>
</div>
<div class="variant">
<sv-flyout class="sideways" position-order="horizontal">
<button type="button" class="trigger" slot="trigger">Beside the trigger</button>
<p class="panel">Opens to the right of the trigger, taking the left side as fallback.</p>
</sv-flyout>
<span>horizontal</span>
</div>
</div>
<style>
.flyout-positions {
display: grid;
gap: 1.5em;
inline-size: min(100%, 14rem);
padding-block: 2em;
margin-inline: auto;
.variant {
display: grid;
gap: 0.4em;
> span {
font-size: 0.8em;
color: var(--sv-gray-3);
text-align: center;
}
}
.trigger {
padding: 0.5em 0.75em;
font: inherit;
color: var(--sv-gray-1);
cursor: pointer;
background-color: var(--sv-gray-6);
border: solid var(--sv-bdr-sz-sm) var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
sv-flyout::part(content) {
inline-size: 10rem;
}
.sideways::part(content) {
inline-size: 8rem;
}
.panel {
margin: 0;
font-size: 0.875rem;
line-height: 1.4;
color: var(--sv-gray-2);
}
}
</style>

Custom States

Target the --opened, --opening, and --closing states with :state() to style the trigger's open look, or anything beside the flyout, like the label above this trigger. --opened lands as the opening begins rather than when it ends, so an open look starts together with the animation.

Watch the label above the trigger. It names the state the flyout is in right now.

state:
<div class="flyout-custom-states">
<sv-flyout>
<button type="button" class="trigger" slot="trigger">
Filters
<!-- Chevron -->
<svg class="chevron" aria-hidden="true" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path d="M480-360 280-560h400L480-360Z"></path>
</svg>
</button>
<p class="panel">Watch the label above the trigger. It names the state the flyout is in right now.</p>
</sv-flyout>
<span class="state-label">state:</span>
</div>
<style>
.flyout-custom-states {
display: grid;
gap: 0.75em;
justify-items: center;
padding-block: 2em;
.trigger {
display: flex;
gap: 0.5em;
align-items: center;
padding: 0.5em 0.75em;
font: inherit;
color: var(--sv-gray-1);
cursor: pointer;
background-color: var(--sv-gray-6);
border: solid var(--sv-bdr-sz-sm) var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
transition: border-color 200ms ease;
}
.chevron {
inline-size: 1.1em;
block-size: 1.1em;
fill: currentcolor;
transition: rotate 200ms ease;
}
sv-flyout:state(--opened) {
.trigger {
border-color: var(--sv-accent);
}
.chevron {
rotate: 180deg;
}
}
.panel {
max-inline-size: 11rem;
margin: 0;
font-size: 0.875rem;
line-height: 1.4;
color: var(--sv-gray-2);
}
.state-label {
/* Rendered above the trigger while staying after the flyout in the DOM, where the sibling selectors below can reach it */
order: -1;
font-size: 0.8em;
color: var(--sv-gray-3);
&::after {
content: " closed";
}
}
sv-flyout:state(--opened) + .state-label::after {
content: " open";
}
sv-flyout:state(--opening) + .state-label::after {
content: " opening";
}
sv-flyout:state(--closing) + .state-label::after {
content: " closing";
}
@media (prefers-reduced-motion: reduce) {
.trigger,
.chevron {
transition: none;
}
}
}
</style>