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 Stock Quote Card widget by haynes lab — https://hayneslab.dev/tools/widgets/stock-quote-card -->
<script data-hl-symbol="ACME" data-hl-config='{"price":184.27,"change":1.84,"open":181.05,"high":186.4,"low":179.92}'>
/*!
* haynes lab — Stock Quote Card widget (free)
* Docs & embed code: https://hayneslab.dev/tools/widgets/stock-quote-card
*
* Owner-supplied data only: this widget renders the quote YOU provide.
* Pass it inline via data-hl-config (JSON: {"price","change","open","high","low"})
* or via data-hl-endpoint (URL returning that same JSON shape).
*/
(function () {
'use strict';
var PREFIX = 'hlw-stock-quote-card';
var STYLE_ID = 'hlw-stock-quote-card-styles';
function findScript() {
return document.currentScript ||
document.querySelector('script[src*="widgets/stock-quote-card.js"]');
}
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
var css =
'.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
'.' + PREFIX + ' { max-width: 340px; margin: 0 auto; padding: 20px 22px 26px;' +
' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
' font-size: 16px; line-height: 1.4; color: #1f2937; background: #ffffff;' +
' border: 1px solid #e5e7eb; border-radius: 12px; }' +
'.' + PREFIX + '__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 10px; }' +
'.' + PREFIX + '__symbol { font-size: 18px; font-weight: 700; color: #111827; letter-spacing: 0.02em; }' +
'.' + PREFIX + '__label { font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.06em; }' +
'.' + PREFIX + '__pricerow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }' +
'.' + PREFIX + '__price { font-size: 32px; font-weight: 700; color: #111827; line-height: 1.1; }' +
'.' + PREFIX + '__chip { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px;' +
' border-radius: 999px; font-size: 13px; font-weight: 600; }' +
'.' + PREFIX + '__chip--up { background: #dcfce7; color: #166534; }' +
'.' + PREFIX + '__chip--down { background: #fee2e2; color: #991b1b; }' +
'.' + PREFIX + '__range { margin-bottom: 12px; }' +
'.' + PREFIX + '__bar { position: relative; height: 6px; border-radius: 3px; background: #e5e7eb; }' +
'.' + PREFIX + '__marker { position: absolute; top: 50%; width: 12px; height: 12px; border-radius: 50%;' +
' background: var(--hlw-accent); border: 2px solid #ffffff; box-shadow: 0 0 0 1px #d1d5db;' +
' transform: translate(-50%, -50%); }' +
'.' + PREFIX + '__bounds { display: flex; justify-content: space-between; margin-top: 6px;' +
' font-size: 12px; color: #6b7280; }' +
'.' + PREFIX + '__stats { display: flex; justify-content: space-between; gap: 8px;' +
' border-top: 1px solid #f3f4f6; padding-top: 12px; }' +
'.' + PREFIX + '__stat { text-align: center; flex: 1; }' +
'.' + PREFIX + '__statlabel { display: block; font-size: 11px; color: #9ca3af;' +
' text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 2px; }' +
'.' + PREFIX + '__statvalue { font-size: 14px; font-weight: 600; color: #374151; }' +
'.' + PREFIX + '__credit { display: inline-flex; align-items: center; gap: 4px; margin-top: 14px;' +
' font-size: 11px; 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 num(v) {
var n = typeof v === 'number' ? v : parseFloat(v);
return isFinite(n) ? n : null;
}
function money(n) {
return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function parseConfig(script) {
var raw = script.getAttribute('data-hl-config');
if (!raw) return null;
var data;
try {
data = JSON.parse(raw);
} catch (e) {
console.warn('[haynes lab stock-quote-card] data-hl-config is not valid JSON.', e);
return null;
}
return data && typeof data === 'object' ? data : null;
}
function render(script, data) {
var price = num(data && data.price);
if (price === null) {
console.warn('[haynes lab stock-quote-card] No valid "price" in quote data. Nothing rendered.');
return;
}
var change = num(data.change);
var open = num(data.open);
var high = num(data.high);
var low = num(data.low);
var symbol = script.getAttribute('data-hl-symbol') || 'STOCK';
var accent = script.getAttribute('data-hl-accent') || '#f97316';
injectStyles();
var root = el('div', PREFIX);
root.setAttribute('role', 'figure');
root.setAttribute('aria-label', symbol + ' stock quote');
root.style.setProperty('--hlw-accent', accent);
var head = el('div', PREFIX + '__head');
head.appendChild(el('span', PREFIX + '__symbol', symbol));
head.appendChild(el('span', PREFIX + '__label', 'Stock Quote'));
root.appendChild(head);
var pricerow = el('div', PREFIX + '__pricerow');
pricerow.appendChild(el('span', PREFIX + '__price', '$' + money(price)));
if (change !== null) {
var up = change >= 0;
var chip = el('span', PREFIX + '__chip ' + (up ? PREFIX + '__chip--up' : PREFIX + '__chip--down'));
chip.appendChild(el('span', null, up ? '▲' : '▼'));
chip.appendChild(document.createTextNode((up ? '+' : '') + change.toFixed(2) + '%'));
pricerow.appendChild(chip);
}
root.appendChild(pricerow);
if (high !== null && low !== null && high > low) {
var range = el('div', PREFIX + '__range');
var bar = el('div', PREFIX + '__bar');
var pct = Math.min(100, Math.max(0, ((price - low) / (high - low)) * 100));
var marker = el('span', PREFIX + '__marker');
marker.style.left = pct + '%';
marker.setAttribute('aria-hidden', 'true');
bar.appendChild(marker);
range.appendChild(bar);
var bounds = el('div', PREFIX + '__bounds');
bounds.appendChild(el('span', null, '$' + money(low)));
bounds.appendChild(el('span', null, '$' + money(high)));
range.appendChild(bounds);
root.appendChild(range);
}
if (open !== null || high !== null || low !== null) {
var stats = el('div', PREFIX + '__stats');
[['Open', open], ['High', high], ['Low', low]].forEach(function (pair) {
if (pair[1] === null) return;
var stat = el('div', PREFIX + '__stat');
stat.appendChild(el('span', PREFIX + '__statlabel', pair[0]));
stat.appendChild(el('span', PREFIX + '__statvalue', '$' + money(pair[1])));
stats.appendChild(stat);
});
if (stats.children.length) root.appendChild(stats);
}
var credit = el('a', PREFIX + '__credit');
credit.appendChild(logoSvg());
credit.appendChild(document.createTextNode('Powered by haynes lab'));
credit.href = 'https://hayneslab.dev/tools/widgets/stock-quote-card';
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 stock-quote-card] Could not locate its own script tag. Nothing rendered.');
return;
}
var config = parseConfig(script);
var endpoint = script.getAttribute('data-hl-endpoint');
if (endpoint) {
fetch(endpoint)
.then(function (res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function (data) { render(script, data); })
.catch(function (e) {
console.warn('[haynes lab stock-quote-card] Failed to load data-hl-endpoint "' + endpoint + '".', e);
if (config) render(script, config);
});
} else if (config) {
render(script, config);
} else {
console.warn('[haynes lab stock-quote-card] Provide data-hl-config (JSON) or data-hl-endpoint. Nothing rendered.');
}
}
if (document.body) {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();
</script>