> For the complete documentation index, see [llms.txt](https://sygnal.gitbook.io/sygnal-webflow-components/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sygnal.gitbook.io/sygnal-webflow-components/cc/sygnal-components/countdown-timer.md).

# Countdown Timer ✅

A simple countdown timer for marketers, to manufacture urgency.

This is a personalized countdown timer that begins counting on each visitor's first visit. The timer persists across page loads and browser sessions using LocalStorage.

## Features

* Custom countdown timer, any duration. timer representation
* 100% designer-controlled styling, any element arrangement you want&#x20;
* Timer-expired state, e.g. hide the timer, show a different element set&#x20;
* Animation support- the ability to trigger custom code to handle custom animations as the timer updates&#x20;
* Debug support, so you can see what it's tracking and calculating&#x20;

<figure><img src="https://2784689242-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fijt1q5GHhhw4y4FjjPNG%2Fuploads%2Fer0qTIgv5dklcYk3Pbt8%2F2026-01-11_14-08-02.gif?alt=media&amp;token=c5de7945-73ae-47ab-b451-8c99fdd7bc02" alt=""><figcaption></figcaption></figure>

## Demo

{% embed url="<https://webflow-core-component-test.webflow.io/media/countdown-timer>" %}

## Use Cases

Currently this timer is designed for marketing purposes, so the start and end time are user-specific. The primary use case is;

* Create urgency from first visit to purchase or other conversion action&#x20;

If it's desired, I'll expand this to other variants, like "count to deadline", which could be used for;

* Submission deadlines&#x20;
* Time-until-launch&#x20;

## Quick Start

1. Install the Sygnal Components lib from [Sygnal Marketplace](https://www.sygnal.com/market)&#x20;
2. Add the **Countdown Timer** component to your page
3. Set the `duration` prop (e.g., `24h` for 24 hours, or `3d 10m` for 3 days, 10 minutes)&#x20;
4. Slot in HTML elements with `countdown-remaining` attributes for your countdown presentation.  Build and customize this slotted component any way you like in the Webflow designer.&#x20;
5. The component automatically updates the text content, based on specific attributes;&#x20;

### Example slot content

Can be any HTML structure; the attributes determine where the component will update text during the countdown.&#x20;

```html
<!-- Example slot content -->
<div>
  <span countdown-remaining="days">00</span> days
  <span countdown-remaining="hours">00</span> hours
  <span countdown-remaining="minutes">00</span> minutes
  <span countdown-remaining="seconds">00</span> seconds
</div>
```

***

## Properties

#### duration

**Type:** Text

**Default:** `"24h"`

**Group:** Settings

The countdown duration as a human-readable string. Supports years, months, days, hours, minutes, and seconds.

**Format:** Whitespace-separated parts, each with a number and unit character.

| Unit    | Character | Notes                            |
| ------- | --------- | -------------------------------- |
| Years   | `y`       | Calculated as 365 days           |
| Months  | `M`       | Uppercase, calculated as 30 days |
| Days    | `d`       |                                  |
| Hours   | `h`       |                                  |
| Minutes | `m`       | Lowercase                        |
| Seconds | `s`       |                                  |

**Examples:**

* `24h` — 24 hours
* `2d 12h` — 2 days and 12 hours
* `1y 6M` — 1 year and 6 months
* `30m 30s` — 30 minutes and 30 seconds
* `1d 2h 30m 15s` — 1 day, 2 hours, 30 minutes, 15 seconds

**Notes:**

* Invalid parts are ignored
* Duplicate units use the first occurrence only (e.g., `1h 2h` = 1 hour)
* Anomalous values are calculated correctly (e.g., `36h` = 1.5 days)

***

#### content

**Type:** Slot

**Group:** Content

The slot for your countdown display HTML. Place elements with `countdown-remaining` attributes inside this slot.

***

#### storageKey

**Type:** Text

**Default:** `"countdown"`

**Group:** Settings

The LocalStorage key prefix for this timer instance. Use different keys to create multiple independent countdown timers on the same site.

**Example:** If you have two offers with different countdowns:

* Offer A: `storageKey="offer-a-countdown"`
* Offer B: `storageKey="offer-b-countdown"`

***

#### leadingZeros

**Type:** Boolean

**Default:** `true`

**Group:** Settings

When enabled, single-digit numbers are padded with a leading zero.

| Value | leadingZeros: true | leadingZeros: false |
| ----- | ------------------ | ------------------- |
| 5     | `"05"`             | `"5"`               |
| 12    | `"12"`             | `"12"`              |
| 0     | `"00"`             | `"0"`               |

***

#### debugMode

**Type:** Boolean

**Default:** `false`

**Group:** Advanced

When enabled, displays a debug panel showing:

* Storage key in use
* Parsed duration in milliseconds
* First visit timestamp
* End time timestamp
* Current remaining time
* Expired status

Useful for testing and troubleshooting. Disable in production.

***

### Slot Attributes

Add these attributes to elements in your slotted content. The component will find and update them automatically.

#### Time Values

| Attribute                       | Updates With                     |
| ------------------------------- | -------------------------------- |
| `countdown-remaining="days"`    | Days remaining (e.g., `"05"`)    |
| `countdown-remaining="hours"`   | Hours remaining (e.g., `"23"`)   |
| `countdown-remaining="minutes"` | Minutes remaining (e.g., `"59"`) |
| `countdown-remaining="seconds"` | Seconds remaining (e.g., `"45"`) |

#### Visibility Controls

| Attribute                       | Behavior                                            |
| ------------------------------- | --------------------------------------------------- |
| `countdown-remaining="active"`  | Visible while timer is running; hidden when expired |
| `countdown-remaining="expired"` | Hidden while timer is running; visible when expired |

**Example:**

```html
<div countdown-remaining="active">
  Offer ends in <span countdown-remaining="hours">00</span>:<span countdown-remaining="minutes">00</span>:<span countdown-remaining="seconds">00</span>
</div>
<div countdown-remaining="expired">
  This offer has expired.
</div>
```

***

### JavaScript Events

The component dispatches CustomEvents on the `window` object for animation integration and custom behaviors.

#### countdown:update

Fired every second with the complete timer state.

```jsx
window.addEventListener('countdown:update', (e) => {
  const { days, hours, minutes, seconds, expired, totalMs } = e.detail;
  
  console.log(`${days}d ${hours}h ${minutes}m ${seconds}s`);
  console.log(`Total milliseconds remaining: ${totalMs}`);
  console.log(`Expired: ${expired}`);
});
```

#### countdown:days

Fired when the days value changes.

```jsx
window.addEventListener('countdown:days', (e) => {
  const { value, previous } = e.detail;
  console.log(`Days changed from ${previous} to ${value}`);
});
```

#### countdown:hours

Fired when the hours value changes.

```jsx
window.addEventListener('countdown:hours', (e) => {
  const { value, previous } = e.detail;
  // Trigger animation when hours change
});
```

#### countdown:minutes

Fired when the minutes value changes.

```jsx
window.addEventListener('countdown:minutes', (e) => {
  const { value, previous } = e.detail;
  // Trigger animation when minutes change
});
```

#### countdown:seconds

Fired when the seconds value changes.

```jsx
window.addEventListener('countdown:seconds', (e) => {
  const { value, previous } = e.detail;
  // Trigger animation on every second tick
});
```

#### countdown:expired

Fired once when the timer expires.

```jsx
window.addEventListener('countdown:expired', (e) => {
  // Show modal, redirect, or trigger other expiration behavior
  console.log('Timer has expired!');
});
```

***

### Animation Example

Here's a complete example showing how to animate SVG progress rings using the countdown events:

```html
<style>
  .progress-ring {
    stroke-dasharray: 251;
    stroke-dashoffset: 251;
    transition: stroke-dashoffset 0.6s linear;
  }
</style>

<svg width="100" height="100">
  <circle cx="50" cy="50" r="40" class="progress-ring" id="seconds-ring" />
</svg>
<div countdown-remaining="seconds">00</div>

<script>
  const CIRCUMFERENCE = 251;
  
  window.addEventListener('countdown:update', (e) => {
    const { seconds } = e.detail;
    const ring = document.getElementById('seconds-ring');
    
    // Calculate progress (0 to 60 seconds)
    const progress = (60 - seconds) / 60;
    const offset = CIRCUMFERENCE - (progress * CIRCUMFERENCE);
    
    ring.style.strokeDashoffset = offset;
  });
</script>
```

***

### How It Works

#### First Visit Detection

When a visitor first loads a page with the Countdown Timer:

1. The component checks LocalStorage for existing timer state
2. If none exists, it records the current timestamp as `firstVisit`
3. It calculates `endTime` as `firstVisit + duration`
4. Both values are saved to LocalStorage

#### Subsequent Visits

On return visits:

1. The component loads the saved `firstVisit` and `endTime`
2. It compares the current time to `endTime`
3. The countdown displays the remaining time (or expired state)

#### Duration Changes

If you change the `duration` prop after a timer has started:

1. The component detects the duration change
2. It recalculates `endTime` from the original `firstVisit` + new duration
3. The new end time is saved to LocalStorage

This means changing from `24h` to `48h` extends the timer from the original first visit, not from now.

#### Expiration

When the countdown reaches zero:

* All time values display as `00`
* Elements with `countdown-remaining="active"` are hidden
* Elements with `countdown-remaining="expired"` are shown
* The `countdown:expired` event fires once
* The timer stops (no more updates)
* The expired state persists until LocalStorage is cleared

***

### LocalStorage

The component stores timer state in LocalStorage using the `storageKey` prop as a prefix.

**Key:** `{storageKey}_state`

**Value:**

```json
{
  "firstVisit": 1704067200000,
  "endTime": 1704153600000,
  "durationMs": 86400000
}
```

#### Clearing the Timer

To reset a timer (e.g., for testing), clear the LocalStorage entry:

```jsx
localStorage.removeItem('countdown_state');
```

Or for a custom storage key:

```jsx
localStorage.removeItem('my-offer_state');
```

***

## Browser Support

* Works in all modern browsers that support:
* LocalStorage
* CustomEvent
* Shadow DOM (handled by Webflow)
* **LocalStorage unavailable:** Timer still works but resets on page refresh&#x20;

***

### Troubleshooting

#### Timer shows 00:00:00 immediately

* Check if the timer has already expired (enable `debugMode`)
* Clear LocalStorage and refresh to reset

#### Slotted elements not updating

* Verify elements have the correct `countdown-remaining` attribute
* Check browser console for errors
* Ensure the content is slotted into the `content` slot

#### Timer resets on every page load

* LocalStorage may be blocked or unavailable
* Check browser privacy settings
* Verify the `storageKey` is consistent across pages

#### Animation not triggering

* Verify event listener is attached to `window`
* Check that event name matches exactly (e.g., `countdown:update`)
* Use `console.log` in the event handler to confirm events are firing

***

### Best Practices

1. **Use unique storage keys** for different countdown timers to avoid conflicts
2. **Test with debugMode** enabled during development, then disable for production
3. **Provide expired content** using `countdown-remaining="expired"` for good UX
4. **Keep duration realistic** — extremely long durations (years) may cause precision issues
5. **Consider time zones** — the timer uses the visitor's local time via `Date.now()`
