Components / Notifications
Incoming Call Notification
Free Notifications React component — IncomingCallNotificationBanner. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.
Incoming call card — large avatar, caller name, FaceTime label, Accept and Decline buttons.
Preview
Setup
How to use
Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.
- 1
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/notifications/incoming-call-notification-banner.tsxin your project. - 4
Import and render:
Exampleimport { IncomingCallNotificationBanner } from "@/components/notifications/incoming-call-notification-banner";
lib/cn.ts
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
components/notifications/incoming-call-notification-banner.tsx
"use client";import {forwardRef,useEffect,useState,type ComponentPropsWithoutRef,} from "react";import Image from "next/image";import { cn } from "@/lib/cn";import { Phone, PhoneOff } from "lucide-react";const EXIT_MS = 260;export type IncomingCallNotificationBannerProps = Readonly<{caller?: string;callType?: string;avatarSrc?: string;avatarAlt?: string;showTriggerLabel?: string;onDismiss?: () => void;onAccept?: () => void;onShow?: () => void;} & ComponentPropsWithoutRef<"output">>;// Incoming call — avatar, caller name, accept/decline actions.export const IncomingCallNotificationBanner = forwardRef<HTMLOutputElement,IncomingCallNotificationBannerProps>(({className,caller = "Sarah Chen",callType = "FaceTime Audio",avatarSrc = "/woman.png",avatarAlt = "Caller",showTriggerLabel = "Show incoming call",onDismiss,onAccept,onShow,...props},ref,) => {const [phase, setPhase] = useState<"open" | "closing" | "closed">("open");useEffect(() => {if (phase !== "closing") return;const timer = globalThis.setTimeout(() => setPhase("closed"), EXIT_MS);return () => globalThis.clearTimeout(timer);}, [phase]);const handleDismiss = () => {if (phase !== "open") return;setPhase("closing");onDismiss?.();};const handleShow = () => {setPhase("open");onShow?.();};if (phase === "closed") {return (<buttontype="button"onClick={handleShow}className={cn("cursor-pointer rounded-xl bg-[#f2f2f7] px-4 py-2 text-xs font-medium text-neutral-700","opacity-100 starting:opacity-0","transition-opacity duration-280 ease-out hover:bg-[#e8e8ed]",className,)}>{showTriggerLabel}</button>);}return (<outputref={ref}data-slot="incoming-call-notification-banner"data-phase={phase}className={cn("block w-80 rounded-[1.25rem] border border-white/70 bg-white/90 p-3.5 font-sans backdrop-blur-xl","shadow-[0_8px_32px_-4px_rgba(0,0,0,0.10)]","translate-y-0 opacity-100 starting:-translate-y-2.5 starting:opacity-0","transition-all duration-340 ease-[cubic-bezier(0.25,0.46,0.45,0.94)]","data-[phase=closing]:-translate-y-2 data-[phase=closing]:opacity-0","data-[phase=closing]:duration-260 data-[phase=closing]:ease-[cubic-bezier(0.4,0,0.6,1)]",className,)}{...props}><div className="flex items-center gap-3"><div className="relative size-11 shrink-0 overflow-hidden rounded-full ring-2 ring-neutral-100"><Imagesrc={avatarSrc}alt={avatarAlt}fillsizes="44px"className="object-cover"/></div><div className="min-w-0 flex-1"><p className="truncate text-[15px] font-semibold text-neutral-900">{caller}</p><p className="truncate text-[12px] text-neutral-500">{callType}</p></div></div><div className="mt-3 flex gap-2"><buttontype="button"onClick={handleDismiss}className="flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-full bg-red-500 py-2 text-[13px] font-semibold text-white transition-colors hover:bg-red-600"><PhoneOff size={14} strokeWidth={2} aria-hidden />Decline</button><buttontype="button"onClick={onAccept}className="flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-full bg-emerald-500 py-2 text-[13px] font-semibold text-white transition-colors hover:bg-emerald-600"><Phone size={14} strokeWidth={2} aria-hidden />Accept</button></div></output>);},);IncomingCallNotificationBanner.displayName = "IncomingCallNotificationBanner";