> 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 ✅

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="/files/jWOu1KlRGstQFPd0i21c" 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()`


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://sygnal.gitbook.io/sygnal-webflow-components/cc/sygnal-components/countdown-timer.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
