crucıble

Pennant

Announcement bar + cookie consent. Pennant is a slim top bar that rotates short messages with a vertical slide (paused on hover/focus, hairline progress meter), rolls an odometer countdown to a deadline and collapses on a height spring when dismissed, remembered via storageKey. Consent is a non-modal cookie card that rises into a bottom corner (full-width bar under 640px) with Accept / Reject / Customize; Customize grows the same card into per-category switches with Necessary locked on, reports onDecision, persists choices and reopens via ref from a footer link.

motionfree

Installation

CLI

npx shadcn@latest add @crucible/pennant

Manual — install dependencies, then copy the source

npm install motion

Props

PropTypeDefaultDescription
messagesPennantMessage[]Messages to rotate through with a vertical slide. One message renders static. Omit (and omit text) for a three-message sample set.
textReact.ReactNodeSingle-message shorthand: the announcement copy. Ignored when messages is set.
linkPennantLinkSingle-message shorthand: inline link.
badgestringSingle-message shorthand: pill before the text.
endsAtDate | number | stringSingle-message shorthand: rolling countdown target.
intervalnumber6000Milliseconds each message holds before sliding to the next.
dismissiblebooleantrueShow the dismiss button.
storageKeystringlocalStorage key that remembers a dismissal across visits. Bump the key when you publish a new announcement. Omit to never persist.
appearance"subtle" | "solid""subtle""subtle" is a near-black bar with accent details; "solid" fills it with the accent.
accentColorstring"#818cf8"Accent hex for the pill, link, countdown and focus rings.
labelstring"Announcement"Accessible name of the landmark region.
onDismiss() => voidCalled after the bar is dismissed by the user or dismiss().
onMessageChange(index: number) => voidCalled when the visible message changes (index into the message list).
refReact.Ref<PennantHandle>Imperative handle (show, dismiss, replay, next, prev). React 19 ref-as-prop.
classNamestringExtra classes for the outer region.
Also accepts all props of Omit<React.ComponentPropsWithoutRef<"div">, "children"> — they pass through to the underlying element.

Honors prefers-reduced-motion with a designed static fallback, and passes className through.