⏳ Component
Timer Widget
Live countdown timer updating every second, with days/hours/minutes/seconds to any target date. Style it with neon/dark/gray variants, or drop in a background image with an adjustable overlay scrim for a photo-backed hero look.
Interactive Demo Playground
Configure Properties
Usage Code Generator
import TimerWidget from '@contentvidya/ui/react/TimerWidget';
import '@contentvidya/ui/theme.css';
// import { defaultBackgroundEffectPlugin } from '@contentvidya/ui/react/utils/backgroundEffects';
// Optionally override the canvas animation engine per-instance:
// backgroundEffectPlugin={{ start: myStart, stop: myStop }}
<TimerWidget
title="Sale ends in:"
targetDate="2026-12-31T23:59:59Z"
variant="neon"
backgroundImageUrl="/hero-sale.jpg"
backgroundPosition="center"
overlay="linear-gradient(to top, rgba(0,0,0,.7), rgba(0,0,0,.2))"
backgroundEffect="particles"
expiredText="This sale has ended — check back soon!"
width="100%"
/>
<script lang="ts">
import TimerWidget from '@contentvidya/ui/svelte/TimerWidget.svelte';
// import { defaultBackgroundEffectPlugin } from '@contentvidya/ui/svelte/utils/backgroundEffects';
// Optionally override the canvas animation engine per-instance:
// backgroundEffectPlugin={{ start: myStart, stop: myStop }}
</script>
<TimerWidget
title="Sale ends in:"
targetDate="2026-12-31T23:59:59Z"
variant="neon"
backgroundImageUrl="/hero-sale.jpg"
overlay="linear-gradient(to top, rgba(0,0,0,.7), rgba(0,0,0,.2))"
backgroundEffect="particles"
expiredText="This sale has ended — check back soon!"
/>
<script type="module"
src="node_modules/@contentvidya/ui/webcomponents/TimerWidget.js"></script>
<!-- backgroundEffectPlugin ({ start, stop } functions) can't be passed as an
HTML attribute string — set it as a JS property instead:
document.querySelector('timer-widget').backgroundEffectPlugin = { start: myStart, stop: myStop }; -->
<timer-widget
title="Sale ends in:"
target-date="2026-12-31T23:59:59Z"
variant="neon"
background-image-url="/hero-sale.jpg"
overlay="linear-gradient(to top, rgba(0,0,0,.7), rgba(0,0,0,.2))"
background-effect="particles"
expired-text="This sale has ended — check back soon!"
></timer-widget>
Props API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| targetDate required | string | — | ISO 8601 date-time string; the countdown ticks towards this date |
| title | string | undefined | Optional heading displayed above the timer blocks |
| variant | 'neon' | 'dark' | 'gray' | undefined | Visual color theme for the timer blocks (default: 'dark') |
| backgroundImageUrl | string | undefined | Background image URL. When set, renders behind the widget with a readability overlay so timer blocks stay legible over photos |
| backgroundPosition | string | undefined | CSS background-position for the background image (default: 'center') |
| overlay | string | undefined | CSS background for the scrim over the background image, e.g. a gradient or rgba() color (default: 'rgba(0, 0, 0, 0.45)') |
| backgroundEffect | 'none' | 'particles' | 'waves' | 'rain' | 'thunderstorm' | 'sunrise' | 'sunset' | 'fog' | 'autumn' | 'festival' | 'santa' | 'sea' | undefined | Animated canvas layer rendered behind the timer blocks, from the same shared plugin engine (src/utils/backgroundEffects.ts) also used by Banner and SlidingBanner. Default: 'none' |
| backgroundEffectPlugin | BackgroundEffectPlugin | undefined | Optional { start, stop } override for the canvas animation engine, letting a consumer (e.g. the CMS page editor) inject its own effect renderer per-instance instead of the built-in one. Falls back to defaultBackgroundEffectPlugin exported from src/utils/backgroundEffects.ts when omitted. |
| expiredText | string | undefined | Message shown once targetDate has passed, in place of the countdown blocks |
| width | string | undefined | CSS width for the root element (default: 'auto') |
| height | string | undefined | CSS height for the root element (default: 'auto'). When 'auto' and backgroundImageUrl is set, the widget's height follows the image's natural aspect ratio via an in-flow |
| className | string | undefined | Additional CSS class names on the root element |
| lazyLoad | boolean | undefined | Defer starting the countdown interval until scrolled near the viewport (default: true) |
| lazyThreshold | number | undefined | IntersectionObserver threshold, 0-1 (default: 0.1) |
| lazyRootMargin | string | undefined | IntersectionObserver rootMargin (default: '200px') |
Past dates: When
targetDate is in the past all values show 0, or expiredText if set. Update or remove the component via your CMS when the countdown expires.SSR: The timer renders
0 on the server then hydrates on the client, avoiding any hydration mismatch.Custom animation engine: Pass
backgroundEffectPlugin={{ start, stop }} to swap the built-in canvas renderers for your own — useful for a CMS page editor that wants to inject a different animation implementation per-instance. Omit it to use the built-in defaultBackgroundEffectPlugin.Background image: Set
backgroundImageUrl to render a photo behind the countdown, with a legibility scrim controlled by overlay (any CSS background value — solid, rgba, or gradient). Leave height at its default 'auto' to let the widget follow the image's natural aspect ratio, or pin an explicit height (e.g. '320px') to avoid layout shift.Background effects: Set
backgroundEffect to 'particles', 'waves', 'rain', 'thunderstorm', 'sunrise', 'sunset', 'fog', 'autumn', 'festival', 'santa', or 'sea' to render an animated canvas layer behind the countdown — the same shared plugin engine (src/utils/backgroundEffects.ts) used by Banner and SlidingBanner. Combine with backgroundImageUrl and overlay for a photo backdrop with motion, or use it alone over the solid variant background.