30 CSS Notifications08 / 30
Tailwind CSS Toast Popup Snippet
Two production-shaped Tailwind toasts: a success popup built entirely from utilities (ring-1, shadow-lg, backdrop-blur) with its slide-in as a one-line custom keyframe consumed via animate-[…] arbitrary values, and a responsive dark-mode-aware toast that sits full-width above the home indicator on phones and docks top-right from the sm: breakpoint up.
Published
The code
<div class="ntf-08-group">
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={corePlugins:{preflight:false},darkMode:'selector'}</script>
<section class="ntf-08a" aria-label="Tailwind utility toast">
<div class="mx-auto w-full max-w-md text-center">
<h2 class="text-2xl font-semibold tracking-tight text-slate-100">Utilities all the way down</h2>
<p class="mt-2 text-sm leading-6 text-slate-400">Layout, ring, shadow, blur — all classes. Only the slide-in keyframe is hand-written CSS, consumed with <code class="rounded bg-white/10 px-1.5 py-0.5 text-xs">animate-[…]</code>.</p>
<button id="ntf-08a-btn" type="button" class="mt-6 rounded-xl bg-emerald-400 px-6 py-3 text-sm font-semibold text-slate-950 shadow-lg shadow-emerald-400/25 transition hover:bg-emerald-300 active:scale-95">Complete order</button>
</div>
<div id="ntf-08a-region" role="status" aria-live="polite" class="pointer-events-none absolute right-4 top-4 flex w-80 max-w-[calc(100%-2rem)] flex-col gap-3"></div>
<template id="ntf-08a-tpl">
<div class="pointer-events-auto flex items-center gap-3 rounded-2xl bg-white/95 p-4 shadow-xl shadow-slate-950/40 ring-1 ring-slate-900/10 backdrop-blur animate-[ntf-08a-in_.5s_cubic-bezier(.22,1.2,.36,1)_both]">
<span class="grid h-9 w-9 flex-none place-items-center rounded-xl bg-emerald-500 text-white" aria-hidden="true"><svg class="h-4.5 w-4.5" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg></span>
<span class="grid min-w-0 gap-0.5"><b class="text-[13.5px] font-semibold text-slate-900">Order confirmed</b><small class="text-xs text-slate-500">#8412 · receipt emailed</small></span>
<button type="button" data-dismiss class="ml-auto grid h-7 w-7 flex-none place-items-center rounded-lg text-slate-400 transition hover:bg-slate-900/5 hover:text-slate-700" aria-label="Dismiss">✕</button>
</div>
</template>
</section>
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={corePlugins:{preflight:false},darkMode:'selector'}</script>
<section class="ntf-08b" aria-label="Responsive dark-mode Tailwind toast">
<div id="ntf-08b-scope" class="absolute inset-0 grid place-items-center bg-slate-100 p-6 transition-colors duration-300 [&.dark]:bg-slate-950">
<div class="w-full max-w-md text-center">
<h2 class="text-2xl font-semibold tracking-tight text-slate-900 [.dark_&]:text-slate-100">Bottom sheet → corner toast</h2>
<p class="mt-2 text-sm leading-6 text-slate-500 [.dark_&]:text-slate-400">Resize the preview: full-width above the home indicator on phones, docked top-right from <code class="rounded bg-slate-900/10 px-1.5 py-0.5 text-xs [.dark_&]:bg-white/10">sm:</code> up. Same element.</p>
<div class="mt-6 flex items-center justify-center gap-3">
<button id="ntf-08b-btn" type="button" class="rounded-xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white shadow-lg transition hover:bg-slate-700 active:scale-95 [.dark_&]:bg-white [.dark_&]:text-slate-900 [.dark_&]:hover:bg-slate-200">Show toast</button>
<button id="ntf-08b-mode" type="button" class="rounded-xl px-5 py-3 text-sm font-semibold text-slate-600 ring-1 ring-slate-900/15 transition hover:bg-slate-900/5 [.dark_&]:text-slate-300 [.dark_&]:ring-white/20 [.dark_&]:hover:bg-white/5">Toggle dark</button>
</div>
</div>
<div id="ntf-08b-region" role="status" aria-live="polite" class="pointer-events-none absolute inset-x-3 bottom-3 sm:inset-x-auto sm:bottom-auto sm:right-4 sm:top-4 sm:w-80"></div>
<template id="ntf-08b-tpl">
<div class="pointer-events-auto flex items-center gap-3 rounded-2xl bg-white/95 p-4 shadow-xl shadow-slate-900/15 ring-1 ring-slate-900/10 backdrop-blur animate-[ntf-08b-up_.45s_cubic-bezier(.22,1.2,.36,1)_both] [.dark_&]:bg-slate-900/95 [.dark_&]:shadow-black/50 [.dark_&]:ring-white/15">
<span class="grid h-9 w-9 flex-none place-items-center rounded-xl bg-sky-500 text-white" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg></span>
<span class="grid min-w-0 gap-0.5"><b class="text-[13.5px] font-semibold text-slate-900 [.dark_&]:text-slate-100">Message sent</b><small class="text-xs text-slate-500 [.dark_&]:text-slate-400">Ana will get it when she's online</small></span>
<button type="button" data-dismiss class="ml-auto grid h-7 w-7 flex-none place-items-center rounded-lg text-slate-400 transition hover:bg-slate-900/5 hover:text-slate-700 [.dark_&]:hover:bg-white/10 [.dark_&]:hover:text-slate-200" aria-label="Dismiss">✕</button>
</div>
</template>
</div>
</section>
</div><div class="ntf-08-group">
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={corePlugins:{preflight:false},darkMode:'selector'}</script>
<section class="ntf-08a" aria-label="Tailwind utility toast">
<div class="mx-auto w-full max-w-md text-center">
<h2 class="text-2xl font-semibold tracking-tight text-slate-100">Utilities all the way down</h2>
<p class="mt-2 text-sm leading-6 text-slate-400">Layout, ring, shadow, blur — all classes. Only the slide-in keyframe is hand-written CSS, consumed with <code class="rounded bg-white/10 px-1.5 py-0.5 text-xs">animate-[…]</code>.</p>
<button id="ntf-08a-btn" type="button" class="mt-6 rounded-xl bg-emerald-400 px-6 py-3 text-sm font-semibold text-slate-950 shadow-lg shadow-emerald-400/25 transition hover:bg-emerald-300 active:scale-95">Complete order</button>
</div>
<div id="ntf-08a-region" role="status" aria-live="polite" class="pointer-events-none absolute right-4 top-4 flex w-80 max-w-[calc(100%-2rem)] flex-col gap-3"></div>
<template id="ntf-08a-tpl">
<div class="pointer-events-auto flex items-center gap-3 rounded-2xl bg-white/95 p-4 shadow-xl shadow-slate-950/40 ring-1 ring-slate-900/10 backdrop-blur animate-[ntf-08a-in_.5s_cubic-bezier(.22,1.2,.36,1)_both]">
<span class="grid h-9 w-9 flex-none place-items-center rounded-xl bg-emerald-500 text-white" aria-hidden="true"><svg class="h-4.5 w-4.5" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg></span>
<span class="grid min-w-0 gap-0.5"><b class="text-[13.5px] font-semibold text-slate-900">Order confirmed</b><small class="text-xs text-slate-500">#8412 · receipt emailed</small></span>
<button type="button" data-dismiss class="ml-auto grid h-7 w-7 flex-none place-items-center rounded-lg text-slate-400 transition hover:bg-slate-900/5 hover:text-slate-700" aria-label="Dismiss">✕</button>
</div>
</template>
</section>
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={corePlugins:{preflight:false},darkMode:'selector'}</script>
<section class="ntf-08b" aria-label="Responsive dark-mode Tailwind toast">
<div id="ntf-08b-scope" class="absolute inset-0 grid place-items-center bg-slate-100 p-6 transition-colors duration-300 [&.dark]:bg-slate-950">
<div class="w-full max-w-md text-center">
<h2 class="text-2xl font-semibold tracking-tight text-slate-900 [.dark_&]:text-slate-100">Bottom sheet → corner toast</h2>
<p class="mt-2 text-sm leading-6 text-slate-500 [.dark_&]:text-slate-400">Resize the preview: full-width above the home indicator on phones, docked top-right from <code class="rounded bg-slate-900/10 px-1.5 py-0.5 text-xs [.dark_&]:bg-white/10">sm:</code> up. Same element.</p>
<div class="mt-6 flex items-center justify-center gap-3">
<button id="ntf-08b-btn" type="button" class="rounded-xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white shadow-lg transition hover:bg-slate-700 active:scale-95 [.dark_&]:bg-white [.dark_&]:text-slate-900 [.dark_&]:hover:bg-slate-200">Show toast</button>
<button id="ntf-08b-mode" type="button" class="rounded-xl px-5 py-3 text-sm font-semibold text-slate-600 ring-1 ring-slate-900/15 transition hover:bg-slate-900/5 [.dark_&]:text-slate-300 [.dark_&]:ring-white/20 [.dark_&]:hover:bg-white/5">Toggle dark</button>
</div>
</div>
<div id="ntf-08b-region" role="status" aria-live="polite" class="pointer-events-none absolute inset-x-3 bottom-3 sm:inset-x-auto sm:bottom-auto sm:right-4 sm:top-4 sm:w-80"></div>
<template id="ntf-08b-tpl">
<div class="pointer-events-auto flex items-center gap-3 rounded-2xl bg-white/95 p-4 shadow-xl shadow-slate-900/15 ring-1 ring-slate-900/10 backdrop-blur animate-[ntf-08b-up_.45s_cubic-bezier(.22,1.2,.36,1)_both] [.dark_&]:bg-slate-900/95 [.dark_&]:shadow-black/50 [.dark_&]:ring-white/15">
<span class="grid h-9 w-9 flex-none place-items-center rounded-xl bg-sky-500 text-white" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg></span>
<span class="grid min-w-0 gap-0.5"><b class="text-[13.5px] font-semibold text-slate-900 [.dark_&]:text-slate-100">Message sent</b><small class="text-xs text-slate-500 [.dark_&]:text-slate-400">Ana will get it when she's online</small></span>
<button type="button" data-dismiss class="ml-auto grid h-7 w-7 flex-none place-items-center rounded-lg text-slate-400 transition hover:bg-slate-900/5 hover:text-slate-700 [.dark_&]:hover:bg-white/10 [.dark_&]:hover:text-slate-200" aria-label="Dismiss">✕</button>
</div>
</template>
</div>
</section>
</div>.ntf-08-group {
display: flex;
flex-wrap: wrap;
align-items: stretch;
width: 100%;
}
.ntf-08-group > section {
flex: 1 1 480px;
min-width: min(100%,360px);
}
.ntf-08a {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
display: grid;
place-items: center;
padding: 48px 24px;
background: radial-gradient(120% 100% at 50% 0%,#0f172a,#020617);
font-family: ui-sans-serif,'Segoe UI',system-ui,sans-serif;
}
@keyframes ntf-08a-in {
from {
opacity: 0;
translate: 16rem 0;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ntf-08a [class*="animate-"] {
animation-name: ntf-08a-fade!important;
animation-duration: .25s!important;
}
@keyframes ntf-08a-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
.ntf-08b {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
font-family: ui-sans-serif,'Segoe UI',system-ui,sans-serif;
}
@keyframes ntf-08b-up {
from {
opacity: 0;
translate: 0 1.5rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ntf-08b [class*="animate-"] {
animation-name: ntf-08b-fade!important;
animation-duration: .25s!important;
}
@keyframes ntf-08b-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}.ntf-08-group {
display: flex;
flex-wrap: wrap;
align-items: stretch;
width: 100%;
}
.ntf-08-group > section {
flex: 1 1 480px;
min-width: min(100%,360px);
}
.ntf-08a {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
display: grid;
place-items: center;
padding: 48px 24px;
background: radial-gradient(120% 100% at 50% 0%,#0f172a,#020617);
font-family: ui-sans-serif,'Segoe UI',system-ui,sans-serif;
}
@keyframes ntf-08a-in {
from {
opacity: 0;
translate: 16rem 0;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ntf-08a [class*="animate-"] {
animation-name: ntf-08a-fade!important;
animation-duration: .25s!important;
}
@keyframes ntf-08a-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
.ntf-08b {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
font-family: ui-sans-serif,'Segoe UI',system-ui,sans-serif;
}
@keyframes ntf-08b-up {
from {
opacity: 0;
translate: 0 1.5rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ntf-08b [class*="animate-"] {
animation-name: ntf-08b-fade!important;
animation-duration: .25s!important;
}
@keyframes ntf-08b-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}(function(){
var btn=document.getElementById('ntf-08a-btn'); if(!btn) return;
var region=document.getElementById('ntf-08a-region'),tpl=document.getElementById('ntf-08a-tpl');
btn.addEventListener('click',function(){
var toast=tpl.content.firstElementChild.cloneNode(true);
toast.querySelector('[data-dismiss]').addEventListener('click',function(){ toast.remove(); });
region.appendChild(toast);
setTimeout(function(){ if(toast.isConnected) toast.remove(); },5000);
});
})();
(function(){
var scope=document.getElementById('ntf-08b-scope'); if(!scope) return;
var region=document.getElementById('ntf-08b-region'),tpl=document.getElementById('ntf-08b-tpl');
document.getElementById('ntf-08b-btn').addEventListener('click',function(){
region.textContent='';
var toast=tpl.content.firstElementChild.cloneNode(true);
toast.querySelector('[data-dismiss]').addEventListener('click',function(){ toast.remove(); });
region.appendChild(toast);
setTimeout(function(){ if(toast.isConnected) toast.remove(); },5000);
});
document.getElementById('ntf-08b-mode').addEventListener('click',function(){
scope.classList.toggle('dark'); /* darkMode:'selector' — the class, not the OS, decides */
});
})();(function(){
var btn=document.getElementById('ntf-08a-btn'); if(!btn) return;
var region=document.getElementById('ntf-08a-region'),tpl=document.getElementById('ntf-08a-tpl');
btn.addEventListener('click',function(){
var toast=tpl.content.firstElementChild.cloneNode(true);
toast.querySelector('[data-dismiss]').addEventListener('click',function(){ toast.remove(); });
region.appendChild(toast);
setTimeout(function(){ if(toast.isConnected) toast.remove(); },5000);
});
})();
(function(){
var scope=document.getElementById('ntf-08b-scope'); if(!scope) return;
var region=document.getElementById('ntf-08b-region'),tpl=document.getElementById('ntf-08b-tpl');
document.getElementById('ntf-08b-btn').addEventListener('click',function(){
region.textContent='';
var toast=tpl.content.firstElementChild.cloneNode(true);
toast.querySelector('[data-dismiss]').addEventListener('click',function(){ toast.remove(); });
region.appendChild(toast);
setTimeout(function(){ if(toast.isConnected) toast.remove(); },5000);
});
document.getElementById('ntf-08b-mode').addEventListener('click',function(){
scope.classList.toggle('dark'); /* darkMode:'selector' — the class, not the OS, decides */
});
})();Here's a working CSS Notification from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind CSS Toast Popup Snippet
Source: https://codefronts.com/snippets/css-notifications/tailwind-css-toast-popup-snippet/
Two production-shaped Tailwind toasts: a success popup built entirely from utilities (ring-1, shadow-lg, backdrop-blur) with its slide-in as a one-line custom keyframe consumed via animate-[…] arbitrary values, and a responsive dark-mode-aware toast that sits full-width above the home indicator on phones and docks top-right from the sm: breakpoint up.
## HTML
```html
<div class="ntf-08-group">
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={corePlugins:{preflight:false},darkMode:'selector'}</script>
<section class="ntf-08a" aria-label="Tailwind utility toast">
<div class="mx-auto w-full max-w-md text-center">
<h2 class="text-2xl font-semibold tracking-tight text-slate-100">Utilities all the way down</h2>
<p class="mt-2 text-sm leading-6 text-slate-400">Layout, ring, shadow, blur — all classes. Only the slide-in keyframe is hand-written CSS, consumed with <code class="rounded bg-white/10 px-1.5 py-0.5 text-xs">animate-[…]</code>.</p>
<button id="ntf-08a-btn" type="button" class="mt-6 rounded-xl bg-emerald-400 px-6 py-3 text-sm font-semibold text-slate-950 shadow-lg shadow-emerald-400/25 transition hover:bg-emerald-300 active:scale-95">Complete order</button>
</div>
<div id="ntf-08a-region" role="status" aria-live="polite" class="pointer-events-none absolute right-4 top-4 flex w-80 max-w-[calc(100%-2rem)] flex-col gap-3"></div>
<template id="ntf-08a-tpl">
<div class="pointer-events-auto flex items-center gap-3 rounded-2xl bg-white/95 p-4 shadow-xl shadow-slate-950/40 ring-1 ring-slate-900/10 backdrop-blur animate-[ntf-08a-in_.5s_cubic-bezier(.22,1.2,.36,1)_both]">
<span class="grid h-9 w-9 flex-none place-items-center rounded-xl bg-emerald-500 text-white" aria-hidden="true"><svg class="h-4.5 w-4.5" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg></span>
<span class="grid min-w-0 gap-0.5"><b class="text-[13.5px] font-semibold text-slate-900">Order confirmed</b><small class="text-xs text-slate-500">#8412 · receipt emailed</small></span>
<button type="button" data-dismiss class="ml-auto grid h-7 w-7 flex-none place-items-center rounded-lg text-slate-400 transition hover:bg-slate-900/5 hover:text-slate-700" aria-label="Dismiss">✕</button>
</div>
</template>
</section>
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={corePlugins:{preflight:false},darkMode:'selector'}</script>
<section class="ntf-08b" aria-label="Responsive dark-mode Tailwind toast">
<div id="ntf-08b-scope" class="absolute inset-0 grid place-items-center bg-slate-100 p-6 transition-colors duration-300 [&.dark]:bg-slate-950">
<div class="w-full max-w-md text-center">
<h2 class="text-2xl font-semibold tracking-tight text-slate-900 [.dark_&]:text-slate-100">Bottom sheet → corner toast</h2>
<p class="mt-2 text-sm leading-6 text-slate-500 [.dark_&]:text-slate-400">Resize the preview: full-width above the home indicator on phones, docked top-right from <code class="rounded bg-slate-900/10 px-1.5 py-0.5 text-xs [.dark_&]:bg-white/10">sm:</code> up. Same element.</p>
<div class="mt-6 flex items-center justify-center gap-3">
<button id="ntf-08b-btn" type="button" class="rounded-xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white shadow-lg transition hover:bg-slate-700 active:scale-95 [.dark_&]:bg-white [.dark_&]:text-slate-900 [.dark_&]:hover:bg-slate-200">Show toast</button>
<button id="ntf-08b-mode" type="button" class="rounded-xl px-5 py-3 text-sm font-semibold text-slate-600 ring-1 ring-slate-900/15 transition hover:bg-slate-900/5 [.dark_&]:text-slate-300 [.dark_&]:ring-white/20 [.dark_&]:hover:bg-white/5">Toggle dark</button>
</div>
</div>
<div id="ntf-08b-region" role="status" aria-live="polite" class="pointer-events-none absolute inset-x-3 bottom-3 sm:inset-x-auto sm:bottom-auto sm:right-4 sm:top-4 sm:w-80"></div>
<template id="ntf-08b-tpl">
<div class="pointer-events-auto flex items-center gap-3 rounded-2xl bg-white/95 p-4 shadow-xl shadow-slate-900/15 ring-1 ring-slate-900/10 backdrop-blur animate-[ntf-08b-up_.45s_cubic-bezier(.22,1.2,.36,1)_both] [.dark_&]:bg-slate-900/95 [.dark_&]:shadow-black/50 [.dark_&]:ring-white/15">
<span class="grid h-9 w-9 flex-none place-items-center rounded-xl bg-sky-500 text-white" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg></span>
<span class="grid min-w-0 gap-0.5"><b class="text-[13.5px] font-semibold text-slate-900 [.dark_&]:text-slate-100">Message sent</b><small class="text-xs text-slate-500 [.dark_&]:text-slate-400">Ana will get it when she's online</small></span>
<button type="button" data-dismiss class="ml-auto grid h-7 w-7 flex-none place-items-center rounded-lg text-slate-400 transition hover:bg-slate-900/5 hover:text-slate-700 [.dark_&]:hover:bg-white/10 [.dark_&]:hover:text-slate-200" aria-label="Dismiss">✕</button>
</div>
</template>
</div>
</section>
</div>
```
## CSS
```css
.ntf-08-group {
display: flex;
flex-wrap: wrap;
align-items: stretch;
width: 100%;
}
.ntf-08-group > section {
flex: 1 1 480px;
min-width: min(100%,360px);
}
.ntf-08a {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
display: grid;
place-items: center;
padding: 48px 24px;
background: radial-gradient(120% 100% at 50% 0%,#0f172a,#020617);
font-family: ui-sans-serif,'Segoe UI',system-ui,sans-serif;
}
@keyframes ntf-08a-in {
from {
opacity: 0;
translate: 16rem 0;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ntf-08a [class*="animate-"] {
animation-name: ntf-08a-fade!important;
animation-duration: .25s!important;
}
@keyframes ntf-08a-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
.ntf-08b {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
font-family: ui-sans-serif,'Segoe UI',system-ui,sans-serif;
}
@keyframes ntf-08b-up {
from {
opacity: 0;
translate: 0 1.5rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ntf-08b [class*="animate-"] {
animation-name: ntf-08b-fade!important;
animation-duration: .25s!important;
}
@keyframes ntf-08b-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
```
## JavaScript
```js
(function(){
var btn=document.getElementById('ntf-08a-btn'); if(!btn) return;
var region=document.getElementById('ntf-08a-region'),tpl=document.getElementById('ntf-08a-tpl');
btn.addEventListener('click',function(){
var toast=tpl.content.firstElementChild.cloneNode(true);
toast.querySelector('[data-dismiss]').addEventListener('click',function(){ toast.remove(); });
region.appendChild(toast);
setTimeout(function(){ if(toast.isConnected) toast.remove(); },5000);
});
})();
(function(){
var scope=document.getElementById('ntf-08b-scope'); if(!scope) return;
var region=document.getElementById('ntf-08b-region'),tpl=document.getElementById('ntf-08b-tpl');
document.getElementById('ntf-08b-btn').addEventListener('click',function(){
region.textContent='';
var toast=tpl.content.firstElementChild.cloneNode(true);
toast.querySelector('[data-dismiss]').addEventListener('click',function(){ toast.remove(); });
region.appendChild(toast);
setTimeout(function(){ if(toast.isConnected) toast.remove(); },5000);
});
document.getElementById('ntf-08b-mode').addEventListener('click',function(){
scope.classList.toggle('dark'); /* darkMode:'selector' — the class, not the OS, decides */
});
})();
```Here's a working CSS Notification from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind CSS Toast Popup Snippet
Source: https://codefronts.com/snippets/css-notifications/tailwind-css-toast-popup-snippet/
Two production-shaped Tailwind toasts: a success popup built entirely from utilities (ring-1, shadow-lg, backdrop-blur) with its slide-in as a one-line custom keyframe consumed via animate-[…] arbitrary values, and a responsive dark-mode-aware toast that sits full-width above the home indicator on phones and docks top-right from the sm: breakpoint up.
## HTML
```html
<div class="ntf-08-group">
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={corePlugins:{preflight:false},darkMode:'selector'}</script>
<section class="ntf-08a" aria-label="Tailwind utility toast">
<div class="mx-auto w-full max-w-md text-center">
<h2 class="text-2xl font-semibold tracking-tight text-slate-100">Utilities all the way down</h2>
<p class="mt-2 text-sm leading-6 text-slate-400">Layout, ring, shadow, blur — all classes. Only the slide-in keyframe is hand-written CSS, consumed with <code class="rounded bg-white/10 px-1.5 py-0.5 text-xs">animate-[…]</code>.</p>
<button id="ntf-08a-btn" type="button" class="mt-6 rounded-xl bg-emerald-400 px-6 py-3 text-sm font-semibold text-slate-950 shadow-lg shadow-emerald-400/25 transition hover:bg-emerald-300 active:scale-95">Complete order</button>
</div>
<div id="ntf-08a-region" role="status" aria-live="polite" class="pointer-events-none absolute right-4 top-4 flex w-80 max-w-[calc(100%-2rem)] flex-col gap-3"></div>
<template id="ntf-08a-tpl">
<div class="pointer-events-auto flex items-center gap-3 rounded-2xl bg-white/95 p-4 shadow-xl shadow-slate-950/40 ring-1 ring-slate-900/10 backdrop-blur animate-[ntf-08a-in_.5s_cubic-bezier(.22,1.2,.36,1)_both]">
<span class="grid h-9 w-9 flex-none place-items-center rounded-xl bg-emerald-500 text-white" aria-hidden="true"><svg class="h-4.5 w-4.5" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg></span>
<span class="grid min-w-0 gap-0.5"><b class="text-[13.5px] font-semibold text-slate-900">Order confirmed</b><small class="text-xs text-slate-500">#8412 · receipt emailed</small></span>
<button type="button" data-dismiss class="ml-auto grid h-7 w-7 flex-none place-items-center rounded-lg text-slate-400 transition hover:bg-slate-900/5 hover:text-slate-700" aria-label="Dismiss">✕</button>
</div>
</template>
</section>
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config={corePlugins:{preflight:false},darkMode:'selector'}</script>
<section class="ntf-08b" aria-label="Responsive dark-mode Tailwind toast">
<div id="ntf-08b-scope" class="absolute inset-0 grid place-items-center bg-slate-100 p-6 transition-colors duration-300 [&.dark]:bg-slate-950">
<div class="w-full max-w-md text-center">
<h2 class="text-2xl font-semibold tracking-tight text-slate-900 [.dark_&]:text-slate-100">Bottom sheet → corner toast</h2>
<p class="mt-2 text-sm leading-6 text-slate-500 [.dark_&]:text-slate-400">Resize the preview: full-width above the home indicator on phones, docked top-right from <code class="rounded bg-slate-900/10 px-1.5 py-0.5 text-xs [.dark_&]:bg-white/10">sm:</code> up. Same element.</p>
<div class="mt-6 flex items-center justify-center gap-3">
<button id="ntf-08b-btn" type="button" class="rounded-xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white shadow-lg transition hover:bg-slate-700 active:scale-95 [.dark_&]:bg-white [.dark_&]:text-slate-900 [.dark_&]:hover:bg-slate-200">Show toast</button>
<button id="ntf-08b-mode" type="button" class="rounded-xl px-5 py-3 text-sm font-semibold text-slate-600 ring-1 ring-slate-900/15 transition hover:bg-slate-900/5 [.dark_&]:text-slate-300 [.dark_&]:ring-white/20 [.dark_&]:hover:bg-white/5">Toggle dark</button>
</div>
</div>
<div id="ntf-08b-region" role="status" aria-live="polite" class="pointer-events-none absolute inset-x-3 bottom-3 sm:inset-x-auto sm:bottom-auto sm:right-4 sm:top-4 sm:w-80"></div>
<template id="ntf-08b-tpl">
<div class="pointer-events-auto flex items-center gap-3 rounded-2xl bg-white/95 p-4 shadow-xl shadow-slate-900/15 ring-1 ring-slate-900/10 backdrop-blur animate-[ntf-08b-up_.45s_cubic-bezier(.22,1.2,.36,1)_both] [.dark_&]:bg-slate-900/95 [.dark_&]:shadow-black/50 [.dark_&]:ring-white/15">
<span class="grid h-9 w-9 flex-none place-items-center rounded-xl bg-sky-500 text-white" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg></span>
<span class="grid min-w-0 gap-0.5"><b class="text-[13.5px] font-semibold text-slate-900 [.dark_&]:text-slate-100">Message sent</b><small class="text-xs text-slate-500 [.dark_&]:text-slate-400">Ana will get it when she's online</small></span>
<button type="button" data-dismiss class="ml-auto grid h-7 w-7 flex-none place-items-center rounded-lg text-slate-400 transition hover:bg-slate-900/5 hover:text-slate-700 [.dark_&]:hover:bg-white/10 [.dark_&]:hover:text-slate-200" aria-label="Dismiss">✕</button>
</div>
</template>
</div>
</section>
</div>
```
## CSS
```css
.ntf-08-group {
display: flex;
flex-wrap: wrap;
align-items: stretch;
width: 100%;
}
.ntf-08-group > section {
flex: 1 1 480px;
min-width: min(100%,360px);
}
.ntf-08a {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
display: grid;
place-items: center;
padding: 48px 24px;
background: radial-gradient(120% 100% at 50% 0%,#0f172a,#020617);
font-family: ui-sans-serif,'Segoe UI',system-ui,sans-serif;
}
@keyframes ntf-08a-in {
from {
opacity: 0;
translate: 16rem 0;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ntf-08a [class*="animate-"] {
animation-name: ntf-08a-fade!important;
animation-duration: .25s!important;
}
@keyframes ntf-08a-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
.ntf-08b {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
font-family: ui-sans-serif,'Segoe UI',system-ui,sans-serif;
}
@keyframes ntf-08b-up {
from {
opacity: 0;
translate: 0 1.5rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.ntf-08b [class*="animate-"] {
animation-name: ntf-08b-fade!important;
animation-duration: .25s!important;
}
@keyframes ntf-08b-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
```
## JavaScript
```js
(function(){
var btn=document.getElementById('ntf-08a-btn'); if(!btn) return;
var region=document.getElementById('ntf-08a-region'),tpl=document.getElementById('ntf-08a-tpl');
btn.addEventListener('click',function(){
var toast=tpl.content.firstElementChild.cloneNode(true);
toast.querySelector('[data-dismiss]').addEventListener('click',function(){ toast.remove(); });
region.appendChild(toast);
setTimeout(function(){ if(toast.isConnected) toast.remove(); },5000);
});
})();
(function(){
var scope=document.getElementById('ntf-08b-scope'); if(!scope) return;
var region=document.getElementById('ntf-08b-region'),tpl=document.getElementById('ntf-08b-tpl');
document.getElementById('ntf-08b-btn').addEventListener('click',function(){
region.textContent='';
var toast=tpl.content.firstElementChild.cloneNode(true);
toast.querySelector('[data-dismiss]').addEventListener('click',function(){ toast.remove(); });
region.appendChild(toast);
setTimeout(function(){ if(toast.isConnected) toast.remove(); },5000);
});
document.getElementById('ntf-08b-mode').addEventListener('click',function(){
scope.classList.toggle('dark'); /* darkMode:'selector' — the class, not the OS, decides */
});
})();
```How this works
Tailwind covers 95% of a toast with utilities — the honest snippet shows exactly where the other 5% lives. Layout and skin are pure utility soup:
<div class="pointer-events-auto flex items-center gap-3
rounded-2xl bg-white/95 p-4 shadow-lg shadow-slate-900/10
ring-1 ring-slate-900/10 backdrop-blur
dark:bg-slate-900/95 dark:ring-white/15">Entry animation is the gap: Tailwind ships no slide-in keyframes, so you declare ONE @keyframes and consume it with an arbitrary value — animate-[ntf-in_.5s_cubic-bezier(.22,1.2,.36,1)_both] — or register it in tailwind.config. The responsive variant is the pattern worth stealing: inset-x-3 bottom-3 sm:inset-x-auto sm:bottom-auto sm:right-4 sm:top-4 sm:w-80 — one element, two placements, no JS measuring. Dark mode rides the same markup via dark: variants with darkMode:'selector' so a class flip (not just the OS) can drive it.
Make it yours
- Swap the accent by changing two utilities (bg-emerald-500 → bg-rose-500, ring-emerald-…) — Tailwind palettes keep contrast steps consistent.
- Registered keyframes beat arbitrary ones once you have 2+ animations: move them to tailwind.config theme.extend.keyframes and get animate-toast-in.
- The sm:w-80 cap is deliberate — full-width toasts on desktop read as banners; 320px reads as a toast.
- Using the CDN in a real project? Don't — install Tailwind properly; the CDN build is for demos exactly like this one.
Gotchas — read before shipping
- The Play CDN injects Preflight (a global reset) that WILL restyle other components on a shared demo page — this snippet disables it via corePlugins:{preflight:false} so it can live in a collection.
- animate-[…] class names can't contain spaces — underscores replace them (animate-[in_.5s_ease] not animate-[in .5s ease]).
- dark: does nothing until you pick a strategy: 'media' follows the OS silently, 'selector' waits for a .dark class — demos that skip this line confuse everyone.
- The toast region needs pointer-events-none with pointer-events-auto restored per toast, or an invisible fixed strip blocks clicks near the corner.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| any | any | any | any |
Utilities compile to plain CSS — support is whatever the underlying properties are (all baseline here). The Play CDN needs JS enabled; in production you'd build with the Tailwind CLI/PostCSS instead and support is identical.