Finance & Investing

Free Stock Quote Card Widget

A clean single-symbol quote card with price, change chip, and day range.

Stock Quote Card renders a compact, brandable card for one ticker symbol: a big current price, a green/red change chip, and the day's open, high, and low with a visual range bar. It's built for finance blogs, investor-relations pages, and market dashboards that want to show a specific quote they supply themselves. Install it with one script tag and pass your data inline as JSON or point it at your own JSON endpoint.

Live demo

Embed this widget

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.

Hosted (recommended)html
<script src="https://hayneslab.dev/widgets/stock-quote-card.js" data-hl-symbol="ACME" data-hl-config='{"price":184.27,"change":1.84,"open":181.05,"high":186.4,"low":179.92}' async></script>
<!-- Free Stock Quote Card widget by haynes lab — https://hayneslab.dev/tools/widgets/stock-quote-card -->
Inline (self-contained)html
<!-- 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>

Configuration

Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.

AttributeDefaultDescription
data-hl-symbol"STOCK"The ticker symbol shown at the top of the card.
data-hl-config(none)Inline JSON object with "price" (required) plus optional "change" (percent), "open", "high", and "low" numbers that you supply.
data-hl-endpoint(none)Optional URL returning the same quote JSON; fetched instead of data-hl-config and falls back to data-hl-config on error.
data-hl-accent"#f97316"Hex color used for the day-range marker.

Frequently asked questions

How do I install the Stock Quote Card?

Paste the script tag where you want the card to appear and add your quote data in data-hl-config (or point data-hl-endpoint at your own JSON feed). The card renders inline right after the script tag.

Where does the quote data come from?

You supply it. The widget never calls a market-data API on its own — it renders the price, change, open, high, and low you pass via data-hl-config or your own data-hl-endpoint URL.

Can I customize the symbol and colors?

Yes — set data-hl-symbol for the ticker text and data-hl-accent for the range-marker color. The card inherits a neutral light style that fits most sites.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it's a single vanilla JavaScript file with no dependencies, so it works anywhere you can paste a script tag, including WordPress, Shopify, Squarespace, and plain HTML sites.

More free widgets

Need a custom widget?

We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.

Get in Touch