Back to Widget Patterns
v2.3Urgency
Limited Time Offer Banner
Top-of-page or bottom banner with a real deadline. Composes CountdownTimer for the countdown display. Includes dismiss persistence and expired-state handling.
Live preview
Rendered with real component code. Each example demonstrates a documented variant.
Warning variant
The banner is normally fixed to the viewport edge. The demo wrapper below uses CSS containment to display it inline.
Ends in
42d 00h 06m 45s
Celebratory variant
The banner is normally fixed to the viewport edge. The demo wrapper below uses CSS containment to display it inline.
Ends in
73d 00h 06m 45s
React props
Configure the component via props in React or via attributes in HTML.
| Prop | Type | Default | Description |
|---|---|---|---|
| message | string | required | The offer copy shown in the banner. Keep to a single line on mobile. |
| deadline | Date | string | required | Passed directly to CountdownTimer. Must be a real, fixed business deadline. |
| cta | { label: string; href: string } | undefined | Optional CTA button rendered after the countdown |
| position | "top" | "bottom" | "top" | Viewport edge to pin the banner to |
| dismissible | boolean | true | When true, renders a close button that persists the dismissed state to localStorage |
| dismissKey | string | "ltob-dismissed" | localStorage key scoping the dismissed state. Change per campaign to avoid cross-campaign conflicts. |
| variant | "warning" | "celebratory" | "minimal" | "warning" | Visual treatment; controls background and text color tokens |
| expiredAction | "hide" | "show-expired-message" | "hide" | Behavior when the countdown reaches zero: hide the banner entirely, or replace its content with expiredMessage |
| expiredMessage | string | "This offer has ended" | Text shown when expiredAction is "show-expired-message" and the deadline has passed |
| className | string | undefined | Extra class appended to the root element |
HTML usage
The HTML variant uses the same shared styles and class names. Drop into any stack.
<!-- Order matters: countdown-timer/styles.css first, then limited-time-offer-banner/styles.css -->
<link rel="stylesheet" href="path/to/countdown-timer/styles.css">
<link rel="stylesheet" href="path/to/limited-time-offer-banner/styles.css">
<div
class="ltob ltob--top ltob--warning"
role="complementary"
aria-label="Promotional offer"
data-dismiss-key="ltob-dismissed"
>
<p class="ltob__message">Sale ends Friday at midnight. 20% off all annual plans.</p>
<div class="ltob__countdown cdt" data-deadline="2026-06-30T23:59:59Z">
<p class="cdt__label">Ends in</p>
<p class="cdt__display" aria-live="off">Loading...</p>
</div>
<a class="ltob__cta" href="/pricing">See pricing</a>
<button class="ltob__close" type="button" aria-label="Dismiss promotional offer">
<svg viewBox="0 0 16 16" aria-hidden="true" width="16" height="16">
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" fill="none"/>
</svg>
</button>
</div>Customization
Override these CSS custom properties to integrate the component into your brand.
.ltob {
--ltob-bg: var(--brand-accent, #b45309);
--ltob-text-color: #ffffff;
--ltob-cta-bg: #ffffff;
--ltob-cta-text-color: #b45309;
--ltob-padding: 0.625rem 1.25rem;
--ltob-z-index: 1000;
--ltob-gap: 1rem;
--ltob-font-size: 0.9375rem;
--ltob-close-size: 2rem;
--ltob-close-opacity: 0.75;
--ltob-close-opacity-hover: 1;
--ltob-cta-radius: 0.375rem;
--ltob-cta-padding: 0.375rem 0.875rem;
--ltob-cta-font-size: 0.875rem;
--ltob-shadow-top: 0 2px 10px rgba(0, 0, 0, 0.14);
--ltob-shadow-bottom: 0 -2px 10px rgba(0, 0, 0, 0.14);
}
/* Variant-specific overrides */
.ltob--warning { --ltob-bg: var(--brand-warning-bg, #b45309); }
.ltob--celebratory { --ltob-bg: var(--brand-celebratory-bg, #166534); }
.ltob--minimal {
--ltob-bg: var(--brand-surface, #ffffff);
--ltob-text-color: var(--brand-ink, #102542);
--ltob-cta-bg: var(--brand-accent, #1e5fcf);
--ltob-cta-text-color: #ffffff;
}