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 Reading Time widget by haynes lab — https://hayneslab.dev/tools/widgets/reading-time -->
<script>
/*!
* haynes lab — Reading Time widget (free)
* Docs & embed code: https://hayneslab.dev/tools/widgets/reading-time
*/
(function () {
'use strict';
var PREFIX = 'hlw-reading-time';
var STYLE_ID = 'hlw-reading-time-styles';
function findScript() {
return document.currentScript ||
document.querySelector('script[src*="widgets/reading-time.js"]');
}
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
var css =
'.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
'.' + PREFIX + ' { display: inline-flex; align-items: baseline; gap: 8px; flex-wrap: wrap;' +
' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
' font-size: 13px; line-height: 1.5; color: #6b7280; }' +
'.' + PREFIX + '__badge { display: inline-flex; align-items: center; gap: 6px;' +
' padding: 4px 12px; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 999px;' +
' color: #374151; font-size: 13px; font-weight: 500; white-space: nowrap; }' +
'.' + PREFIX + '__icon { display: block; flex: 0 0 auto; }' +
'.' + PREFIX + '__credit { font-size: 11px; display: inline-flex; align-items: center; gap: 4px;' +
' color: #9ca3af; text-decoration: none; }' +
'.' + PREFIX + '__credit svg { display: block; }' +
'.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }';
var style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = css;
document.head.appendChild(style);
}
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 clockSvg(accent) {
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('viewBox', '0 0 16 16');
svg.setAttribute('width', '14');
svg.setAttribute('height', '14');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
svg.setAttribute('class', PREFIX + '__icon');
var face = document.createElementNS(svgNS, 'circle');
face.setAttribute('cx', '8');
face.setAttribute('cy', '8');
face.setAttribute('r', '6.5');
face.setAttribute('fill', 'none');
face.setAttribute('stroke', accent);
face.setAttribute('stroke-width', '1.5');
svg.appendChild(face);
var hands = document.createElementNS(svgNS, 'path');
hands.setAttribute('d', 'M8 4.5 V8 L10.8 9.8');
hands.setAttribute('fill', 'none');
hands.setAttribute('stroke', accent);
hands.setAttribute('stroke-width', '1.5');
hands.setAttribute('stroke-linecap', 'round');
svg.appendChild(hands);
return svg;
}
function countWords(node) {
var clone = node.cloneNode(true);
var skip = clone.querySelectorAll('script, style, noscript, .' + PREFIX);
Array.prototype.forEach.call(skip, function (n) {
if (n.parentNode) n.parentNode.removeChild(n);
});
var text = (clone.textContent || '').replace(/\s+/g, ' ').trim();
if (!text) return 0;
return text.split(' ').length;
}
function findTarget(script) {
var selector = script.getAttribute('data-hl-target');
if (selector) {
try {
var chosen = document.querySelector(selector);
if (chosen) return chosen;
console.warn('[haynes lab reading-time] data-hl-target "' + selector + '" matched nothing. Falling back to article/body.');
} catch (e) {
console.warn('[haynes lab reading-time] data-hl-target "' + selector + '" is not a valid selector. Falling back to article/body.', e);
}
}
return document.querySelector('article') || document.body;
}
function build(script) {
var wpm = parseInt(script.getAttribute('data-hl-wpm'), 10);
if (isNaN(wpm) || wpm < 50 || wpm > 1000) wpm = 200;
var accent = script.getAttribute('data-hl-accent') || '#f97316';
var target = findTarget(script);
var words = countWords(target);
var minutes = Math.max(1, Math.ceil(words / wpm));
injectStyles();
var root = el('span', PREFIX);
var label = minutes + ' min read';
var badge = el('span', PREFIX + '__badge');
badge.setAttribute('aria-label', 'Estimated reading time: ' + label);
badge.appendChild(clockSvg(accent));
badge.appendChild(document.createTextNode(label));
root.appendChild(badge);
var credit = el('a', PREFIX + '__credit');
credit.appendChild(logoSvg());
credit.appendChild(document.createTextNode('Powered by haynes lab'));
credit.href = 'https://hayneslab.dev/tools/widgets/reading-time';
credit.target = '_blank';
credit.rel = 'noopener';
root.appendChild(credit);
if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
script.insertAdjacentElement('afterend', root);
} else {
document.body.appendChild(root);
}
}
function init() {
var script = findScript();
if (!script) {
console.warn('[haynes lab reading-time] Could not locate its own script tag. Nothing rendered.');
return;
}
build(script);
}
if (document.body) {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();
</script>