webdevpuneet.com

Upgrade Banner — Free HTML CSS JS Paywall Snippet

Upgrade Banner · Pricing · Plain HTML, CSS & JS · Live preview

CategoryPricing

Copy & paste

Source Code

<div class="app-shell">
  <!-- Simulated app bar -->
  <nav class="app-nav">
    <span class="nav-brand">MyApp</span>
    <span class="plan-chip">Free plan</span>
  </nav>

  <!-- App content area -->
  <main class="app-content">
    <div class="content-block"></div>
    <div class="content-block short"></div>

    <!-- Upgrade banner — shows at the top of the content area -->
    <div class="upgrade-banner" id="upgrade-banner">
      <div class="banner-left">
        <div class="banner-icon">⚡</div>
        <div class="banner-text">
          <strong>You're on the Free plan.</strong>
          <span>Unlock unlimited exports, API access, and priority support.</span>
        </div>
      </div>
      <div class="banner-right">
        <a href="#" class="upgrade-btn">Upgrade to Pro</a>
        <button class="dismiss-btn" onclick="dismiss()" aria-label="Dismiss">✕</button>
      </div>
    </div>

    <!-- Paywall gate for locked features -->
    <div class="feature-gate">
      <div class="gate-inner">
        <div class="gate-icon">🔒</div>
        <div class="gate-title">Team collaboration is a Pro feature</div>
        <p class="gate-desc">Invite your team, share workspaces, and collaborate in real time. Available on Pro and Team plans.</p>
        <a href="#" class="gate-cta">Upgrade to unlock</a>
        <a href="#" class="gate-link">See all Pro features →</a>
      </div>
    </div>

    <div class="content-block short"></div>
  </main>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Dismissible banner: CSS @keyframes max-height + opacity collapse to 0
animationend event removes element from DOM — no layout height JS needed
Gradient banner background: linear-gradient(violet → blue) with matching border
Locked feature gate: 1.5px dashed violet border signals locked/placeholder content
Feature gate: primary upgrade CTA + secondary text link pattern
Plan badge chip in simulated app nav bar showing current plan
Responsive flex layout: banner wraps to two rows on narrow screens
Both components self-contained — no shared wrapper dependencies
Export as HTML file, React JSX component, or React + Tailwind CSS

About this UI Snippet

Upgrade Banner & Paywall Gate — Dismissible In-App Freemium Upsell Components

Screenshot of the Upgrade Banner snippet rendered live

If you are building a freemium SaaS product and need an in-app upgrade prompt that does not annoy users but does convert them, this snippet gives you two components: a dismissible upgrade banner for persistent plan awareness and a locked feature gate for blocking and explaining premium functionality.

The upgrade banner — how it works

The banner sits at the top of the content area and uses a CSS linear-gradient background from violet to blue with a matching border-color. The left side shows a lightning bolt icon, the current plan name in bold, and a short benefit description. The right side has an "Upgrade to Pro" button and a dismiss ✕ button.

The dismiss animation works entirely in CSS: clicking ✕ calls dismiss(), which adds the .dismissed class to the banner. That class triggers a @keyframes animation that animates max-height from the current value to 0 and opacity from 1 to 0 simultaneously. The animationend event listener fires when the animation completes and calls banner.remove() to clean up the DOM. No JavaScript height calculation, no getBoundingClientRect — just CSS driving the collapse and JS handling the cleanup.

The paywall feature gate — how it works

The feature gate replaces the actual content area of a locked feature. It uses a 1.5px dashed border in a violet tint — this dashed border is a well-established visual signal for "placeholder" or "locked" content. Inside, a lock icon, a title explaining which specific feature is gated, a description of the value the user would get, a primary "Upgrade to unlock" CTA, and a secondary "See all Pro features" text link.

This approach — showing the locked area with an explanation rather than hiding it entirely — is a deliberate conversion pattern. Users who see what they are missing have stronger upgrade intent than users who never encounter the feature. The dashed border communicates "this could be yours" rather than "this does not exist."

Persisting dismiss state across page loads

By default, the banner reappears on every page load. To persist the dismiss for N days, add localStorage.setItem("upgrade_dismissed", Date.now()) inside the dismiss() function. On page load, read the key: if the stored timestamp is within the TTL (e.g. Date.now() - stored < 7 * 24 * 60 * 60 * 1000), skip rendering the banner. For a server-rendered app, set a cookie instead so the server can skip the banner HTML entirely.

When to show each component

The banner is best for persistent but low-priority awareness — show it across all pages to free users but make it easy to dismiss. The feature gate is best at the point of intent — when a free user tries to use a specific premium feature. Combining both is the standard freemium pattern: banner for awareness, gate for conversion at the moment of need.

Step by step

How to Use

  1. 1
    Click the ✕ dismiss buttonThe banner collapses upward and fades out via a CSS @keyframes animation. The animationend event removes it from the DOM. Clicking the banner area itself does nothing — only the ✕ button triggers the dismiss.
  2. 2
    Update the banner textIn the HTML, edit the strong tag inside .banner-text to show your plan name ("You're on the Free plan"). Edit the span text to list the top 2-3 benefits of upgrading — keep it under one line.
  3. 3
    Update the feature gate textChange .gate-title to name the specific locked feature (e.g. "API access is a Pro feature"). Update .gate-desc to explain what the user gets and which plan unlocks it.
  4. 4
    Wire the CTA buttons to your billing pageReplace href="#" on .upgrade-btn (banner) and .gate-cta (gate) with your Stripe checkout URL or billing portal link. Both can point to the same URL or to plan-specific checkout sessions.
  5. 5
    Persist dismiss with localStorageInside dismiss() in the JS panel, add localStorage.setItem("upgrade_dismissed", Date.now()). On page load, check if the key exists and is within your TTL. If yes, set banner.style.display = "none" before the user sees it.
  6. 6
    Export in your formatClick "JSX" to download a React component using useState for dismissed and conditional rendering. Click "Tailwind" for a Tailwind CSS version. Click "HTML" for a standalone file.

Real-world uses

Common Use Cases

Persistent freemium upgrade awareness banner
Show the banner across all pages for free-tier users. Make it easy to dismiss but keep it visible enough that users encounter the upgrade message regularly without feeling harassed.
Feature-level paywall gate for premium functionality
Block the actual content area of premium features with the dashed-border gate. Research consistently shows users who see what they are missing convert at a higher rate than users for whom the feature simply does not appear.
Context-triggered limit-reached prompts
Trigger the banner dynamically when a user hits a free plan limit — for example, "You have used 4 of 5 free exports this month." Contextual prompts at the moment of intent convert significantly better than passive top-of-page banners.
Learn CSS @keyframes height collapse animation
The dismiss animation technique — animating max-height and opacity to 0 in @keyframes, then removing the element on animationend — applies to any dismissible notification, cookie banner, or alert banner in any project.
Trial expiry and plan renewal notifications
Replace the banner text with a trial countdown — "Your trial ends in 3 days." Store the trial start date in localStorage, compute the remaining days on load, and inject the count into the banner text dynamically.
Combine with usage calculator for conversion flow
Link the gate upgrade CTA to the usage calculator snippet. Let the user see their personalised cost estimate before they commit — this single step reduces pricing objection and increases checkout completion rates.
Related: Student & Nonprofit Discount Card
See the Student & Nonprofit Discount Card for a related pricing pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

The @keyframes animation targets max-height (animating to 0) and opacity (animating to 0) simultaneously. max-height: 0 collapses the element's space in the layout without needing to know the actual pixel height. padding: 0 and margin: 0 are also set in the final keyframe so no spacing remains. The animationend event fires once the animation completes and calls banner.remove() to fully remove the element from the DOM.

In dismiss(), add localStorage.setItem("upgrade_dismissed", Date.now()) before the animation code. On every page load, check the stored timestamp: const dismissed = localStorage.getItem("upgrade_dismissed"); if (dismissed && Date.now() - dismissed < TTL_MS) banner.style.display = "none"; where TTL_MS is your desired duration in milliseconds (e.g. 604800000 for 7 days). Set TTL_MS to 0 to always show the banner until dismissed once.

Click "JSX" to download. In React, replace the dismissed DOM manipulation with useState(false). Conditionally render the banner with {!dismissed && <div className="upgrade-banner">...</div>}. Call setDismissed(true) in the dismiss handler. For persistence, add localStorage logic inside a useEffect that runs on mount to check the stored dismiss timestamp.

In a backend-rendered app, check the user plan server-side and only include the banner HTML when plan === "free". In a React SPA, read the plan from your auth context: {user.plan === "free" && <UpgradeBanner />}. In vanilla JS with a global, check window.USER_PLAN === "free" at the top of the script and skip the banner if not free.