Skip to content
All Taste components

Cards & surfaces

Stacked notifications

A tidy pile of notifications that fans out into a readable list when you hover it.

stacked · fanned · drop-in entrance

Payment received$2,400 from Kaveri Foodsnow
Asha commented“Ship the new pricing on Monday?”4m
Deploy finishedtaste-web is live on production12m
Hover the pile to open it
"use client";

import { useState } from "react";
import { cn } from "@/lib/utils";

interface Note { id: string; title: string; text: string; time: string; icon: React.ReactNode }

export function NotificationStack({ notes }: { notes: Note[] }) {
  const [open, setOpen] = useState(false);
  const shown = notes.slice(0, 3);
  return (
    <div
      onMouseEnter={() => setOpen(true)}
      onMouseLeave={() => setOpen(false)}
      onFocus={() => setOpen(true)}
      onBlur={() => setOpen(false)}
      className="relative w-full max-w-xs transition-[height] duration-500 ease-spring motion-reduce:transition-none"
      style={{ height: open ? 70 + (shown.length - 1) * 72 : 92 }}
    >
      {shown.map((n, i) => (
        <article
          key={n.id}
          tabIndex={0}
          className="absolute inset-x-0 top-0 flex items-start gap-2.5 rounded-lg border border-border bg-bg px-3 py-2.5 text-[13px] shadow-md transition-transform duration-500 ease-spring animate-[stack-in_.6s_cubic-bezier(.2,.7,.1,1)_both] motion-reduce:animate-none motion-reduce:transition-none"
          style={{
            zIndex: shown.length - i,
            transformOrigin: "50% 0",
            transform: open ? "translateY(" + i * 72 + "px)" : "translateY(" + i * 10 + "px) scale(" + (1 - i * 0.05) + ")",
            animationDelay: 100 + (shown.length - 1 - i) * 90 + "ms",
          }}
        >
          <span className="grid size-[30px] flex-none place-items-center rounded-full bg-primary-soft text-primary-soft-fg">{n.icon}</span>
          <span className={cn("min-w-0 flex-1 transition-opacity duration-300", !open && i > 0 && "opacity-0")}>
            <b className="block font-medium text-fg">{n.title}</b>
            <span className="block truncate text-fg-muted">{n.text}</span>
          </span>
          <span className={cn("whitespace-nowrap text-[11.5px] text-fg-subtle transition-opacity duration-300", !open && i > 0 && "opacity-0")}>{n.time}</span>
        </article>
      ))}
    </div>
  );
}

/* globals.css
@keyframes stack-in { from { opacity: 0; translate: 0 -12px; } }
*/

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.