More Pricing Snippets
Trial Countdown Banner — Free HTML CSS JS Snippet
Trial Countdown Banner · Pricing · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Trial Countdown Banner — Days Remaining, Trial Progress Bar, Expiry Date & Upgrade CTA

A trial countdown banner keeps free trial users aware of their remaining trial time throughout their product session — the most effective in-product conversion mechanism for SaaS products. This snippet provides a complete in-app trial countdown system: a top banner with days remaining, a trial progress bar (Day X of 14), an expiry date, a high-contrast amber upgrade CTA, a dismiss button, and a separate feature limit warning card that appears in the main content area. For a persistent page-wide variant, see the upgrade banner snippet.
The trial countdown calculation
The initTrial() function computes elapsed and remaining days from the trial start date. elapsed = floor((now - start) / 86400000) — the difference in milliseconds divided by milliseconds per day. remaining = TRIAL_DAYS - elapsed. The progress bar fill percentage = elapsed / TRIAL_DAYS * 100. The expire date is computed by adding TRIAL_DAYS to the start date.
The banner design
A dark indigo gradient banner (linear-gradient from #1e1b4b to #312e81) sits at the top of the app shell. White text on the dark background provides high contrast. The days remaining number is highlighted in amber (#fbbf24) — the most urgent visual signal without red. The upgrade button also uses amber, creating visual alignment between the urgency signal and the action.
The trial progress bar
A horizontal progress bar shows how far through the trial the user is. The gradient fill goes from indigo to amber — moving toward the amber urgency colour as the trial nears its end. "Trial started" and "Trial ends" labels frame the bar. "Day X of 14" below gives exact context.
The feature limit warning card
A separate limit card appears in the main content area when the user approaches a free plan limit (4 of 5 projects used). This contextual prompt converts at a higher rate than the persistent banner alone — it triggers at the exact moment the user encounters the constraint.
Dismiss handling
The dismiss button collapses the banner via JavaScript animation (max-height from scrollHeight to 0). Uncommenting the localStorage line in dismissBanner() persists the dismiss across page loads. For server-rendered apps, save the dismiss timestamp to the user's account instead. For a fully server-rendered approach, add a dismissed_at timestamp to the user table and skip the banner HTML entirely when within the TTL.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Instead of working out the date math on your own, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why initTrial() zeroes out the hours on both the now and start Date objects with setHours(0,0,0,0) before subtracting them, and what bug would appear if that step were skipped near a daylight-saving transition. It's also a good candidate for an optimization pass — ask whether recalculating and re-rendering the whole banner on every page load is the right approach versus computing elapsed/remaining once on the server and passing it down as props. For extending it, have it add a color progression that shifts the banner from indigo to red as remaining days approach zero, wire the dismiss button to localStorage with a re-show timeout, or add a second banner variant driven by usage percentage instead of days. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a SaaS trial countdown banner in plain HTML, CSS, and vanilla JavaScript with no libraries.
Requirements:
- Configure a trial start Date and a trial length in days as top-level constants.
- Compute elapsed days as the floor of the millisecond difference between today and the start date divided by 86400000, after zeroing the hours/minutes/seconds/milliseconds on both Date objects so the calculation is immune to time-of-day drift.
- Compute remaining days as the trial length minus elapsed, clamped to a minimum of zero, and compute a fill percentage as elapsed divided by trial length times 100, clamped to a maximum of 100.
- Display the remaining days with correct singular/plural wording ("1 day" vs "3 days"), update a progress bar's width to the computed percentage, show "Day X of N" text, and compute and format the expiry date (start date plus trial length days) as a short human-readable date like "Jun 7".
- Include a dismiss button that collapses the banner with an animated transition of max-height, opacity, and padding down to zero, driven by first setting max-height to the banner's current scrollHeight, then on the next animation frame collapsing it to zero so the CSS transition actually animates instead of jumping instantly.
- Include a separate feature-limit warning card elsewhere on the page showing usage against a plan limit (e.g. "4 of 5 free projects used"), with its own upgrade call-to-action pointing to the same destination as the banner's upgrade button.Step by step
How to Use
- 1Set your trial start date and lengthIn the JS panel, set TRIAL_START_DATE to your user's actual trial start (from your database) and TRIAL_DAYS to your trial length (14 or 30 days). The banner auto-computes days remaining, progress bar fill, and expiry date.
- 2Wire the Upgrade CTASet href="#" on .upgrade-btn and .limit-cta to your billing page, Stripe checkout session, or upgrade flow URL. Use the same destination for both to keep the upgrade path consistent.
- 3Update the feature limit cardEdit .limit-title ("You've used 4 of 5 free projects") and .limit-sub to match your actual free plan limits. Show or hide this card based on the user's actual usage relative to the free tier threshold.
- 4Persist dismiss state across page loadsUncomment the localStorage line inside dismissBanner(): localStorage.setItem("trial_banner_dismissed","1"). On page load, check: if (localStorage.getItem("trial_banner_dismissed")) { banner.style.display = "none"; }
- 5Change the banner colour schemeThe banner uses a dark indigo gradient. For a lighter scheme, change background to linear-gradient(135deg, #eff6ff, #dbeafe) and text colour to #1e40af. Update the upgrade button to background: #1e40af and colour: #fff.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component using a server-fetched trialStartDate prop and useState for dismissed state, or "Tailwind" for a Tailwind CSS version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
initTrial() computes elapsed days: const elapsed = Math.floor((now - start) / 86400000). Both dates are set to midnight (setHours(0,0,0,0)) before subtraction to avoid time-zone and hour-of-day drift. 86400000 is the number of milliseconds in one day. remaining = TRIAL_DAYS - elapsed. Progress percentage = elapsed / TRIAL_DAYS * 100. The expiry date is computed by adding TRIAL_DAYS days to the start date using setDate(start.getDate() + TRIAL_DAYS).
Uncomment the localStorage line in dismissBanner(): localStorage.setItem("trial_banner_dismissed", "1"). At the end of initTrial(), add: if (localStorage.getItem("trial_banner_dismissed")) { document.getElementById("trial-banner").style.display = "none"; return; }. For a more sophisticated approach, store a timestamp and re-show the banner after 3 days: const dismissed = localStorage.getItem("trial_dismissed"); if (dismissed && Date.now() - parseInt(dismissed) < 3*24*3600000) return.
In initTrial(), check the remaining days and apply a different gradient class: if (remaining <= 3) { banner.style.background = "linear-gradient(135deg, #7f1d1d, #b91c1c)"; } else if (remaining <= 7) { banner.style.background = "linear-gradient(135deg, #78350f, #92400e)"; }. This creates a colour progression from indigo (comfortable) through amber (urgent) to red (critical) as the trial countdown reaches its final days.
Click "JSX" to download. Accept trialStartDate and trialDays as props from the server (fetched from your database in getServerSideProps or a Server Component). Compute elapsed, remaining, and pct in the component body or with useMemo. Manage dismissed with useState(false). Set the dismiss handler to call setDismissed(true) and optionally write to localStorage. Conditionally render: {!dismissed && <TrialBanner ... />}.