Component TimeTimer

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:
strictoption 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:
- Downloads all the HTML (even hidden parts)
- Parses and creates DOM nodes for hidden elements
- Loads images and resources referenced in hidden content
- Keeps all this in memory
- Just visually hides it with CSS
With TimeTimer's conditional rendering:
- Only the active period's content is sent to the browser
- The DOM contains only what should be visible right now
- When the period changes, React replaces the old content with the new one
- 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
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
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
| Prop | Type | Default | Description |
|---|---|---|---|
date | string | - | Fixed date: DD/MM/YYYY or DD/MM/YYYY HH:MM or DD/MM |
start | string | - | Start date: DD/MM/YYYY HH:MM (requires duration) |
duration | string | - | Duration: 30d, 2h, 45m (requires start) |
strict | boolean | false | Uses UTC instead of local timezone |
children | node | - | Main content + FallbackBefore/After |
FallbackBefore
| Prop | Type | Default | Description |
|---|---|---|---|
start | string | - | Custom start date for the fallback |
children | node | - | Content to display before the period |
FallbackAfter
| Prop | Type | Default | Description |
|---|---|---|---|
end | string | - | Date until when to display the fallback (doesn't affect duration) |
children | node | - | Content to display after the period |
Accepted Formats
Dates
| Format | Example | Behavior |
|---|---|---|
DD/MM/YYYY | 25/12/2025 | All day long (00:00 to 23:59) |
DD/MM/YYYY HH:MM | 25/12/2025 18:00 | From 6 PM, for 24 hours |
DD/MM | 25/12 | Current year automatic |
Durations
| Unit | Example | Description |
|---|---|---|
d | 30d | 30 days |
h | 2h | 2 hours |
m | 45m | 45 minutes |
Other Examples
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
<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>
One-Week Period
<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>
With Custom Dates
<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>
<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>
Customizable Duration
<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>
Custom Dates in Fallbacks
<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>
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 of2hfor a shorter duration - Customize the CSS styles to your liking
Advanced Use Cases
Maintenance Banner
<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>
Countdown to an Event
<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>
2-Hour Flash Sale
<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>
Temporary Discount Code (15 minutes)
<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>
Temporary Message After a Sale
<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>
Interactive Advent Calendar
Here's a complete example combining TimeTimer with Columns, Card, and LogoIcon to create an Advent calendar:
<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>
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
strictfor global events - Test with different dates before publishing
- Take advantage of custom dates in fallbacks—it's super flexible
Resources
This article is part of the Design your site series:
Loading comments…
Related posts

Component ImageOnClick
September 5, 2025

Component Card
September 7, 2025
Component LogoIcon
September 15, 2025