Timer

Flexible countdown component with visual and headless modes, event callbacks, and notification integration

Overview

Timer is a flexible countdown component that works in both visual mode (with DOM display) and headless mode (pure logic). Perfect for Pomodoro timers, cooking timers, workout intervals, and any countdown scenario.

Key Features

  • Visual mode with customisable display and controls
  • Headless mode for background timers without UI
  • Multiple time formats (HH:MM:SS, MM:SS, SS)
  • Start, pause, reset, and stop controls
  • Dynamic time manipulation (add/subtract time)
  • Desktop notification integration on completion
  • Optional sound playback when timer finishes
  • Event callbacks for all lifecycle events
  • High-precision timing with configurable update intervals

When to Use Timers

  • Productivity: Pomodoro technique, work/break intervals, time blocking
  • Cooking & Recipes: Step timing, cooking duration, multi-stage recipes
  • Fitness: Workout intervals, rest periods, HIIT training
  • Education: Quiz time limits, study sessions, presentation timing
  • Gaming: Turn timers, challenge countdowns, cooldown periods
  • Meetings: Speaking time, agenda items, total meeting duration

Basic Usage

// Visual timer with controls
const timer = Domma.elements.timer('#timer', {
    duration: 60000,  // 1 minute in milliseconds
    format: 'mm:ss',
    showControls: true,
    onComplete: () => {
        console.log('Timer finished!');
    }
});

// Headless timer (no UI)
const backgroundTimer = Domma.elements.timer(null, {
    duration: 300000,  // 5 minutes
    onTick: (remaining) => {
        console.log(`${remaining}ms remaining`);
    }
});

backgroundTimer.start();

Quick Start

Create your first timer in seconds with automatic UI and controls.

Simplest Visual Timer

// HTML: Just an empty div
<div id="my-timer"></div>

// JavaScript: Timer with controls
const timer = Domma.elements.timer('#my-timer', {
    duration: 30000,  // 30 seconds
    showControls: true,
    autoStart: true   // Starts immediately
});
Simplest Visual Timer
<div class="timer-demo-area" id="quick-timer"></div>
/**
 * Simplest Visual Timer - from public/showcase/elements/timer.
 */
export function mount(root) {
    const quickTimer = E.timer(root.querySelector('#quick-timer'), {
        duration: 30000,
        showControls: true,
        autoStart: false
    });
}

Simplest Headless Timer

// No HTML needed - pure JavaScript
const timer = Domma.elements.timer(null, {
    duration: 10000,  // 10 seconds
    onComplete: () => Domma.elements.alert('Done!')
});

timer.start();  // Go!
Simplest Headless Timer

<button type="button" class="btn btn-primary" id="quick-headless">Start 10s Headless Timer</button>
<p id="headless-status" class="mt-2"></p>
/**
 * Simplest Headless Timer - from public/showcase/elements/timer.
 */
export function mount(root) {
        // Quick Start: Headless Timer
    $(root).find('#quick-headless').on('click', () => {
            const status = $(root).find('#headless-status')[0];
            status.textContent = 'Timer started (10 seconds)...';

            const headless = E.timer(null, {
                duration: 10000,
                onTick: (remaining) => {
                    status.textContent = `${Math.ceil(remaining / 1000)}s remaining`;
                },
                onComplete: () => {
                    status.textContent = '✓ Headless timer complete!';
                    status.className = 'mt-2 text-success';
                }
            });

            headless.start();
        });
}

Step-by-Step Tutorial

Build a Pomodoro timer with work/break intervals and notifications.

Step 1: Create Work Timer

const workTimer = Domma.elements.timer('#work-timer', {
    duration: 1500000,  // 25 minutes (Pomodoro work period)
    format: 'mm:ss',
    showControls: true,
    notification: true,  // Desktop notification on complete
    notificationOptions: {
        title: 'Work Session Complete!',
        body: 'Time for a 5-minute break',
        requireInteraction: true
    },
    onComplete: () => {
        console.log('Work session finished');
        // Start break timer automatically
        breakTimer.start();
    }
});

Step 2: Create Break Timer

const breakTimer = Domma.elements.timer('#break-timer', {
    duration: 300000,  // 5 minutes
    format: 'mm:ss',
    showControls: true,
    notification: true,
    notificationOptions: {
        title: 'Break Complete!',
        body: 'Ready to start another work session?'
    },
    onComplete: () => {
        console.log('Break finished');
        // Optionally auto-start work timer again
    }
});

Step 3: Add Controls

// Manual control buttons
$('#start-work').on('click', () => {
    workTimer.start();
});

// Add time during work session
$('#add-5min').on('click', () => {
    workTimer.add(300000);  // Add 5 minutes
});

// Pause for interruption
$('#pause').on('click', () => {
    workTimer.pause();
});
Step 3: Add Controls
Work Timer (25 min)
Break Timer (5 min)
<div class="grid grid-cols-2 gap-4">
    <div>
        <h5>Work Timer (25 min)</h5>
        <div id="tutorial-work-timer" class="timer-demo-area"></div>
    </div>
    <div>
        <h5>Break Timer (5 min)</h5>
        <div id="tutorial-break-timer" class="timer-demo-area"></div>
    </div>
</div>
/**
 * Step 3: Add Controls - from public/showcase/elements/timer.
 */
export function mount(root) {
        // Tutorial: Pomodoro Timers
    const tutorialWorkTimer = E.timer(root.querySelector('#tutorial-work-timer'), {
            duration: 1500000,
            format: 'mm:ss',
            showControls: true
        });
    const tutorialBreakTimer = E.timer(root.querySelector('#tutorial-break-timer'), {
            duration: 300000,
            format: 'mm:ss',
            showControls: true
        });
}

Real-World Examples

1. Cooking Timer

const cookingTimer = Domma.elements.timer('#cooking-timer', {
    duration: 600000,  // 10 minutes
    format: 'mm:ss',
    showControls: true,
    sound: true,
    soundUrl: '/sounds/kitchen-bell.mp3',
    notification: true,
    notificationOptions: {
        title: 'Cooking Timer',
        body: 'Your food is ready!',
        requireInteraction: true
    }
});
1. Cooking Timer
<div id="example-cooking" class="timer-demo-area"></div>
/**
 * 1. Cooking Timer - from public/showcase/elements/timer.
 */
export function mount(root) {
        // Example 1: Cooking Timer
    const cookingTimer = E.timer(root.querySelector('#example-cooking'), {
            duration: 600000,
            format: 'mm:ss',
            showControls: true
        });
}

2. HIIT Workout Timer

let currentRound = 1;
const totalRounds = 8;

const workoutTimer = Domma.elements.timer('#workout', {
    duration: 30000,  // 30 seconds work
    format: 'ss',
    onComplete: () => {
        if (currentRound < totalRounds) {
            // Start rest period
            restTimer.start();
        } else {
            Domma.elements.alert('Workout complete!', { title: 'HIIT Complete' });
        }
    }
});

const restTimer = Domma.elements.timer(null, {
    duration: 10000,  // 10 seconds rest
    onComplete: () => {
        currentRound++;
        workoutTimer.reset();
        workoutTimer.start();
    }
});
2. HIIT Workout Timer

Round: 1/8

<div id="example-workout" class="timer-demo-area"></div>
<p id="workout-status" class="mt-2">Round: 1/8</p>
/**
 * 2. HIIT Workout Timer - from public/showcase/elements/timer.
 */
export function mount(root) {
        // Example 2: HIIT Workout
    let workoutRound = 1;
    const workoutTimer = E.timer(root.querySelector('#example-workout'), {
            duration: 30000,
            format: 'ss',
            showControls: true,
            onComplete: () => {
                if (workoutRound < 3) {
                    workoutRound++;
                    $(root).find('#workout-status')[0].textContent = `Round: ${workoutRound}/3`;
                    workoutTimer.reset();
                    setTimeout(() => workoutTimer.start(), 1000);
                } else {
                    $(root).find('#workout-status')[0].textContent = 'Workout complete!';
                }
            }
        });
}

3. Presentation Timer

const presentationTimer = Domma.elements.timer('#presentation', {
    duration: 600000,  // 10 minutes
    format: 'mm:ss',
    showControls: true,
    onTick: (remaining) => {
        // Visual warnings at certain thresholds
        if (remaining === 120000) {  // 2 minutes
            Domma.elements.alert('2 minutes remaining!', { title: 'Time Warning' });
        } else if (remaining === 60000) {  // 1 minute
            Domma.elements.alert('1 minute remaining!', { title: 'Time Warning' });
        }
    }
});
3. Presentation Timer
<div id="example-presentation" class="timer-demo-area"></div>
/**
 * 3. Presentation Timer - from public/showcase/elements/timer.
 */
export function mount(root) {
        // Example 3: Presentation Timer
    const presentationTimer = E.timer(root.querySelector('#example-presentation'), {
            duration: 600000,
            format: 'mm:ss',
            showControls: true
        });
}

4. Multiple Timers

// Manage multiple cooking tasks
const timers = {
    pasta: Domma.elements.timer('#pasta-timer', {
        duration: 480000,  // 8 minutes
        format: 'mm:ss',
        showControls: true
    }),
    sauce: Domma.elements.timer('#sauce-timer', {
        duration: 900000,  // 15 minutes
        format: 'mm:ss',
        showControls: true
    }),
    garlic: Domma.elements.timer('#garlic-timer', {
        duration: 120000,  // 2 minutes
        format: 'mm:ss',
        showControls: true
    })
};

// Start all at once
Object.values(timers).forEach(timer => timer.start());
4. Multiple Timers
Pasta (8 min)
Sauce (15 min)
Garlic (2 min)
<div class="grid grid-cols-3 gap-4">
    <div>
        <h5>Pasta (8 min)</h5>
        <div id="multi-pasta" class="timer-demo-area"></div>
    </div>
    <div>
        <h5>Sauce (15 min)</h5>
        <div id="multi-sauce" class="timer-demo-area"></div>
    </div>
    <div>
        <h5>Garlic (2 min)</h5>
        <div id="multi-garlic" class="timer-demo-area"></div>
    </div>
</div>
<button type="button" class="btn btn-primary mt-4" id="start-all">Start All Timers</button>
/**
 * 4. Multiple Timers - from public/showcase/elements/timer.
 */
export function mount(root) {
        // Example 4: Multiple Timers
    const multiTimers = {
            pasta: E.timer(root.querySelector('#multi-pasta'), {
                duration: 480000,
                format: 'mm:ss',
                showControls: true
            }),
            sauce: E.timer(root.querySelector('#multi-sauce'), {
                duration: 900000,
                format: 'mm:ss',
                showControls: true
            }),
            garlic: E.timer(root.querySelector('#multi-garlic'), {
                duration: 120000,
                format: 'mm:ss',
                showControls: true
            })
        };
    $(root).find('#start-all').on('click', () => {
            Object.values(multiTimers).forEach(timer => {
                timer.reset();
                timer.start();
            });
        });
}

Best Practices

✅ Do's

  • Use headless mode for background timers - No DOM overhead when UI not needed
  • Set appropriate update intervals - 100ms for smooth UI, 1000ms for efficiency
  • Add notifications for long timers - Users may switch tabs or minimize window
  • Provide controls for user timers - showControls: true for user-facing timers
  • Choose correct time format - HH:MM:SS for hours, MM:SS for minutes, SS for seconds
  • Clean up with destroy() - Stop timers and remove listeners when done
  • Use onTick for progress bars - Update UI elements based on remaining time

❌ Don'ts

  • Don't use very short update intervals - 10ms updates waste CPU, 100ms is sufficient
  • Don't forget to stop timers - Running timers continue even if element removed from DOM
  • Don't rely solely on client-side timing - For critical timings, validate server-side
  • Don't create excessive timer instances - Reuse timers with reset() and setDuration()
  • Don't use timers for real-time clocks - Use Date objects for wall-clock time

Performance Pattern

// Good: Reuse timer for repeated countdowns
const reusableTimer = Domma.elements.timer('#timer', {
    duration: 60000,
    showControls: true,
    onComplete: () => {
        console.log('Cycle complete');
        // Reset and restart for next cycle
        setTimeout(() => {
            reusableTimer.reset();
            reusableTimer.start();
        }, 1000);
    }
});

// Bad: Creating new timer each time (memory leak)
function startNewTimer() {
    const timer = Domma.elements.timer('#timer', {
        duration: 60000,
        onComplete: () => startNewTimer()  // Creates infinite timers!
    });
    timer.start();
}

Cleanup Pattern

// Store reference for cleanup
let currentTimer = null;

function setupTimer() {
    // Clean up old timer
    if (currentTimer) {
        currentTimer.destroy();
    }

    // Create new timer
    currentTimer = Domma.elements.timer('#timer', {
        duration: 60000,
        showControls: true
    });
}

// Clean up when page unloads
window.addEventListener('beforeunload', () => {
    if (currentTimer) {
        currentTimer.destroy();
    }
});

API Reference

Factory Method

// Visual mode (with DOM element)
elements.timer(selector, options)

// Headless mode (no DOM element)
elements.timer(null, options)

Options

Option Type Default Description
duration number 60000 Timer duration in milliseconds
autoStart boolean false Start timer automatically on creation
format string 'mm:ss' Display format: 'hh:mm:ss', 'mm:ss', 'ss'
showControls boolean false Show start/pause/reset buttons
updateInterval number 100 Update frequency in milliseconds
notification boolean false Show desktop notification on complete
notificationOptions object {} Options passed to notification
sound boolean false Play sound on completion
soundUrl string null URL to sound file
onTick function null Callback on each update (remaining)
onComplete function null Callback when timer reaches 0
onStart function null Callback when timer starts
onPause function null Callback when timer pauses
onReset function null Callback when timer resets

Instance Methods

// Control methods
timer.start()          // Start or resume timer
timer.pause()          // Pause timer
timer.reset()          // Reset to initial duration
timer.stop()           // Stop timer completely

// Time manipulation
timer.add(ms)          // Add milliseconds
timer.subtract(ms)     // Subtract milliseconds
timer.setDuration(ms)  // Change total duration

// State methods
timer.isRunning()      // Returns boolean
timer.getRemaining()   // Returns milliseconds remaining
timer.getElapsed()     // Returns milliseconds elapsed

// Cleanup
timer.destroy()        // Stop timer and clean up

Declarative Configuration

Set up timers declaratively using the config engine.

Basic Timer Setup

$.setup({
    '#my-timer': {
        component: 'timer',
        options: {
            duration: 300000,  // 5 minutes
            format: 'mm:ss',
            showControls: true,
            notification: true
        }
    }
});
Basic Timer Setup
<div id="config-timer" class="timer-demo-area"></div>
/**
 * Basic Timer Setup - from public/showcase/elements/timer.
 */
export function mount(root) {
        // Config Engine Setup
    $.setup({
            '#config-timer': {
                component: 'timer',
                options: {
                    duration: 300000,
                    format: 'mm:ss',
                    showControls: true
                }
            }
        });
}

Multiple Timers

$.setup({
    '#quick-timer': {
        component: 'timer',
        options: {
            duration: 60000,
            format: 'ss',
            showControls: true
        }
    },
    '#long-timer': {
        component: 'timer',
        options: {
            duration: 3600000,  // 1 hour
            format: 'hh:mm:ss',
            showControls: true
        }
    }
});

CSS Customisation

Override these CSS variables to customise Timer appearance and match your design system.

Variable Default Controls
--dm-success var(--dm-green-600) Timer colour (plenty of time)
--dm-warning var(--dm-amber-500) Timer colour (low time)
--dm-danger var(--dm-red-600) Timer colour (critical)
--dm-font-size-3xl 1.875rem Timer display size

Example Override

/* Time colour thresholds set via JavaScript options */

Full CSS Customisation Cheat-Sheet →

Related Components

Timer integrates seamlessly with other Domma components for complete timing solutions.

Related Components
<div class="grid grid-cols-1 grid-cols-md-2 gap-4"><div class="related-card-style">
        <h4>DesktopNotification</h4>
        <p>Desktop alerts when timers complete</p>
        <a href="/showcase/elements/notification" class="btn btn-sm btn-outline">View Notification →</a>
    </div>

    <div class="related-card-style">
        <h4>Alarm</h4>
        <p>Schedule alarms for specific times of day</p>
        <a href="/showcase/elements/alarm" class="btn btn-sm btn-outline">View Alarm →</a>
    </div>

    <div class="related-card-style">
        <h4>Progression</h4>
        <p>Visual progress indication for timers</p>
        <a href="/showcase/elements/progression" class="btn btn-sm btn-outline">View Progression →</a>
    </div>

    <div class="related-card-style">
        <h4>Modal</h4>
        <p>Full-screen timer overlays</p>
        <a href="/showcase/elements/modal" class="btn btn-sm btn-outline">View Modal →</a>
    </div>
</div>