Business & Operations

Free Org Chart Widget

Render a clean top-down org chart from a simple JSON snippet.

A lightweight org chart widget that turns a nested JSON structure of names, roles, and reports into a clean top-down hierarchy with pure CSS connector lines. It is ideal for company about pages, team directories, and intranets, and it scrolls horizontally on small screens so wide charts stay usable. Install it with a single script tag — no dependencies, no accounts.

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/org-chart.js" data-hl-config='{"name":"Alex Morgan","role":"CEO","children":[{"name":"Jamie Chen","role":"VP Engineering","children":[{"name":"Priya Patel","role":"Frontend Lead"},{"name":"Sam Rivera","role":"Backend Lead"}]},{"name":"Taylor Brooks","role":"VP Operations","children":[{"name":"Jordan Lee","role":"People Ops"},{"name":"Casey Kim","role":"Finance Manager"}]}]}' async></script>
<!-- Free Org Chart widget by haynes lab — https://hayneslab.dev/tools/widgets/org-chart -->
Inline (self-contained)html
<!-- Free Org Chart widget by haynes lab — https://hayneslab.dev/tools/widgets/org-chart -->
<script data-hl-config='{"name":"Alex Morgan","role":"CEO","children":[{"name":"Jamie Chen","role":"VP Engineering","children":[{"name":"Priya Patel","role":"Frontend Lead"},{"name":"Sam Rivera","role":"Backend Lead"}]},{"name":"Taylor Brooks","role":"VP Operations","children":[{"name":"Jordan Lee","role":"People Ops"},{"name":"Casey Kim","role":"Finance Manager"}]}]}'>
/*!
 * haynes lab — Org Chart widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/org-chart
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-org-chart';
  var STYLE_ID = 'hlw-org-chart-styles';

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

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 100%; padding: 28px 16px 36px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 15px; line-height: 1.4; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; }' +
      '.' + PREFIX + '__scroll { overflow-x: auto; padding-bottom: 4px; }' +
      '.' + PREFIX + '__tree, .' + PREFIX + '__level { list-style: none; margin: 0;' +
      ' display: flex; justify-content: center; }' +
      '.' + PREFIX + '__tree { padding: 0; width: max-content; min-width: 100%; margin: 0 auto; }' +
      '.' + PREFIX + '__level { position: relative; padding: 24px 0 0; }' +
      '.' + PREFIX + '__level::before { content: ""; position: absolute; top: 0; left: 50%;' +
      ' width: 0; height: 24px; border-left: 2px solid #d1d5db; }' +
      '.' + PREFIX + '__item { list-style: none; display: flex; flex-direction: column;' +
      ' align-items: center; position: relative; padding: 24px 12px 0; }' +
      '.' + PREFIX + '__item::before, .' + PREFIX + '__item::after { content: "";' +
      ' position: absolute; top: 0; right: 50%; width: 50%; height: 24px;' +
      ' border-top: 2px solid #d1d5db; }' +
      '.' + PREFIX + '__item::after { right: auto; left: 50%; border-left: 2px solid #d1d5db; }' +
      '.' + PREFIX + '__item:only-child::before, .' + PREFIX + '__item:only-child::after { display: none; }' +
      '.' + PREFIX + '__item:only-child { padding-top: 0; }' +
      '.' + PREFIX + '__item:first-child::before, .' + PREFIX + '__item:last-child::after { border: 0 none; }' +
      '.' + PREFIX + '__item:last-child::before { border-right: 2px solid #d1d5db; border-radius: 0 6px 0 0; }' +
      '.' + PREFIX + '__item:first-child::after { border-radius: 6px 0 0 0; }' +
      '.' + PREFIX + '__node { min-width: 140px; max-width: 200px; padding: 10px 14px;' +
      ' background: #ffffff; border: 1px solid #e5e7eb; border-radius: 10px;' +
      ' text-align: center; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); }' +
      '.' + PREFIX + '__node--root { border-top: 3px solid var(--hlw-accent); }' +
      '.' + PREFIX + '__name { font-weight: 600; font-size: 14px; color: #111827; }' +
      '.' + PREFIX + '__role { font-size: 12px; color: #6b7280; margin-top: 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: 560px) { .' + PREFIX + ' { padding: 20px 10px 32px; }' +
      ' .' + PREFIX + '__node { min-width: 120px; } }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function normalize(node) {
    if (!node || typeof node.name !== 'string' || !node.name) return null;
    var clean = {
      name: node.name,
      role: typeof node.role === 'string' ? node.role : '',
      children: []
    };
    if (Array.isArray(node.children)) {
      node.children.forEach(function (child) {
        var n = normalize(child);
        if (n) clean.children.push(n);
      });
    }
    return clean;
  }

  function parseConfig(script) {
    var raw = script.getAttribute('data-hl-config');
    if (!raw) {
      console.warn('[haynes lab org-chart] Missing required data-hl-config attribute (nested JSON of {name, role, children}). Nothing rendered.');
      return null;
    }
    var data;
    try {
      data = JSON.parse(raw);
    } catch (e) {
      console.warn('[haynes lab org-chart] data-hl-config is not valid JSON. Nothing rendered.', e);
      return null;
    }
    var root = normalize(data);
    if (!root) {
      console.warn('[haynes lab org-chart] data-hl-config must be an object with at least a "name" string. Nothing rendered.');
      return null;
    }
    return root;
  }

  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 buildItem(node, isRoot) {
    var li = el('li', PREFIX + '__item');
    var card = el('div', PREFIX + '__node' + (isRoot ? ' ' + PREFIX + '__node--root' : ''));
    card.appendChild(el('div', PREFIX + '__name', node.name));
    if (node.role) card.appendChild(el('div', PREFIX + '__role', node.role));
    li.appendChild(card);
    if (node.children.length) {
      var ul = el('ul', PREFIX + '__level');
      node.children.forEach(function (child) {
        ul.appendChild(buildItem(child, false));
      });
      li.appendChild(ul);
    }
    return li;
  }

  function build(script) {
    var data = parseConfig(script);
    if (!data) return;

    var accent = script.getAttribute('data-hl-accent') || '#f97316';

    injectStyles();

    var root = el('div', PREFIX);
    root.setAttribute('role', 'region');
    root.setAttribute('aria-label', 'Organization chart');
    root.style.setProperty('--hlw-accent', accent);

    var scroller = el('div', PREFIX + '__scroll');
    var tree = el('ul', PREFIX + '__tree');
    tree.appendChild(buildItem(data, true));
    scroller.appendChild(tree);
    root.appendChild(scroller);

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/org-chart';
    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 org-chart] 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-config(required)Nested JSON object for the chart: {"name","role","children":[...]}, where each child follows the same shape.
data-hl-accent"#f97316"Highlight color used on the top (root) node of the chart.

Frequently asked questions

How do I install the Org Chart?

Paste the script snippet where you want the chart to appear — it renders inline right after the tag and builds the hierarchy from the JSON in data-hl-config.

How do I change the people and the colors?

Edit the nested JSON in data-hl-config — each node takes a "name", an optional "role", and an optional "children" array. Set data-hl-accent to change the root node highlight color.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JS file with no dependencies, so it works on WordPress, Shopify, Squarespace, or any site that can embed a script tag.

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