Announcement Bar
block / marketing
The strip above the nav: dismissal that survives a reload, an optional live countdown that takes the bar down when it lapses, and one first render the server and the client both agree on.
- type
- block
- files
- 1
- registry deps
- @automindz/render-link
- npm deps
- lucide-react
installs
- components/automindz/announcement-bar.tsx
- Dismissal persists in localStorage, keyed by id plus version
- One first render for server and client — no mismatch, no flash
- Closing collapses a grid row and retires itself — no timer in React
- Only a removal the reader watched animates; a stale bar is absent
- Dismiss is a sibling of the action link, never inside it
id is an identity, not a label: the localStorage key derives from it, so changing the copy without bumping version ships an announcement that is already dismissed for every reader who closed the last one — and for nobody else. Bump version on a copy change; change id only when it is genuinely a different announcement. No stylesheet and no ui primitives: pass renderLink for next/link, action for the link beside the copy, endsAt (a Date or anything Date.parse takes) for a live countdown that hides the bar at zero. The bar renders on the server and reserves its height, so a reader who has not dismissed it sees no layout shift; a reader who has gets it removed one commit after hydration. To remove that shift entirely, mirror the key into a cookie and skip rendering the bar in your layout. sticky is position: sticky — any ancestor with overflow: hidden kills it. Closing collapses a grid row from 1fr to 0fr and retires the element with a discrete display transition, so the page rises into the space instead of lurching into it; there is no timer in React and nothing extra to wire. Retune it with className="[--automindz-announcement-exit:400ms]" — that property is the exit's only duration, read by both halves of it. Only a removal the reader watched animates: a bar dismissed on an earlier visit, or one whose endsAt was already past on load, is simply absent. Under prefers-reduced-motion: reduce the bar goes and the layout settles in one frame.
Variations
one state per section
- 01Default, dismissible
- 02Dismissing, with a page under it
- 03Primary, counting in days
- 04Expiring, live
- 05Destructive, not dismissible
- 06Same id, different version
- 07Sticky, above the nav
Default, dismissible
Press the X and it is gone for good: the key is written to localStorage and the bar never comes back, in this tab or the next one. Clear it below to re-arm the preview — that button is doing what a version bump does in production.
Batch 4 is live: announcement bar, integration grid and two more marketing blocks.
See what shippedDismissing, with a page under it
The bar is above the nav and in flow, so closing it moves the whole document, not just the strip — press the X and watch the nav rise into the space instead of jumping into it. The collapse is one grid row from 1fr to 0fr, because height: auto cannot be interpolated, and the element retires itself with a discrete display transition rather than a timer in React.
Batch 4 is live.
See what shipped
Products · Pricing · DocsPage content, so there is something for the bar to push down.
Page content, so there is something for the bar to push down.
Page content, so there is something for the bar to push down.
Page content, so there is something for the bar to push down.
Primary, counting in days
Two days out, so the label ticks once an hour and not once a second — a 1Hz interval here would be 259,200 renders of a string that changes 72 times. The abbreviations are aria-hidden; a screen reader gets 'Ends in 2 days 14 hours', and the <time> element carries the real ISO deadline either way.
Early-access pricing for the starter template.
Claim a seatExpiring, live
Under a minute the tick rate follows the label down to seconds — watch it count, and watch the bar take itself off the page at zero rather than sitting there advertising a window that has closed. A lapse leaves exactly the way a dismissal does; a deadline that was already past when the page loaded gets no exit at all, because that is a bar the reader was never meant to see. Reload to run it again.
Last call: the migration window closes shortly.
Start the migrationDestructive, not dismissible
No X at all, because a maintenance notice the reader can hide is a support ticket later. The tone is a destructive tint rather than a solid fill: shadcn guarantees no ink colour on a solid --destructive, so text-destructive on a 10% wash is the pair that holds its contrast in both schemes.
Scheduled maintenance on Sunday 02:00–04:00 UTC. Checkout will be read-only.
Status pageSame id, different version
The sharpest edge in the item, made visible: both bars are id='registry-preview-versioned', but the second carries version={2}. Dismiss the first and the second stays — which is exactly what a copy change needs, and exactly what does not happen if you edit the message and leave the version alone.
v1 — the copy everyone has already dismissed.
v2 — same slot, new key, seen again by everyone.
Sticky, above the nav
Scroll the panel: the bar pins to the top and the nav scrolls under it. position: sticky keeps it in flow, so it pushes the nav down rather than covering it — and any ancestor with overflow: hidden silently kills that, which is CSS, not something the component can defend against.
Registry 0.4 is out.
Changelog
Products · Pricing · DocsPage content, so there is something for the bar to stay above.
Page content, so there is something for the bar to stay above.
Page content, so there is something for the bar to stay above.
Page content, so there is something for the bar to stay above.
Page content, so there is something for the bar to stay above.
Page content, so there is something for the bar to stay above.
Page content, so there is something for the bar to stay above.
Page content, so there is something for the bar to stay above.