freebies /
Sticky Announcement Bar
Fixed top CTA bar that persists while scrolling. Dismissable with localStorage memory so it stays gone after close.
Preview
Source
tsx
"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { X, Sparkles } from "lucide-react";
const STORAGE_KEY = "innovations_sticky_bar_dismissed";
export default function StickyBar() {
const [visible, setVisible] = useState(false);
// SSR-guarded: check localStorage only in the browser
useEffect(() => {
const dismissed = localStorage.getItem(STORAGE_KEY);
if (!dismissed) {
setVisible(true);
}
}, []);
function dismiss() {
setVisible(false);
localStorage.setItem(STORAGE_KEY, "true");
}
if (!visible) {
return (
<div className="flex min-h-[200px] flex-col items-center justify-center gap-4 p-8 text-center">
<p className="text-sm text-muted-foreground">
Sticky bar is dismissed.{" "}
<button
onClick={() => {
localStorage.removeItem(STORAGE_KEY);
setVisible(true);
}}
className="text-primary underline hover:no-underline"
>
Reset demo
</button>
</p>
</div>
);
}
return (
<>
{/* Sticky bar */}
<div className="fixed top-0 left-0 right-0 z-50 flex items-center justify-between gap-4 bg-primary px-4 py-3 sm:px-6">
<div className="flex items-center gap-2 text-primary-foreground min-w-0">
<Sparkles className="h-4 w-4 shrink-0 opacity-80" />
<p className="text-sm font-medium truncate">
<span className="font-bold">Limited time:</span> Get our free growth playbook — no strings attached.
</p>
</div>
<div className="flex items-center gap-2 shrink-0">
<Button
size="sm"
variant="secondary"
className="h-7 text-xs font-semibold hidden sm:inline-flex"
asChild
>
<a href="#optin">Grab it free</a>
</Button>
<button
onClick={dismiss}
aria-label="Dismiss"
className="rounded-full p-1 text-primary-foreground/70 transition-colors hover:bg-white/20 hover:text-primary-foreground"
>
<X className="h-4 w-4" />
</button>
</div>
</div>
{/* Spacer to prevent content jump */}
<div className="h-11" />
{/* Preview content */}
<div className="flex min-h-[180px] items-center justify-center p-8">
<p className="text-sm text-muted-foreground text-center max-w-xs">
Scroll down — the bar stays pinned to the top. Dismiss it with ✕ and it won't reappear (localStorage).
</p>
</div>
</>
);
} Claude Code Instructions
CLI Install
npx innovations add sticky-barWhere to use it
Add to your root layout so it appears on every page.
In Next.js root layout (app/layout.tsx):
import StickyBar from '@/components/innovations/freebies/sticky-bar';
// Place as the first child inside <body>
In Astro root layout:
<StickyBar client:load />
The localStorage check is inside useEffect so it's SSR-safe.
Change STORAGE_KEY to a unique string per promotion so returning visitors see new bars.
The bar uses bg-primary — override with any Tailwind background class to match your brand.
Add padding-top to your main content container (pt-11 or pt-14) to prevent the bar from overlapping content.