Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.
<!-- Free Business Hours widget by haynes lab — https://hayneslab.dev/tools/widgets/business-hours -->
<script data-hl-config='{"mon":"8:00-18:00","tue":"8:00-18:00","wed":"8:00-18:00","thu":"8:00-18:00","fri":"8:00-18:00","sat":"9:00-14:00","sun":"closed"}'>
/*!
* haynes lab — Business Hours widget (free)
* Docs & embed code: https://hayneslab.dev/tools/widgets/business-hours
*/
(function () {
'use strict';
var PREFIX = 'hlw-business-hours';
var DAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
var DAY_LABELS = { mon: 'Monday', tue: 'Tuesday', wed: 'Wednesday', thu: 'Thursday', fri: 'Friday', sat: 'Saturday', sun: 'Sunday' };
var WEEKDAY_TO_KEY = { Mon: 'mon', Tue: 'tue', Wed: 'wed', Thu: 'thu', Fri: 'fri', Sat: 'sat', Sun: 'sun' };
function injectStyles() {
if (document.getElementById(PREFIX + '-styles')) return;
var style = document.createElement('style');
style.id = PREFIX + '-styles';
style.textContent =
'.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; margin: 0; padding: 0; }' +
'.' + PREFIX + ' { font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.5; color: #1f2937; background: #ffffff; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px 12px; max-width: 340px; }' +
'.' + PREFIX + '__header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }' +
'.' + PREFIX + '__title { font-size: 15px; font-weight: 600; color: #111827; }' +
'.' + PREFIX + '__badge { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 999px; white-space: nowrap; }' +
'.' + PREFIX + '__badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }' +
'.' + PREFIX + '__badge--open { color: var(--hlw-accent, #22c55e); background: #f0fdf4; background: color-mix(in srgb, var(--hlw-accent, #22c55e) 12%, transparent); }' +
'.' + PREFIX + '__badge--closed { color: #6b7280; background: #f3f4f6; }' +
'.' + PREFIX + '__table { width: 100%; border-collapse: collapse; }' +
'.' + PREFIX + '__row td { padding: 4px 0; border-bottom: 1px solid #f3f4f6; }' +
'.' + PREFIX + '__row:last-child td { border-bottom: none; }' +
'.' + PREFIX + '__day { color: #374151; }' +
'.' + PREFIX + '__hours { text-align: right; color: #6b7280; }' +
'.' + PREFIX + '__row--today .' + PREFIX + '__day { font-weight: 700; color: var(--hlw-accent, #22c55e); }' +
'.' + PREFIX + '__row--today .' + PREFIX + '__hours { font-weight: 600; color: #111827; }' +
'.' + PREFIX + '__credit { display: flex; align-items: center; justify-content: flex-end; gap: 4px; margin-top: 8px; font-size: 11px; color: #9ca3af; text-decoration: none; }' +
'.' + PREFIX + '__credit svg { display: block; }' +
'.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }';
document.head.appendChild(style);
}
function parseRange(value) {
if (typeof value !== 'string') return null;
var v = value.trim().toLowerCase();
if (v === 'closed' || v === '') return null;
var m = v.match(/^(\d{1,2}):(\d{2})\s*-\s*(\d{1,2}):(\d{2})$/);
if (!m) return null;
var start = (+m[1]) * 60 + (+m[2]);
var end = (+m[3]) * 60 + (+m[4]);
if (start >= 1440 || end > 1440) return null;
return { start: start, end: end };
}
function nowInZone(tz) {
try {
var parts = new Intl.DateTimeFormat('en-US', {
timeZone: tz, weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23'
}).formatToParts(new Date());
var bag = {};
parts.forEach(function (p) { bag[p.type] = p.value; });
var hour = (+bag.hour) % 24;
return { day: WEEKDAY_TO_KEY[bag.weekday], minutes: hour * 60 + (+bag.minute) };
} catch (e) {
console.warn('[hlw business-hours] Unknown timezone "' + tz + '" — falling back to visitor local time.');
var d = new Date();
var keys = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
return { day: keys[d.getDay()], minutes: d.getHours() * 60 + d.getMinutes() };
}
}
function isOpen(hours, tz) {
var now = nowInZone(tz);
var range = parseRange(hours[now.day]);
if (!range) return false;
if (range.end <= range.start) return now.minutes >= range.start || now.minutes < range.end;
return now.minutes >= range.start && now.minutes < range.end;
}
function el(tag, className, text) {
var node = document.createElement(tag);
if (className) node.className = className;
if (text != null) node.textContent = text;
return node;
}
function logoSvg() {
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('viewBox', '0 0 32 32');
svg.setAttribute('width', '13');
svg.setAttribute('height', '13');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
function circle(cx, cy, r, attrs) {
var c = document.createElementNS(svgNS, 'circle');
c.setAttribute('cx', cx);
c.setAttribute('cy', cy);
c.setAttribute('r', r);
for (var key in attrs) c.setAttribute(key, attrs[key]);
svg.appendChild(c);
}
circle(16, 16, 16, { fill: '#0f0a0a' });
circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
circle(16, 16, 3, { fill: '#06b6d4' });
circle(24.5, 7.5, 2, { fill: '#f97316' });
circle(7, 16, 1.8, { fill: '#f97316' });
circle(20, 20, 1.5, { fill: '#f97316' });
return svg;
}
function render(script) {
var raw = script.getAttribute('data-hl-config');
if (!raw) {
console.warn('[hlw business-hours] Missing required data-hl-config (JSON weekly hours). Widget not rendered. See https://hayneslab.dev/tools/widgets/business-hours');
return;
}
var hours;
try {
hours = JSON.parse(raw);
if (!hours || typeof hours !== 'object') throw new Error('not an object');
} catch (e) {
console.warn('[hlw business-hours] data-hl-config is not valid JSON — widget not rendered.', e);
return;
}
var tz = script.getAttribute('data-hl-timezone') || 'America/New_York';
var title = script.hasAttribute('data-hl-title') ? script.getAttribute('data-hl-title') : 'Business hours';
var showStatus = (script.getAttribute('data-hl-show-status') || 'true') !== 'false';
var accent = script.getAttribute('data-hl-accent') || '#22c55e';
injectStyles();
var root = el('div', PREFIX);
root.style.setProperty('--hlw-accent', accent);
var badge = null;
if (title !== '' || showStatus) {
var header = el('div', PREFIX + '__header');
if (title !== '') header.appendChild(el('h3', PREFIX + '__title', title));
if (showStatus) badge = el('span', PREFIX + '__badge', '');
if (badge) header.appendChild(badge);
root.appendChild(header);
}
var todayKey = nowInZone(tz).day;
var table = el('table', PREFIX + '__table');
var tbody = document.createElement('tbody');
DAYS.forEach(function (day) {
var tr = el('tr', PREFIX + '__row' + (day === todayKey ? ' ' + PREFIX + '__row--today' : ''));
tr.appendChild(el('td', PREFIX + '__day', DAY_LABELS[day]));
var value = typeof hours[day] === 'string' && hours[day].trim() !== '' ? hours[day].trim() : 'closed';
var range = parseRange(value);
tr.appendChild(el('td', PREFIX + '__hours', range ? value.replace('-', '–') : 'Closed'));
tbody.appendChild(tr);
});
table.appendChild(tbody);
root.appendChild(table);
var credit = el('a', PREFIX + '__credit');
credit.appendChild(logoSvg());
credit.appendChild(document.createTextNode('Powered by haynes lab'));
credit.href = 'https://hayneslab.dev/tools/widgets/business-hours';
credit.target = '_blank';
credit.rel = 'noopener';
root.appendChild(credit);
function updateStatus() {
if (!badge) return;
var open = isOpen(hours, tz);
badge.textContent = open ? 'Open now' : 'Closed';
badge.className = PREFIX + '__badge ' + (open ? PREFIX + '__badge--open' : PREFIX + '__badge--closed');
}
updateStatus();
if (badge) setInterval(updateStatus, 60000);
if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
script.insertAdjacentElement('afterend', root);
} else {
document.body.appendChild(root);
}
}
function init() {
var script = document.currentScript ||
document.querySelector('script[src*="widgets/business-hours.js"]');
if (!script) {
console.warn('[hlw business-hours] Could not locate its own <script> tag — widget not rendered.');
return;
}
if (document.body) {
render(script);
} else {
document.addEventListener('DOMContentLoaded', function () { render(script); });
}
}
init();
})();
</script>