Show success toast Show error toast Show info toast
@use 'sprucecss/scss/spruce' as *;
.toast-demo {
@include layout-cluster ( 's' ) ;
}
.toast-region {
inset-block-end : spacer ( 'm' ) ;
inset-inline-end : spacer ( 'm' ) ;
max-inline-size : calc ( 100vi - #{ spacer ( 'l' ) } ) ;
position : fixed;
z-index : z-index ( 'toast' ) ;
}
.toast-list {
@include clear-list;
display : flex;
flex-direction : column;
gap : spacer ( 'xs' ) ;
}
.toast {
--toast-color : #{ color ( 'info' , 'alert' ) } ;
align-items : center;
background-color : color ( 'surface' ) ;
border : config ( 'border-width-base' , $display ) solid color ( 'border' ) ;
border-radius : radius ( 'md' ) ;
box-shadow : shadow ( 'lg' ) ;
display : flex;
gap : spacer ( 's' ) ;
inline-size : 22rem;
margin : 0 !important ;
max-inline-size : 100%;
padding : spacer ( 'xs' ) spacer ( 'xs' ) spacer ( 'xs' ) spacer ( 's' ) ;
@include motion-safe {
animation : toast-in 0.2s ease-out;
}
& --success {
--toast-color : #{ color ( 'success' , 'alert' ) } ;
}
& --danger {
--toast-color : #{ color ( 'danger' , 'alert' ) } ;
}
& --info {
--toast-color : #{ color ( 'info' , 'alert' ) } ;
}
& __indicator {
background-color : var ( --toast-color) ;
block-size : 0.625rem;
border-radius : 50%;
box-shadow : 0 0 0 4px color-mix ( in srgb, var ( --toast-color) 20%, transparent) ;
flex-shrink : 0;
inline-size : 0.625rem;
}
& __message {
color : color ( 'heading' ) ;
flex-grow : 1;
line-height : config ( 'line-height-md' , $typography ) ;
margin : 0;
}
& .is-leaving {
opacity : 0;
translate : 0 0.5rem;
@include transition ( $property : 'opacity, translate' ) ;
}
}
@keyframes toast-in {
from {
opacity : 0;
translate : 0 0.5rem;
}
} < div class = " toast-demo" >
< button class = " btn btn--primary" type = " button" data-toast = " Your changes were saved." data-toast-type = " success" > Show success toast</ button>
< button class = " btn btn--outline-danger" type = " button" data-toast = " We couldn’t reach the server. Try again." data-toast-type = " danger" > Show error toast</ button>
< button class = " btn btn--ghost-primary" type = " button" data-toast = " A new version is available." data-toast-type = " info" > Show info toast</ button>
</ div>
< div class = " toast-region" role = " region" aria-label = " Notifications" >
< ol class = " toast-list" aria-live = " polite" aria-relevant = " additions" > </ ol>
</ div>
< template id = " toast-template" >
< li class = " toast" >
< span class = " toast__indicator" aria-hidden = " true" > </ span>
< p class = " toast__message" > </ p>
< button class = " btn btn--ghost-secondary btn--icon btn--sm toast__close" type = " button" aria-label = " Dismiss notification" >
< svg class = " btn__icon" xmlns = " http://www.w3.org/2000/svg" viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2" stroke-linecap = " round" aria-hidden = " true" focusable = " false" > < path d = " M18 6 6 18M6 6l12 12" /> </ svg>
</ button>
</ li>
</ template> ( ( ) => {
const list = document. querySelector ( '.toast-list' ) ;
const template = document. getElementById ( 'toast-template' ) ;
if ( ! list || ! template) {
return ;
}
const dismiss = ( toast ) => {
toast. classList. add ( 'is-leaving' ) ;
setTimeout ( ( ) => toast. remove ( ) , 200 ) ;
} ;
const showToast = ( message, type = 'info' , duration = 5000 ) => {
const toast = template. content. firstElementChild. cloneNode ( true ) ;
let timer = null ;
const start = ( ) => { timer = setTimeout ( ( ) => dismiss ( toast) , duration) ; } ;
const stop = ( ) => clearTimeout ( timer) ;
toast. classList. add ( ` toast-- ${ type} ` ) ;
toast. querySelector ( '.toast__message' ) . textContent = message;
toast. querySelector ( '.toast__close' ) . addEventListener ( 'click' , ( ) => dismiss ( toast) ) ;
toast. addEventListener ( 'mouseenter' , stop) ;
toast. addEventListener ( 'mouseleave' , start) ;
toast. addEventListener ( 'focusin' , stop) ;
toast. addEventListener ( 'focusout' , start) ;
list. append ( toast) ;
start ( ) ;
return toast;
} ;
window. showToast = showToast;
document. querySelectorAll ( '[data-toast]' ) . forEach ( ( button ) => {
button. addEventListener ( 'click' , ( ) => showToast ( button. dataset. toast, button. dataset. toastType) ) ;
} ) ;
} ) ( ) ;