Animated Flyout
A notifications panel that grows out of the bell instead of the default height reveal. Animate the expander part from :state(--opening) and :state(--closing), and keep it laid out while closing. Gate the replacement for reduced motion yourself.
Reduced Motion
Your system is set to reduce motion, so the animations shown on this page will not play for you.
Notifications
- MK
Maya Kent commented on Q3 roadmap
- JO
Jonas Ortiz assigned you Fix login redirect
- LP
Lea Park shared Brand guidelines with you
Code
The following example assumes that you have imported the components and set up the theme.
<div class="notifications">48 collapsed lines
<sv-flyout class="notifications-flyout" position-order="bottom center center, top center center"> <!-- Flyout Trigger --> <button type="button" class="trigger" aria-label="Notifications" slot="trigger"> <!-- Bell --> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10.3 21a1.9 1.9 0 0 0 3.4 0" /> </svg> </button>
<!-- Flyout Content --> <section class="panel"> <header> <h2>Notifications</h2> <button type="button">Mark all as read</button> </header>
<ul> <li> <span class="avatar">MK</span> <p> <strong>Maya Kent</strong> commented on <strong>Q3 roadmap</strong> </p> <time>2m</time> </li> <li> <span class="avatar">JO</span> <p> <strong>Jonas Ortiz</strong> assigned you <strong>Fix login redirect</strong> </p> <time>1h</time> </li> <li> <span class="avatar">LP</span> <p> <strong>Lea Park</strong> shared <strong>Brand guidelines</strong> with you </p> <time>Yesterday</time> </li> </ul> </section> </sv-flyout></div>
<style>166 collapsed lines
.notifications { display: grid; place-items: start center; min-block-size: 360px; padding-block-start: 1.5em;
.trigger { position: relative; display: grid; place-items: center; inline-size: 2.75em; aspect-ratio: 1; padding: 0; 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: 50%;
svg { inline-size: 1.2em; fill: none; stroke: currentcolor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* Unread dot */ &::after { position: absolute; inset-block-start: 0.45em; inset-inline-end: 0.5em; inline-size: 0.5em; aspect-ratio: 1; content: ""; background-color: var(--sv-accent); border: 2px solid var(--sv-gray-6); border-radius: 50%; } }
.notifications-flyout { --sv-flyout-open-dur: 400ms; --sv-flyout-close-dur: 160ms;
&::part(content) { inline-size: min(21rem, calc(100vw - 2rem)); padding: 0; }
/* The panel grows out of the bell */ &::part(expander) { transform-origin: top center; }
/* Replace the default height reveal */ &:state(--opening)::part(expander) { animation: panel-enter var(--sv-flyout-open-dur) cubic-bezier(0.2, 0.9, 0.3, 1.1); }
/* Keep the expander laid out while the exit runs */ &:state(--closing)::part(expander) { grid-template-rows: 1fr; animation: panel-exit var(--sv-flyout-close-dur) ease-in forwards; }
/* Custom animations must honor the user's motion preference */ @media (prefers-reduced-motion: reduce) { --sv-flyout-open-dur: 0s; --sv-flyout-close-dur: 0s; } }
.panel { color: var(--sv-gray-1);
header { display: flex; align-items: center; justify-content: space-between; padding: 0.85em 1em; border-block-end: solid var(--sv-bdr-sz-sm) var(--sv-gray-5);
h2 { margin: 0; font-size: 0.95rem; }
button { padding: 0; font: inherit; font-size: 0.8rem; color: var(--sv-accent); cursor: pointer; background: none; border: none; } }
ul { padding: 0.35em; margin: 0; list-style: none; }
li { display: grid; grid-template-columns: auto 1fr auto; gap: 0.75em; align-items: start; padding: 0.65em; border-radius: var(--sv-bdr-rad-md);
&:hover { background-color: var(--sv-gray-6); } }
.avatar { display: grid; place-items: center; inline-size: 2.5rem; aspect-ratio: 1; font-size: 0.8rem; font-weight: 600; background-color: var(--sv-gray-5); border-radius: 50%; }
p { margin: 0; font-size: 0.85rem; line-height: 1.4; color: var(--sv-gray-2);
strong { font-weight: 600; color: var(--sv-gray-1); } }
time { font-size: 0.75rem; color: var(--sv-gray-3); white-space: nowrap; } } }
@keyframes panel-enter { from { opacity: 0; scale: 0.9; translate: 0 -0.75rem; } }
@keyframes panel-exit { to { opacity: 0; scale: 0.97; translate: 0 -0.25rem; } }</style>