Skip to main content

Component TimeTimer

· 12 min read ·
Docux
Curious explorer, a bit of a mad experimenter, and a bit of a contributor.

Developer Development License: MIT AI 50%

The Time Timer is an innovative visual tool designed to "materialize" the passage of time, allowing for better activity management by making the remaining time visible. It comes in the form of a timer with an analog dial featuring a colored disk, often red on a white background, which gradually shrinks as the countdown progresses until the final alarm or color change. This device, invented by Jan Rogers to help her daughter better understand transitions between activities, is particularly useful for children who struggle with the concept of elapsed time, but also for adults. It's used to visualize the remaining time for a task, whether for homework, classes, playtime, or other daily activities, thus promoting autonomy, concentration, and time management.

While observing my daughter's Time Timer, I had the idea to develop the TimeTimer component to solve a simple need: displaying content only at certain dates or during specific periods. Whether for promotional offers, seasonal events, or temporary announcements, this component allows you to automate the display without having to manually modify your pages.

Bonus: at the end of this article, you'll discover how I created an Advent calendar with my TimeTimer component!

Features I Integrated

  • Fixed date display: Content visible on a specific date (with or without time)
  • Period display: Time ranges with start date and duration
  • Custom dates for fallbacks: Override start/end dates in messages
  • UTC support: strict option to avoid timezone issues
  • Flexible fallbacks: Customizable messages before and after the active period

Why Conditional Rendering Over CSS?

Unlike solutions that use display: none or visibility: hidden, TimeTimer uses React's conditional rendering:

// ❌ CSS approach - All content loaded, just hidden
<div style={{ display: isActive ? 'block' : 'none' }}>
Heavy promotional content with images...
</div>

// ✅ TimeTimer - Only active content exists in DOM
{isActive ? <PromoContent /> : null}

What happens in the DOM:

When you use CSS to hide content, the browser still:

  1. Downloads all the HTML (even hidden parts)
  2. Parses and creates DOM nodes for hidden elements
  3. Loads images and resources referenced in hidden content
  4. Keeps all this in memory
  5. Just visually hides it with CSS

With TimeTimer's conditional rendering:

  1. Only the active period's content is sent to the browser
  2. The DOM contains only what should be visible right now
  3. When the period changes, React replaces the old content with the new one
  4. Inactive content literally doesn't exist in the page source

Example: Inspect the page source

  • With CSS: You'll see all periods (past, present, future) in the HTML
  • With TimeTimer: You'll only see the current period's content

The benefits:

  • 🚀 Performance: Inactive content isn't downloaded or rendered at all
  • 🔍 SEO: Search engines only see current, relevant content
  • Accessibility: Screen readers skip inactive periods entirely
  • 💾 Lighter page: Reduced HTML size and memory usage

Installation

File Structure

📁src

Create the Component

import React, { useMemo } from 'react';
import PropTypes from 'prop-types';

// --- Sub-components (defined first)
export const FallbackBefore = ({ children, start }) => <>{children}</>;
export const FallbackAfter = ({ children, end }) => <>{children}</>;

FallbackBefore.propTypes = {
children: PropTypes.node.isRequired,
start: PropTypes.string,
};

FallbackAfter.propTypes = {
children: PropTypes.node.isRequired,
end: PropTypes.string,
};

/**
* Calculates the temporal state based on dates and fallbacks
*/
const calculateTimeState = (now, calculatedStartDate, calculatedEndDate, fallbackBeforeStartDate, fallbackAfterEndDate) => {
if (!calculatedStartDate || !calculatedEndDate) {
return 'none';
}

// During the main period
if (now >= calculatedStartDate && now <= calculatedEndDate) {
return 'during';
}

// Before the main period
if (now < calculatedStartDate) {
if (fallbackBeforeStartDate) {
return now >= fallbackBeforeStartDate ? 'before' : 'none';
}
return 'before';
}

// After the main period
if (now > calculatedEndDate) {
if (fallbackAfterEndDate) {
return now <= fallbackAfterEndDate ? 'after' : 'none';
}
return 'after';
}

return 'none';
};

/**
* Conditional display component based on date or duration.
*
* Usage:
* <TimeTimer date="25/12/2025" strict>
* 🎄 Merry Christmas!
* <FallbackBefore>⏳ Not Christmas yet</FallbackBefore>
* <FallbackAfter>🕰️ Christmas has passed</FallbackAfter>
* </TimeTimer>
*
* <TimeTimer start="01/11/2025 10:10" duration="30d" strict>
* 🎉 Active offer!
* </TimeTimer>
*
* <TimeTimer start="01/12/2025 10:00" duration="30d">
* 🎁 Active offer!
* <FallbackBefore start="15/11/2025 10:00">
* ⏳ The offer starts soon
* </FallbackBefore>
* <FallbackAfter end="20/12/2025 18:00">
* 🕰️ Offer ended
* </FallbackAfter>
* </TimeTimer>
*/
const TimeTimer = ({ date, start, duration, strict = false, children }) => {
const now = useMemo(() => new Date(), []);

// --- Helper to parse dates
const parseDate = (dateStr) => {
if (!dateStr) return null;

const parts = dateStr.split(' ');
const datePart = parts[0];
const timePart = parts[1];

const [day, month, year] = datePart.split('/');
const [h = '0', min = '0'] = timePart ? timePart.split(':') : ['0', '0'];

const targetYear = year ? Number(year) : (strict ? now.getUTCFullYear() : now.getFullYear());

return strict
? new Date(Date.UTC(targetYear, Number(month) - 1, Number(day), Number(h), Number(min), 0, 0))
: new Date(targetYear, Number(month) - 1, Number(day), Number(h), Number(min), 0, 0);
};

// --- Date calculation (with memoization)
const { calculatedStartDate, calculatedEndDate } = useMemo(() => {
let startDate = null;
let endDate = null;

// Case 1: Fixed date
if (date && !start) {
const target = parseDate(date);
const endOfDay = new Date(target);
if (strict) {
endOfDay.setUTCDate(endOfDay.getUTCDate() + 1);
} else {
endOfDay.setDate(endOfDay.getDate() + 1);
}
startDate = target;
endDate = endOfDay;
}
// Case 2: Chronometer
else if (start && duration) {
const startDateTime = parseDate(start);
const match = duration.match(/(\d+)([dhm])/);

if (!match) {
console.warn(`TimeTimer: invalid duration format "${duration}". Use "30d", "2h", or "45m".`);
return { calculatedStartDate: null, calculatedEndDate: null };
}

const value = Number(match[1]);
const unit = match[2];
const endDateTime = new Date(startDateTime);

if (strict) {
if (unit === 'd') endDateTime.setUTCDate(endDateTime.getUTCDate() + value);
else if (unit === 'h') endDateTime.setUTCHours(endDateTime.getUTCHours() + value);
else if (unit === 'm') endDateTime.setUTCMinutes(endDateTime.getUTCMinutes() + value);
} else {
if (unit === 'd') endDateTime.setDate(endDateTime.getDate() + value);
else if (unit === 'h') endDateTime.setHours(endDateTime.getHours() + value);
else if (unit === 'm') endDateTime.setMinutes(endDateTime.getMinutes() + value);
}

startDate = startDateTime;
endDate = endDateTime;
}

return { calculatedStartDate: startDate, calculatedEndDate: endDate };
}, [date, start, duration, strict]);

// --- Extract fallback children
const childrenArray = React.Children.toArray(children);

const fallbackBefore = childrenArray.find(
(child) => React.isValidElement(child) && child.type === FallbackBefore
);
const fallbackAfter = childrenArray.find(
(child) => React.isValidElement(child) && child.type === FallbackAfter
);

const mainChildren = childrenArray.filter(
(child) =>
!React.isValidElement(child) ||
(child.type !== FallbackBefore && child.type !== FallbackAfter)
);

// --- Handle custom dates in fallbacks
const fallbackBeforeStartDate = fallbackBefore?.props?.start ? parseDate(fallbackBefore.props.start) : null;
const fallbackAfterEndDate = fallbackAfter?.props?.end ? parseDate(fallbackAfter.props.end) : null;

// --- Calculate state
const state = calculateTimeState(
now,
calculatedStartDate,
calculatedEndDate,
fallbackBeforeStartDate,
fallbackAfterEndDate
);

// --- Render based on state
if (state === 'before' && fallbackBefore) {
return fallbackBefore;
}
if (state === 'after' && fallbackAfter) {
return fallbackAfter;
}
if (state === 'during') {
return <>{mainChildren}</>;
}

return null;
};

TimeTimer.propTypes = {
date: PropTypes.string,
start: PropTypes.string,
duration: PropTypes.string,
strict: PropTypes.bool,
children: PropTypes.node.isRequired,
};

export default TimeTimer;

Register the Component in MDX

Add the component to src/theme/MDXComponents.js:

import TimeTimer, { FallbackBefore, FallbackAfter } from '@site/src/components/TimeTimer';

export default {
...MDXComponents,
TimeTimer,
FallbackBefore,
FallbackAfter
};

How to Use It

Simple Fixed Date

Here's how I display content only on Christmas day:

<TimeTimer date="25/12/2025">
🎄 Merry Christmas!
<FallbackBefore>⏳ Not Christmas yet...</FallbackBefore>
<FallbackAfter>🎁 Christmas has passed!</FallbackAfter>
</TimeTimer>

Behavior:

  • Before 25/12/2025 at 00:00 → "⏳ Not Christmas yet..."
  • All day on 25/12/2025 → "🎄 Merry Christmas!"
  • From 26/12/2025 at 00:00 → "🎁 Christmas has passed!"

Date with Specific Time

To trigger display at a specific time, I use this format:

<TimeTimer date="31/12/2025 23:59">
🎉 Happy New Year! The countdown has begun!
<FallbackBefore>⏰ Come back at 11:59 PM for the countdown</FallbackBefore>
</TimeTimer>

Behavior:

  • Before 31/12/2025 at 23:59 → "⏰ Come back at 11:59 PM for the countdown"
  • From 31/12/2025 23:59 to 01/01/2026 23:59 → "🎉 Happy New Year!"
  • From 01/01/2026 at 23:59 → Nothing (no FallbackAfter)

Period with Duration

For an offer lasting 30 days, I combine start and duration:

<TimeTimer start="01/11/2025 10:00" duration="30d">
🎁 Black Friday Sale: -50% on everything!
<FallbackBefore>⏳ The offer starts on November 1st at 10 AM</FallbackBefore>
<FallbackAfter>❌ Offer ended, see you next year!</FallbackAfter>
</TimeTimer>

Behavior:

  • Before 01/11/2025 at 10:00 → FallbackBefore message
  • From 01/11 10:00 to 01/12 10:00 (30 days) → Main content
  • From 01/12/2025 at 10:00 → FallbackAfter message

Advanced Features I Added

Custom Dates in Fallbacks

A feature I love: you can customize the start/end dates of fallback messages independently from the main period:

<TimeTimer start="01/12/2025 10:00" duration="30d">
🎉 Christmas Sale Active!

<FallbackBefore start="15/11/2025 10:00">
📅 Get ready! The offer starts soon
</FallbackBefore>

<FallbackAfter end="10/01/2026 23:59">
🎄 Offer ended, thank you!
</FallbackAfter>
</TimeTimer>

Behavior:

  • Before 15/11 at 10:00 → Nothing
  • From 15/11 to 01/12 → "📅 Get ready!"
  • From 01/12 to 31/12 (30 days) → "🎉 Christmas Sale Active!"
  • From 31/12 to 10/01 23:59 → "🎄 Offer ended!"
  • After 10/01 23:59 → Nothing
Important Point

The end prop in FallbackAfter does NOT modify your offer's duration! It only defines until when you want to display the "Offer ended" message. Your duration="30d" remains intact.

Strict UTC Mode

I added a strict mode for worldwide events. It avoids timezone issues:

<TimeTimer date="01/01/2026 00:00" strict>
🌍 Happy New Year worldwide! (UTC Time)
<FallbackBefore>🕐 Just a few more hours...</FallbackBefore>
</TimeTimer>

What it changes concretely:

  • Without strict: each visitor sees content according to their local timezone
  • With strict: everyone sees the change at exactly the same moment (UTC time)

Date without Year (Recurring)

A handy trick: you can omit the year to create recurring content that displays every year:

<TimeTimer date="25/12">
🎅 It's Christmas today!
<FallbackBefore>⏳ Patience, Christmas is coming...</FallbackBefore>
<FallbackAfter>🎁 Christmas has passed this year</FallbackAfter>
</TimeTimer>

Props Reference I Implemented

TimeTimer

PropTypeDefaultDescription
datestring-Fixed date: DD/MM/YYYY or DD/MM/YYYY HH:MM or DD/MM
startstring-Start date: DD/MM/YYYY HH:MM (requires duration)
durationstring-Duration: 30d, 2h, 45m (requires start)
strictbooleanfalseUses UTC instead of local timezone
childrennode-Main content + FallbackBefore/After

FallbackBefore

PropTypeDefaultDescription
startstring-Custom start date for the fallback
childrennode-Content to display before the period

FallbackAfter

PropTypeDefaultDescription
endstring-Date until when to display the fallback (doesn't affect duration)
childrennode-Content to display after the period

Accepted Formats

Dates

FormatExampleBehavior
DD/MM/YYYY25/12/2025All day long (00:00 to 23:59)
DD/MM/YYYY HH:MM25/12/2025 18:00From 6 PM, for 24 hours
DD/MM25/12Current year automatic

Durations

UnitExampleDescription
d30d30 days
h2h2 hours
m45m45 minutes

Other Examples

tip

You'll note that I used the jsx live format for the interactive examples below. This allows you to test the component in real-time directly on the page!

Play with the dates to experiment with the component's behavior (insert a space to refresh the Live editor).

To learn how to set up live mode in your articles, check out my tutorial on integrating interactive components with Docusaurus.

Today's Content

Live Editor
<TimeTimer date="01/11/2025">
  ✅ You see this message because it's November 1st, 2025
  
  <FallbackBefore>
    ⏳ This message will only display on November 1st
  </FallbackBefore>
  
  <FallbackAfter>
    🕰️ November 1st has passed
  </FallbackAfter>
</TimeTimer>
Result
Loading...

One-Week Period

Live Editor
<TimeTimer start="01/11/2025 00:00" duration="7d">
  📅 Week of November 1st to 8th active!
  
  <FallbackBefore>
    ⏳ The week hasn't started yet
  </FallbackBefore>
  
  <FallbackAfter>
    ✔️ The week of November 1st is over
  </FallbackAfter>
</TimeTimer>
Result
Loading...

With Custom Dates

Live Editor
<TimeTimer start="01/11/2025 00:00" duration="30d">
  🎯 **Main content active**
  
  <FallbackBefore start="25/10/2025">
    📢 Preparation in progress... Starts November 1st
  </FallbackBefore>
  
  <FallbackAfter end="15/11/2025">
    ✅ Finished earlier than expected!
  </FallbackAfter>
</TimeTimer>
Result
Loading...
Live Editor

    <TimeTimer date="01/11/2025">
      <div style={{padding: '20px', background: '#4CAF50', color: 'white', borderRadius: '8px'}}>
        ✅ Content displayed on November 1st, 2025
      </div>
      
      <FallbackBefore>
        <div style={{padding: '20px', background: '#FF9800', color: 'white', borderRadius: '8px'}}>
          ⏳ Not yet November 1st
        </div>
      </FallbackBefore>
      
      <FallbackAfter>
        <div style={{padding: '20px', background: '#2196F3', color: 'white', borderRadius: '8px'}}>
          🕰️ November 1st has passed
        </div>
      </FallbackAfter>
    </TimeTimer>

Result
Loading...

Customizable Duration

Live Editor

    <TimeTimer start="01/11/2025 10:00" duration="2h">
      <div style={{padding: '20px', background: '#E91E63', color: 'white', borderRadius: '8px'}}>
        ⚡ Flash sale active for 2 hours!
      </div>
      
      <FallbackBefore>
        <div style={{padding: '15px', background: '#9C27B0', color: 'white', borderRadius: '8px'}}>
          🔔 The offer starts at 10am
        </div>
      </FallbackBefore>
      
      <FallbackAfter>
        <div style={{padding: '15px', background: '#607D8B', color: 'white', borderRadius: '8px'}}>
          😢 Offer ended
        </div>
      </FallbackAfter>
    </TimeTimer>

Result
Loading...

Custom Dates in Fallbacks

Live Editor

     <TimeTimer start="01/12/2025 10:00" duration="30d">
      <div style={{padding: '20px', background: '#00BCD4', color: 'white', borderRadius: '8px', fontSize: '18px'}}>
        🎉 Christmas offer active!
      </div>
      
      <FallbackBefore start="01/11/2025 10:00">
        <div style={{padding: '15px', background: '#FFC107', color: '#333', borderRadius: '8px'}}>
          📅 Get ready! The offer starts soon
        </div>
      </FallbackBefore>
      
      <FallbackAfter end="10/01/2026 23:59">
        <div style={{padding: '15px', background: '#8BC34A', color: 'white', borderRadius: '8px'}}>
          🎄 Offer ended, thank you!
        </div>
      </FallbackAfter>
    </TimeTimer>

Result
Loading...
tip

Feel free to modify the dates and durations in the code above to experiment! A few ideas:

  • Change date="01/11/2025" to today's date to see the active content
  • Test duration="5m" instead of 2h for a shorter duration
  • Customize the CSS styles to your liking

Advanced Use Cases

Maintenance Banner

Live Editor
<TimeTimer start="15/11/2025 02:00" duration="2h">
  <FallbackBefore start="14/11/2025 20:00">
    ⚠️ Scheduled maintenance tomorrow at 2am
  </FallbackBefore>
  
  🔧 Site under maintenance, back at 4am
  
  <FallbackAfter>
    ✅ Maintenance completed, everything works!
  </FallbackAfter>
</TimeTimer>
Result
Loading...

Countdown to an Event

Live Editor
<TimeTimer date="25/12/2025 00:00">
  🎄 It's Christmas! Happy Holidays to all!
  
  <FallbackBefore start="01/12/2025">
    🎅 Just a few more days until Christmas!
  </FallbackBefore>
</TimeTimer>
Result
Loading...

2-Hour Flash Sale

Live Editor
<TimeTimer start="01/11/2025 14:00" duration="2h">
  ⚡ FLASH SALE: -70% for 2 hours!
  
  <FallbackBefore>
    🔔 Next flash sale at 2pm
  </FallbackBefore>
  
  <FallbackAfter>
    😢 Flash sale ended, see you tomorrow!
  </FallbackAfter>
</TimeTimer>
Result
Loading...

Temporary Discount Code (15 minutes)

Live Editor
<TimeTimer start="01/11/2025 22:20" duration="10m">
  🎟️ PROMO CODE: **FLASH15** valid for 15 minutes!
  
  <FallbackBefore>
    ⏰ Promo code coming in a few moments...
  </FallbackBefore>
  
  <FallbackAfter end="01/11/2025 22:35">
    ⏱️ Code expired! Next code at 4pm
  </FallbackAfter>
</TimeTimer>
Result
Loading...

Temporary Message After a Sale

Live Editor
<TimeTimer start="01/11/2025 00:00" duration="30d">
  🎉 Black Friday: -50% on everything!
  
  <FallbackAfter end="07/12/2025 23:59">
    👋 Thank you for participating! See you next year
  </FallbackAfter>
</TimeTimer>
Result
Loading...

Interactive Advent Calendar

Here's a complete example combining TimeTimer with Columns, Card, and LogoIcon to create an Advent calendar:

Live Editor
<Columns className="margin-vert--md">
  <Column className="margin-vert--md">
    <TimeTimer date="01/11/2025">
      <Card shadow>
        <CardBody>
          <center>
            <LogoIcon name="noto:wrapped-gift" size={48} />
            <h3>Day 1</h3>
            <p>🎁 Gift discovered!</p>
          </center>
        </CardBody>
      </Card>
      <FallbackBefore>
        <Card>
          <CardBody>
            <center>
              <LogoIcon name="noto:locked" size={48} />
              <h3>Day 1</h3>
              <p>Please wait...</p>
            </center>
          </CardBody>
        </Card>
      </FallbackBefore>
      <FallbackAfter>
        <Card>
          <CardBody>
            <center>
              <LogoIcon name="noto:check-mark-button" size={48} />
              <h3>Day 1</h3>
              <p>✅ Already opened</p>
            </center>
          </CardBody>
        </Card>
      </FallbackAfter>
    </TimeTimer>
  </Column>
  
  <Column className="margin-vert--md">
    <TimeTimer date="06/11/2025">
      <Card shadow>
        <CardBody>
          <center>
            <LogoIcon name="noto:candy" size={48} />
            <h3>Day 2</h3>
            <p>🍬 Surprise candy!</p>
          </center>
        </CardBody>
      </Card>
      <FallbackBefore>
        <Card>
          <CardBody>
            <center>
              <LogoIcon name="noto:locked" size={48} />
              <h3>Day 2</h3>
              <p>Please wait...</p>
            </center>
          </CardBody>
        </Card>
      </FallbackBefore>
      <FallbackAfter>
        <Card>
          <CardBody>
            <center>
              <LogoIcon name="noto:check-mark-button" size={48} />
              <h3>Day 2</h3>
              <p>✅ Already opened</p>
            </center>
          </CardBody>
        </Card>
      </FallbackAfter>
    </TimeTimer>
  </Column>
  
  <Column className="margin-vert--md">
    <TimeTimer date="03/12/2025">
      <Card shadow>
        <CardBody>
          <center>
            <LogoIcon name="noto:cookie" size={48} />
            <h3>Day 3</h3>
            <p>🍪 Special cookie!</p>
          </center>
        </CardBody>
      </Card>
      <FallbackBefore>
        <Card>
          <CardBody>
            <center>
              <LogoIcon name="noto:locked" size={48} />
              <h3>Day 3</h3>
              <p>Please wait...</p>
            </center>
          </CardBody>
        </Card>
      </FallbackBefore>
      <FallbackAfter>
        <Card>
          <CardBody>
            <center>
              <LogoIcon name="noto:check-mark-button" size={48} />
              <h3>Day 3</h3>
              <p>✅ Already opened</p>
            </center>
          </CardBody>
        </Card>
      </FallbackAfter>
    </TimeTimer>
  </Column>
</Columns>




Result
Loading...
Dynamic Three-State Calendar

This calendar displays three different states for each box:

  • 🔒 Before the date: Locked box with a padlock
  • 🎁 On the day: Opened box with the day's gift
  • After the date: Box marked as "Already opened"

You can easily extend it to December 24 by duplicating the structure!

My Usage Recommendations

What I Recommend

  • Use strict for global events
  • Test with different dates before publishing
  • Take advantage of custom dates in fallbacks—it's super flexible

Resources

Related posts

Back to top