- You’ll build
- Countdown Timer to WordPress Without a Plugin
- Code required
- HTML + CSS + JS
- Installation
- Theme and role dependent
On this page
Before you startHow the countdown worksInstall it in WordPressSet a reliable date and timezoneCustomize the displayTroubleshoot incorrect valuesAccessibility and performanceFrequently asked questionsCheck your installationComplete working code
The preview and download are generated from these same source files. The complete snippet requires permission to save inline styles and scripts.
<section class="iljs-demo iljs-countdown" data-iljs-end="2027-01-01T00:00:00Z"><span>AN EXAMPLE DEADLINE · JAN 1, 2027 UTC</span><h2>Something worth waiting for.</h2><div class="iljs-countdown-grid" role="timer" aria-label="Time remaining"><div><b data-unit="days">—</b><small>days</small></div><div><b data-unit="hours">—</b><small>hours</small></div><div><b data-unit="minutes">—</b><small>minutes</small></div><div><b data-unit="seconds">—</b><small>seconds</small></div></div><p class="iljs-countdown-status" role="status">Enable JavaScript to see the time remaining.</p></section>
/* Change the values here, keeping the iljs- prefix to avoid theme conflicts. */
.iljs-demo { --iljs-accent:#9235f6; --iljs-radius:18px; font-family:system-ui,sans-serif; color:#242038; width:min(620px,100%); margin:auto; }
.iljs-demo button,.iljs-demo a { font:inherit; }
.iljs-demo button:focus-visible,.iljs-demo a:focus-visible,.iljs-demo input:focus-visible,.iljs-demo summary:focus-visible { outline:3px solid #c491ff; outline-offset:4px; }
.iljs-countdown { max-width:560px;background:#211530;padding:35px 25px;border-radius:var(--iljs-radius);text-align:center;color:white; }
.iljs-countdown>span { font-size:9px;letter-spacing:1.2px;color:#cc9afd; }
.iljs-countdown h2 { font-size:27px;line-height:1.2;margin:20px 0 25px; }
.iljs-countdown-grid { display:grid;grid-template-columns:repeat(4,1fr);gap:10px; }
.iljs-countdown-grid>div { background:#ffffff0c;border:1px solid #ffffff12;border-radius:10px;padding:18px 5px; }
.iljs-countdown-grid b { display:block;font-size:32px;font-variant-numeric:tabular-nums; }
.iljs-countdown-grid small { color:#bba9ce;font-size:10px; }
.iljs-countdown-status { font-size:11px;color:#bba9ce;margin:16px 0 0;min-height:18px; }
@media(max-width:350px){.iljs-countdown-grid b{font-size:25px}.iljs-countdown{padding:30px 15px}}
(() => {
const init=()=>document.querySelectorAll('.iljs-countdown').forEach(root=>{
if(root.dataset.iljsReady)return;root.dataset.iljsReady='true';
const end=Date.parse(root.dataset.iljsEnd);const status=root.querySelector('.iljs-countdown-status');let interval;
if(!Number.isFinite(end)){status.textContent='Set a valid deadline including a timezone.';return;}
const tick=()=>{const left=Math.max(0,Math.floor((end-Date.now())/1000));const values={days:Math.floor(left/86400),hours:Math.floor(left%86400/3600),minutes:Math.floor(left%3600/60),seconds:left%60};for(const [unit,value] of Object.entries(values))root.querySelector('[data-unit="'+unit+'"]').textContent=String(value).padStart(2,'0');status.textContent=left===0?'This deadline has passed.':'Deadline: '+new Date(end).toLocaleString();if(left===0&&interval)clearInterval(interval);};
tick();if(end>Date.now())interval=setInterval(()=>{if(!document.hidden)tick();},1000);
document.addEventListener('visibilitychange',()=>{if(!document.hidden)tick();});
});document.readyState==='loading'?document.addEventListener('DOMContentLoaded',init,{once:true}):init();
})();
<section class="iljs-demo iljs-countdown" data-iljs-end="2027-01-01T00:00:00Z"><span>AN EXAMPLE DEADLINE · JAN 1, 2027 UTC</span><h2>Something worth waiting for.</h2><div class="iljs-countdown-grid" role="timer" aria-label="Time remaining"><div><b data-unit="days">—</b><small>days</small></div><div><b data-unit="hours">—</b><small>hours</small></div><div><b data-unit="minutes">—</b><small>minutes</small></div><div><b data-unit="seconds">—</b><small>seconds</small></div></div><p class="iljs-countdown-status" role="status">Enable JavaScript to see the time remaining.</p></section>
<style>
/* Change the values here, keeping the iljs- prefix to avoid theme conflicts. */
.iljs-demo { --iljs-accent:#9235f6; --iljs-radius:18px; font-family:system-ui,sans-serif; color:#242038; width:min(620px,100%); margin:auto; }
.iljs-demo button,.iljs-demo a { font:inherit; }
.iljs-demo button:focus-visible,.iljs-demo a:focus-visible,.iljs-demo input:focus-visible,.iljs-demo summary:focus-visible { outline:3px solid #c491ff; outline-offset:4px; }
.iljs-countdown { max-width:560px;background:#211530;padding:35px 25px;border-radius:var(--iljs-radius);text-align:center;color:white; }
.iljs-countdown>span { font-size:9px;letter-spacing:1.2px;color:#cc9afd; }
.iljs-countdown h2 { font-size:27px;line-height:1.2;margin:20px 0 25px; }
.iljs-countdown-grid { display:grid;grid-template-columns:repeat(4,1fr);gap:10px; }
.iljs-countdown-grid>div { background:#ffffff0c;border:1px solid #ffffff12;border-radius:10px;padding:18px 5px; }
.iljs-countdown-grid b { display:block;font-size:32px;font-variant-numeric:tabular-nums; }
.iljs-countdown-grid small { color:#bba9ce;font-size:10px; }
.iljs-countdown-status { font-size:11px;color:#bba9ce;margin:16px 0 0;min-height:18px; }
@media(max-width:350px){.iljs-countdown-grid b{font-size:25px}.iljs-countdown{padding:30px 15px}}
</style>
<script>
(() => {
const init=()=>document.querySelectorAll('.iljs-countdown').forEach(root=>{
if(root.dataset.iljsReady)return;root.dataset.iljsReady='true';
const end=Date.parse(root.dataset.iljsEnd);const status=root.querySelector('.iljs-countdown-status');let interval;
if(!Number.isFinite(end)){status.textContent='Set a valid deadline including a timezone.';return;}
const tick=()=>{const left=Math.max(0,Math.floor((end-Date.now())/1000));const values={days:Math.floor(left/86400),hours:Math.floor(left%86400/3600),minutes:Math.floor(left%3600/60),seconds:left%60};for(const [unit,value] of Object.entries(values))root.querySelector('[data-unit="'+unit+'"]').textContent=String(value).padStart(2,'0');status.textContent=left===0?'This deadline has passed.':'Deadline: '+new Date(end).toLocaleString();if(left===0&&interval)clearInterval(interval);};
tick();if(end>Date.now())interval=setInterval(()=>{if(!document.hidden)tick();},1000);
document.addEventListener('visibilitychange',()=>{if(!document.hidden)tick();});
});document.readyState==='loading'?document.addEventListener('DOMContentLoaded',init,{once:true}):init();
})();
</script>Before you start
The widget calculates remaining time locally from the browser’s clock. It does not synchronize with a time server or enforce access to a sale. A visitor with an inaccurate device clock may see a different result. This is a visual information widget, not an authoritative scheduling or payment system.
How the countdown works
The deadline lives in the outer element’s data-iljs-end attribute as an ISO date-time string. The trailing Z specifies UTC. The script parses that value once and rejects an invalid result with a visible message rather than silently counting from an unintended date.
Each tick subtracts Date.now() from the target, converts milliseconds into whole seconds, and clamps the result at zero. Division and remainders separate days, hours, minutes, and seconds. The script writes each value into its matching data-unit element and pads short numbers to two digits.
The timer does not subtract one from its previous result. Recalculating from the deadline avoids accumulating drift when browser intervals run late. Hidden tabs skip periodic updates; returning to the tab triggers a fresh calculation. Once the remaining time reaches zero, the status changes to “This deadline has passed” and the running interval is cleared.
Install it in WordPress
- Add a Custom HTML block to a draft page and paste the HTML markup. Keep the four
data-unitlabels unchanged. - Set
data-iljs-endto a real deadline with an explicit timezone. Edit the visible example date in the heading area so the announcement matches the attribute. - Install the CSS through your theme’s available Additional CSS interface or an approved stylesheet. The declarations affect
.iljs-countdown, not every heading or number on the page. - Install the JavaScript through a permitted method. See how to add JavaScript to WordPress for script-saving limits and child-theme enqueueing.
- Save and preview the front end. Verify both a future deadline and a temporarily substituted past deadline before publishing.
WordPress block and classic themes can use the same page-level markup. Elementor can use an HTML widget, with styles and script supplied through permitted interfaces. WordPress 7.0’s separate CSS and JavaScript panels require unfiltered_html. Restricted plans or roles may strip the inline version; use administrator-approved installation rather than assuming every account can run it.
Set a reliable date and timezone
Use a complete ISO string. 2027-01-01T00:00:00Z means midnight UTC. A value such as 2027-01-01T00:00:00+05:00 describes midnight at an offset five hours ahead of UTC. These are different instants. Confirm the event’s intended timezone and convert it deliberately.
Avoid a bare string such as 01/02/27. Locale-dependent formats can be ambiguous, and browser parsing is not a substitute for a clearly specified instant. If a daylight-saving timezone matters, determine the correct offset for that specific deadline. The browser’s toLocaleString() displays the instant using its local settings, which is useful but should not replace the clearly labeled event timezone in your surrounding copy.
The general WordPress installation guide recommends changing one input at a time. Here, first verify the original UTC example, then change the machine-readable deadline and its visible description together. For an event explanation, an FAQ accordion can clarify opening times and eligibility without crowding the countdown panel.
Customize the display
The dark panel uses #211530, with translucent cards behind each number. Change those colors and --iljs-radius to match your site. The digits use tabular numerals to reduce shifting as seconds change. Preserve that declaration if the font supports it.
The four-card grid remains four columns in the supplied version. At a very narrow width, padding and digit size decrease. If your surrounding layout makes the widget narrower than 280 pixels, consider two columns rather than squeezing the numbers further. Keep labels visible; four numbers without units are ambiguous.
Change the headline to identify the event. A gradient action link below the timer can point to real event details, but do not label it as a checkout action unless it reaches an actual checkout. The countdown does not implement reservations, capacity, or offer validity.
Troubleshoot incorrect values
The widget says the deadline is invalid. Check the data-iljs-end spelling and the full ISO date. An impossible calendar value or missing attribute can make parsing fail.
The countdown is off by several hours. Compare the timezone offset with the event’s intended instant. A UTC deadline and a local midnight are not interchangeable.
The digits never update. Confirm that the script loaded and that WordPress did not remove it. The initial em dashes intentionally avoid presenting invented time values before initialization.
The counter jumps after returning to the tab. That is expected. The widget recalculates the actual remaining time rather than replaying every missed interval. Hidden-tab throttling should not cause accumulated drift.
Zero remains visible after expiration. That is the designed expired state. Rewrite the message or hide the event action if your content requires it. Never reset the deadline automatically merely to maintain urgency.
Accessibility and performance
The changing numbers use role="timer", which does not need to announce every second. The status message describes the deadline or expiry separately. Avoid making the entire counter an assertive live region; repeated announcements would interrupt reading.
The widget updates once per second while visible and stops at expiry. It does not make network requests or transmit visitor information. It has no continuous visual animation, so reduced motion does not require a separate moving version. Test the layout with larger text and phone-sized widths, including deadlines with more than two days digits.
Frequently asked questions
Can it enforce a deadline?
No. The browser’s clock and code are controlled locally. Enforcing a purchase, registration, or access rule requires the relevant authoritative service, which this example does not supply.
Can I add several timers?
Yes. Each root has its own deadline and scoped queries. Keep visible descriptions consistent with their own data attributes. Avoid adding many continuously updating widgets to one page.
Why does the preview expire eventually?
Because it uses an actual fixed deadline. An honest example should demonstrate the expired state when that instant passes instead of secretly restarting.
Check your installation
Compare the deadline with a trusted calendar, test a past date, and inspect the phone layout. The information is ready when its date, timezone, labels, and expired behavior all tell the same story.


