Business & Operations

Free Office Map Card Widget

Embedded OpenStreetMap card with your address and a directions link.

A compact card that shows your office location on an interactive OpenStreetMap, lists your address, and links visitors straight to Google Maps directions. Ideal for contact pages, footers, and local business sites. Install it by pasting a single script tag with your coordinates and address.

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/office-map-card.js" data-hl-lat="35.2271" data-hl-lon="-80.8431" data-hl-title="Visit our Charlotte office" data-hl-address="Tryon Street
Charlotte, NC 28202" async></script>
<!-- Free Office Map Card widget by haynes lab — https://hayneslab.dev/tools/widgets/office-map-card -->
Inline (self-contained)html
<!-- Free Office Map Card widget by haynes lab — https://hayneslab.dev/tools/widgets/office-map-card -->
<script data-hl-lat="35.2271" data-hl-lon="-80.8431" data-hl-title="Visit our Charlotte office" data-hl-address="Tryon Street
Charlotte, NC 28202">
/*!
 * haynes lab — Office Map Card widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/office-map-card
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-office-map-card';
  var STYLE_ID = 'hlw-office-map-card-styles';
  var BBOX_SPAN = 0.01; // degrees of latitude/longitude either side of the point

  function findScript() {
    return document.currentScript ||
      document.querySelector('script[src*="widgets/office-map-card.js"]');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 460px; margin: 0 auto; overflow: hidden;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 15px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; }' +
      '.' + PREFIX + '__map { display: block; width: 100%; height: 240px; border: 0;' +
      ' border-bottom: 1px solid #e5e7eb; }' +
      '.' + PREFIX + '__body { padding: 18px 20px 30px; }' +
      '.' + PREFIX + '__title { margin: 0 0 10px; font-size: 17px; font-weight: 600; color: #111827; }' +
      '.' + PREFIX + '__address { margin: 0 0 14px; font-style: normal; font-size: 14px;' +
      ' line-height: 1.55; color: #4b5563; }' +
      '.' + PREFIX + '__directions { display: inline-flex; align-items: center; gap: 6px;' +
      ' padding: 9px 16px; border-radius: 8px; background: var(--hlw-accent, #f97316); color: #ffffff;' +
      ' font-size: 14px; font-weight: 600; text-decoration: none; }' +
      '.' + PREFIX + '__directions:hover { filter: brightness(0.92); }' +
      '.' + PREFIX + '__directions:focus-visible { outline: 2px solid var(--hlw-accent, #f97316); outline-offset: 2px; }' +
      '.' + PREFIX + '__credit { position: absolute; right: 10px; bottom: 6px; 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; }' +
      '@media (max-width: 520px) { .' + PREFIX + ' { max-width: 100%; }' +
      ' .' + PREFIX + '__map { height: 200px; } }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function parseCoord(script, attr, min, max) {
    var raw = script.getAttribute(attr);
    var value = parseFloat(raw);
    if (raw == null || isNaN(value) || value < min || value > max) {
      console.warn('[haynes lab office-map-card] Missing or invalid ' + attr +
        ' attribute (a number between ' + min + ' and ' + max + '). Nothing rendered.');
      return null;
    }
    return value;
  }

  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 pinSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', '14');
    svg.setAttribute('height', '14');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    var path = document.createElementNS(svgNS, 'path');
    path.setAttribute('d', 'M12 2a7 7 0 0 0-7 7c0 5.25 7 13 7 13s7-7.75 7-13a7 7 0 0 0-7-7zm0 9.5A2.5 2.5 0 1 1 12 6.5a2.5 2.5 0 0 1 0 5z');
    path.setAttribute('fill', 'currentColor');
    svg.appendChild(path);
    return svg;
  }

  function build(script) {
    var lat = parseCoord(script, 'data-hl-lat', -90, 90);
    var lon = parseCoord(script, 'data-hl-lon', -180, 180);
    if (lat == null || lon == null) return;

    var title = script.getAttribute('data-hl-title') || 'Find us';
    var address = script.getAttribute('data-hl-address') || '';

    injectStyles();

    var root = el('div', PREFIX);

    var map = el('iframe', PREFIX + '__map');
    map.src = 'https://www.openstreetmap.org/export/embed.html?bbox=' +
      (lon - BBOX_SPAN) + '%2C' + (lat - BBOX_SPAN) + '%2C' +
      (lon + BBOX_SPAN) + '%2C' + (lat + BBOX_SPAN) +
      '&layer=mapnik&marker=' + lat + '%2C' + lon;
    map.setAttribute('loading', 'lazy');
    map.setAttribute('referrerpolicy', 'no-referrer-when-downgrade');
    map.title = 'Map of ' + (address || title);
    root.appendChild(map);

    var body = el('div', PREFIX + '__body');
    body.appendChild(el('h3', PREFIX + '__title', title));

    if (address) {
      var addr = el('address', PREFIX + '__address');
      address.split(/\n+/).forEach(function (line, i) {
        if (i > 0) addr.appendChild(document.createElement('br'));
        addr.appendChild(document.createTextNode(line));
      });
      body.appendChild(addr);

      var directions = el('a', PREFIX + '__directions');
      directions.appendChild(pinSvg());
      directions.appendChild(document.createTextNode('Get directions'));
      directions.href = 'https://www.google.com/maps/dir/?api=1&destination=' +
        encodeURIComponent(address.replace(/\s*\n\s*/g, ', '));
      directions.target = '_blank';
      directions.rel = 'noopener';
      body.appendChild(directions);
    }

    root.appendChild(body);

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/office-map-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 office-map-card] Could not locate its own script tag. Nothing rendered.');
      return;
    }
    build(script);
  }

  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-lat(required)Latitude of your office, used to center the map and place the marker (e.g. "35.2271").
data-hl-lon(required)Longitude of your office, used to center the map and place the marker (e.g. "-80.8431").
data-hl-title"Find us"Heading shown above the address in the card body.
data-hl-address(none)Street address shown under the title and used as the destination for the "Get directions" link; use newlines for multi-line addresses.

Frequently asked questions

How do I install the Office Map Card?

Paste the script tag where you want the card to appear — it renders inline right after the tag. Set data-hl-lat and data-hl-lon to your office coordinates and data-hl-address to your street address.

Can I change the heading or hide the directions link?

Yes — set data-hl-title to any heading you like. The address block and "Get directions" link only appear when data-hl-address is set, so omitting it leaves a clean map card with just a title.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JS file with no dependencies. Paste the embed snippet into any HTML block, code injection area, or custom liquid section and it works out of the box.

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