Staaarter

Sale Countdown Timer

A live sale countdown that genuinely ticks down once a second from a fixed starting duration, in three layouts: a card with large digit tiles, an inline compact bar, or a full-width colored banner. Built to be styled as "time remaining as of page load" rather than a wall-clock deadline.

By Staaarter Team
Ecommerce
Docs
Live preview

Docs

Installation

A real shadcn registry command, not a copy-paste stand-in: it fetches this block plus its button dependencies and any missing npm packages, and installs them straight into your project.

npx shadcn add https://staaarter.com/r/sale-countdown-timer.json

Prefer not to use the CLI? Copy the source from the Code toggle above into src/components/blocks/ecommerce/ecommerce5.tsx instead.

Usage

The file also exports ecommerce5Demo, the exact props behind the preview above. Spread it to get a working section in one line, then replace it with your own data.

import { Ecommerce5, ecommerce5Demo } from "@/components/blocks/ecommerce/ecommerce5";

export default function Page() {
  return <Ecommerce5 {...ecommerce5Demo} />;
}

Props

PropTypeDefaultDescription
headingstring"Summer Sale ends soon"Heading text (or banner message when it hasn't ended).
descriptionstring"Up to 40% off sitewide..."Supporting copy shown in the default and compact variants.
ctaLabelstring"Shop the sale"Label for the call-to-action button.
ctaHrefstring"#"Destination for the call-to-action button.
initialDurationCountdownDurationnoneTime remaining as of when the component mounts; the timer counts down from this in memory.
variant"default" | "compact" | "banner""default"Layout: card with large digit tiles, inline compact bar, or full-width colored banner.
classNamestringnoneExtra classes for the outer section element.

Types

interface CountdownDuration {
  days: number;
  hours: number;
  minutes: number;
  seconds: number;
}

Behavior notes

  • The countdown is a real timer: `initialDuration` is converted to a total-seconds count once via a lazy `useState` initializer, then a `setInterval` inside `useEffect` decrements it every second down to zero, it never reads `Date.now()` or constructs a target `Date`, so the server-rendered markup and the client's first tick always agree.
  • It stops exactly at zero rather than going negative; the banner variant swaps its message to "This sale has ended." once it does, the default and compact variants show a small "This offer has ended." line instead.
  • The `variant` prop is a real, complete layout switch (three different JSX trees), not a CSS-only reskin of the same markup.
  • The call-to-action button keeps working after the timer ends, it never disables itself, in a real store you'd conditionally hide or redirect it once the sale is over.