Announcement Banner
blockmarketingInteractive release, security, and maintenance broadcast center with rotating live ticker, CLI copy button, and floating pill / top-bar modes.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/announcement-banner.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/announcement-banner.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/announcement-banner.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/announcement-banner.jsonManifest:
https://uipkge.dev/r/svelte/announcement-banner.jsonInstalls to:src/lib/components/blocks/announcement-banner/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Includes
Files installed (1)
src/lib/components/blocks/announcement-banner/AnnouncementBanner.svelte9.3 kB
<script lang="ts" module> export interface AnnouncementItem { id: string category: 'release' | 'security' | 'maintenance' | 'feature' badgeText: string title: string actionLabel: string actionUrl?: string cliSnippet?: string } export interface AnnouncementBannerProps { variant?: 'floating-pill' | 'top-bar' | 'interactive-ticker' autoplay?: boolean class?: string } </script> <script lang="ts"> import { onDestroy, onMount } from 'svelte' import { ArrowRight, ChevronLeft, ChevronRight, Megaphone, Terminal, X } from '@lucide/svelte' import { Badge } from '$lib/components/ui/badge' import { cn } from '$lib/utils' let { variant = 'interactive-ticker', autoplay = true, class: className }: AnnouncementBannerProps = $props() const announcements: AnnouncementItem[] = [ { id: 'ann-1', category: 'release', badgeText: 'v2.4.0 Release', title: 'Tailwind CSS v4 OKLCH tokens & 281+ production workbenches are live.', actionLabel: 'Read Changelog', actionUrl: 'https://uipkge.dev/changelog', cliSnippet: 'npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/init.json', }, { id: 'ann-2', category: 'feature', badgeText: 'Multi-Framework', title: 'Svelte 5 runes parity with the Vue 3.5 & React 19 registries, zero runtime CSS.', actionLabel: 'Explore Parity Matrix', actionUrl: 'https://uipkge.dev/components', cliSnippet: 'npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/button.json', }, { id: 'ann-3', category: 'security', badgeText: 'Security Notice', title: 'Upstream dependency security patches verified and updated.', actionLabel: 'Security Advisory', actionUrl: 'https://uipkge.dev/security', }, ] let currentIndex = $state(0) let dismissed = $state(false) let isPaused = $state(false) let copiedCli = $state(false) let timer: ReturnType<typeof setInterval> | undefined function nextAnnouncement() { currentIndex = (currentIndex + 1) % announcements.length } function prevAnnouncement() { currentIndex = (currentIndex - 1 + announcements.length) % announcements.length } function startAutoplay() { if (!autoplay) return stopAutoplay() timer = setInterval(() => { if (!isPaused) nextAnnouncement() }, 6000) } function stopAutoplay() { if (timer) clearInterval(timer) } onMount(startAutoplay) onDestroy(stopAutoplay) function copyCliSnippet(snippet?: string) { if (!snippet) return navigator.clipboard.writeText(snippet) copiedCli = true setTimeout(() => (copiedCli = false), 2000) } const current = $derived(announcements[currentIndex]) </script> {#if !dismissed} <div data-slot="announcement-banner" data-variant={variant} role="region" aria-label="Announcement" class={cn('relative z-50 transition-colors duration-200', className)} onmouseenter={() => (isPaused = true)} onmouseleave={() => (isPaused = false)} > <!-- 1. Interactive Ticker Mode (Default) --> {#if variant === 'interactive-ticker'} <div class="bg-muted/70 border-border text-foreground flex items-center justify-between gap-3 overflow-hidden border-b px-4 py-2 text-xs shadow-xs backdrop-blur-md" > <div class="mx-auto flex w-full max-w-6xl items-center justify-between gap-4"> <!-- Left: Status Beacon & Item Indicator --> <div class="flex shrink-0 items-center gap-3"> <div class="text-muted-foreground flex items-center gap-1.5 font-mono text-xs"> <span class="bg-primary size-2 animate-pulse rounded-full"></span> <span class="text-foreground font-semibold">Live Dispatch</span> </div> <Badge variant="secondary" class={cn( 'px-2 py-0.5 font-mono text-xs', current.category === 'release' && 'bg-primary/10 text-primary border-primary/20', current.category === 'security' && 'border-warning/20 bg-warning/10 text-warning', current.category === 'feature' && 'border-success/20 bg-success/10 text-success', )} > {current.badgeText} </Badge> </div> <!-- Center: Dynamic Headline & Action Link --> <div class="flex flex-1 items-center justify-center gap-2 truncate"> <p class="text-foreground truncate font-medium"> {current.title} </p> {#if current.actionUrl} <a href={current.actionUrl} class="text-primary hidden shrink-0 items-center gap-1 font-semibold hover:underline sm:inline-flex" > {current.actionLabel} <ArrowRight class="size-3" /> </a> {/if} </div> <!-- Right: Ticker Navigation & CLI / Dismiss --> <div class="flex shrink-0 items-center gap-2"> {#if current.cliSnippet} <button type="button" class="bg-background border-border text-muted-foreground hover:text-foreground hidden items-center gap-1.5 rounded border px-2 py-0.5 font-mono text-xs transition-colors md:inline-flex" onclick={() => copyCliSnippet(current.cliSnippet)} > <Terminal class="text-primary size-3" /> <span>{copiedCli ? 'Copied CLI!' : 'Copy CLI'}</span> </button> {/if} <div class="border-border bg-background flex items-center overflow-hidden rounded-md border"> <button type="button" class="hover:bg-muted text-muted-foreground hover:text-foreground p-1 transition-colors" aria-label="Previous announcement" onclick={prevAnnouncement} > <ChevronLeft class="size-3.5" /> </button> <span class="text-muted-foreground px-1.5 font-mono text-xs"> {currentIndex + 1}/{announcements.length} </span> <button type="button" class="hover:bg-muted text-muted-foreground hover:text-foreground p-1 transition-colors" aria-label="Next announcement" onclick={nextAnnouncement} > <ChevronRight class="size-3.5" /> </button> </div> <button type="button" aria-label="Dismiss banner" class="text-muted-foreground hover:text-foreground hover:bg-muted rounded-md p-1 transition-colors" onclick={() => (dismissed = true)} > <X class="size-3.5" /> </button> </div> </div> </div> <!-- 2. Floating Pill Mode --> {:else if variant === 'floating-pill'} <div class="mx-auto w-fit max-w-2xl px-4 py-2"> <div class="bg-card/90 border-border text-foreground hover:border-primary/40 flex items-center gap-3 rounded-full border py-1.5 pr-2 pl-3 text-xs shadow-md backdrop-blur-md transition-colors" > <Badge variant="secondary" class="bg-primary/10 text-primary border-primary/20 px-2 py-0.5 font-mono text-xs"> {current.badgeText} </Badge> <p class="text-foreground max-w-sm truncate font-medium"> {current.title} </p> {#if current.actionUrl} <a href={current.actionUrl} class="text-primary inline-flex shrink-0 items-center gap-1 font-semibold hover:underline" > {current.actionLabel} <ArrowRight class="size-3" /> </a> {/if} <button type="button" aria-label="Dismiss announcement" class="text-muted-foreground hover:text-foreground hover:bg-muted rounded-full p-1 transition-colors" onclick={() => (dismissed = true)} > <X class="size-3.5" /> </button> </div> </div> <!-- 3. Top Solid Bar Mode --> {:else} <div class="bg-primary text-primary-foreground flex items-center justify-between gap-3 px-4 py-2 text-xs shadow-xs"> <div class="mx-auto flex w-full max-w-6xl items-center justify-between gap-4"> <div class="flex items-center gap-2 truncate"> <Megaphone class="size-4 shrink-0" /> <span class="font-mono font-bold">[{current.badgeText}]</span> <span class="truncate">{current.title}</span> </div> <div class="flex shrink-0 items-center gap-3"> {#if current.actionUrl} <a href={current.actionUrl} class="inline-flex items-center gap-1 font-semibold underline-offset-4 hover:underline" > {current.actionLabel} <ArrowRight class="size-3" /> </a> {/if} <button type="button" aria-label="Dismiss banner" class="text-primary-foreground/80 hover:text-primary-foreground hover:bg-primary-foreground/15 rounded p-1 transition-colors" onclick={() => (dismissed = true)} > <X class="size-3.5" /> </button> </div> </div> </div> {/if} </div> {/if}
Raw manifest:https://uipkge.dev/r/svelte/announcement-banner.json