Business & Operations

Free Staff Directory Widget

A filterable staff grid with photos, roles, and department buttons.

A clean staff directory that turns a simple JSON list into a searchable-by-department team grid with photos, roles, and optional email links. It is ideal for small businesses, agencies, and organizations that want a professional "meet the team" section without a CMS. 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/staff-directory.js" data-hl-config='[{"name":"Maya Chen","role":"Operations Manager","department":"Operations","photo":"https://placehold.co/144x144/f97316/ffffff?text=MC","email":"[email protected]"},{"name":"Jordan Lee","role":"Logistics Coordinator","department":"Operations","photo":"https://placehold.co/144x144/06b6d4/ffffff?text=JL"},{"name":"Priya Patel","role":"Lead Engineer","department":"Engineering","photo":"https://placehold.co/144x144/111827/ffffff?text=PP","email":"[email protected]"},{"name":"Sam Ortiz","role":"Frontend Developer","department":"Engineering","photo":"https://placehold.co/144x144/6b7280/ffffff?text=SO"},{"name":"Ava Nguyen","role":"Sales Director","department":"Sales","photo":"https://placehold.co/144x144/0ea5e9/ffffff?text=AN","email":"[email protected]"},{"name":"Marcus Hill","role":"Account Executive","department":"Sales","photo":"https://placehold.co/144x144/f59e0b/ffffff?text=MH"}]' data-hl-title="Meet the Team" async></script>
<!-- Free Staff Directory widget by haynes lab — https://hayneslab.dev/tools/widgets/staff-directory -->
Inline (self-contained)html
<!-- Free Staff Directory widget by haynes lab — https://hayneslab.dev/tools/widgets/staff-directory -->
<script data-hl-config='[{"name":"Maya Chen","role":"Operations Manager","department":"Operations","photo":"https://placehold.co/144x144/f97316/ffffff?text=MC","email":"[email protected]"},{"name":"Jordan Lee","role":"Logistics Coordinator","department":"Operations","photo":"https://placehold.co/144x144/06b6d4/ffffff?text=JL"},{"name":"Priya Patel","role":"Lead Engineer","department":"Engineering","photo":"https://placehold.co/144x144/111827/ffffff?text=PP","email":"[email protected]"},{"name":"Sam Ortiz","role":"Frontend Developer","department":"Engineering","photo":"https://placehold.co/144x144/6b7280/ffffff?text=SO"},{"name":"Ava Nguyen","role":"Sales Director","department":"Sales","photo":"https://placehold.co/144x144/0ea5e9/ffffff?text=AN","email":"[email protected]"},{"name":"Marcus Hill","role":"Account Executive","department":"Sales","photo":"https://placehold.co/144x144/f59e0b/ffffff?text=MH"}]' data-hl-title="Meet the Team">
/*!
 * haynes lab — Staff Directory widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/staff-directory
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-staff-directory';
  var STYLE_ID = 'hlw-staff-directory-styles';

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

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 960px; margin: 0 auto; padding: 28px 24px 40px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 16px; line-height: 1.5; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; }' +
      '.' + PREFIX + '__title { margin: 0 0 18px; font-size: 22px; font-weight: 700; color: #111827; text-align: center; }' +
      '.' + PREFIX + '__filters { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-bottom: 22px; }' +
      '.' + PREFIX + '__filter { border: 1px solid #e5e7eb; border-radius: 999px; background: #ffffff;' +
      ' color: #4b5563; font-size: 13px; font-weight: 600; padding: 6px 14px; cursor: pointer; }' +
      '.' + PREFIX + '__filter:hover { border-color: #9ca3af; color: #111827; }' +
      '.' + PREFIX + '__filter[aria-pressed="true"] { background: #111827; border-color: #111827; color: #ffffff; }' +
      '.' + PREFIX + '__filter:focus-visible { outline: 2px solid #111827; outline-offset: 2px; }' +
      '.' + PREFIX + '__grid { display: grid; grid-template-columns: repeat(var(--hlw-cols, 3), 1fr); gap: 16px; }' +
      '.' + PREFIX + '__card { border: 1px solid #e5e7eb; border-radius: 10px; padding: 20px 16px;' +
      ' text-align: center; background: #ffffff; }' +
      '.' + PREFIX + '__photo { width: 72px; height: 72px; border-radius: 50%; object-fit: cover;' +
      ' display: block; margin: 0 auto 12px; background: #f3f4f6; }' +
      '.' + PREFIX + '__initials { width: 72px; height: 72px; border-radius: 50%; margin: 0 auto 12px;' +
      ' display: flex; align-items: center; justify-content: center; background: #f3f4f6;' +
      ' color: #6b7280; font-size: 24px; font-weight: 700; }' +
      '.' + PREFIX + '__name { font-weight: 600; font-size: 15px; color: #111827; }' +
      '.' + PREFIX + '__role { font-size: 13px; color: #6b7280; margin-top: 2px; }' +
      '.' + PREFIX + '__dept { display: inline-block; margin-top: 8px; font-size: 11px; font-weight: 600;' +
      ' color: #4b5563; background: #f3f4f6; border-radius: 999px; padding: 2px 10px; }' +
      '.' + PREFIX + '__email { display: inline-block; margin-top: 8px; font-size: 13px; color: #2563eb;' +
      ' text-decoration: none; }' +
      '.' + PREFIX + '__email:hover { text-decoration: underline; }' +
      '.' + 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: 900px) { .' + PREFIX + '__grid { grid-template-columns: repeat(2, 1fr); } }' +
      '@media (max-width: 560px) { .' + PREFIX + ' { padding: 22px 16px 38px; }' +
      ' .' + PREFIX + '__grid { grid-template-columns: 1fr; } }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function parseStaff(script) {
    var raw = script.getAttribute('data-hl-config');
    if (!raw) {
      console.warn('[haynes lab staff-directory] Missing required data-hl-config attribute (JSON array of {name, role, department, photo?, email?}). Nothing rendered.');
      return null;
    }
    var items;
    try {
      items = JSON.parse(raw);
    } catch (e) {
      console.warn('[haynes lab staff-directory] data-hl-config is not valid JSON. Nothing rendered.', e);
      return null;
    }
    if (!Array.isArray(items)) {
      console.warn('[haynes lab staff-directory] data-hl-config must be a JSON array. Nothing rendered.');
      return null;
    }
    var valid = items.filter(function (p) {
      return p && typeof p.name === 'string' && p.name &&
        typeof p.role === 'string' && p.role &&
        typeof p.department === 'string' && p.department;
    });
    if (!valid.length) {
      console.warn('[haynes lab staff-directory] data-hl-config has no valid staff members (each needs at least "name", "role", and "department"). Nothing rendered.');
      return null;
    }
    return valid;
  }

  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 initialsOf(name) {
    var parts = name.trim().split(/\s+/);
    var initials = parts[0].charAt(0);
    if (parts.length > 1) initials += parts[parts.length - 1].charAt(0);
    return initials.toUpperCase();
  }

  function build(script) {
    var staff = parseStaff(script);
    if (!staff) return;

    var title = script.getAttribute('data-hl-title');
    if (title == null) title = 'Our Team';
    var columns = parseInt(script.getAttribute('data-hl-columns'), 10);
    if (isNaN(columns) || columns < 1 || columns > 6) columns = 3;

    var departments = [];
    staff.forEach(function (p) {
      if (departments.indexOf(p.department) === -1) departments.push(p.department);
    });

    injectStyles();

    var root = el('div', PREFIX);
    root.setAttribute('role', 'region');
    root.setAttribute('aria-label', title || 'Staff directory');
    root.style.setProperty('--hlw-cols', String(columns));

    if (title) root.appendChild(el('h2', PREFIX + '__title', title));

    var grid = el('div', PREFIX + '__grid');
    var cards = staff.map(function (p) {
      var card = el('div', PREFIX + '__card');
      if (typeof p.photo === 'string' && p.photo) {
        var img = el('img', PREFIX + '__photo');
        img.src = p.photo;
        img.alt = p.name;
        img.loading = 'lazy';
        img.width = 72;
        img.height = 72;
        card.appendChild(img);
      } else {
        card.appendChild(el('div', PREFIX + '__initials', initialsOf(p.name)));
      }
      card.appendChild(el('div', PREFIX + '__name', p.name));
      card.appendChild(el('div', PREFIX + '__role', p.role));
      card.appendChild(el('span', PREFIX + '__dept', p.department));
      if (typeof p.email === 'string' && p.email) {
        var email = el('a', PREFIX + '__email', p.email);
        email.href = 'mailto:' + p.email;
        card.appendChild(document.createElement('br'));
        card.appendChild(email);
      }
      grid.appendChild(card);
      return card;
    });

    if (departments.length > 1) {
      var filters = el('div', PREFIX + '__filters');
      filters.setAttribute('role', 'group');
      filters.setAttribute('aria-label', 'Filter by department');

      function applyFilter(activeBtn, dept) {
        Array.prototype.forEach.call(filters.children, function (b) {
          b.setAttribute('aria-pressed', b === activeBtn ? 'true' : 'false');
        });
        staff.forEach(function (p, i) {
          cards[i].style.display = (!dept || p.department === dept) ? '' : 'none';
        });
      }

      function makeFilter(label, dept) {
        var btn = el('button', PREFIX + '__filter', label);
        btn.type = 'button';
        btn.setAttribute('aria-pressed', 'false');
        btn.addEventListener('click', function () { applyFilter(btn, dept); });
        filters.appendChild(btn);
        return btn;
      }

      var allBtn = makeFilter('All', null);
      departments.forEach(function (d) { makeFilter(d, d); });
      allBtn.setAttribute('aria-pressed', 'true');
      root.appendChild(filters);
    }

    root.appendChild(grid);

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/staff-directory';
    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 staff-directory] 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)JSON array of staff objects with name, role, department, and optional photo and email fields.
data-hl-title"Our Team"Heading shown above the directory; set to an empty string to hide it.
data-hl-columns"3"Number of desktop grid columns (1-6); collapses automatically on smaller screens.

Frequently asked questions

How do I install the Staff Directory?

Paste the script snippet where you want the directory to appear — it renders inline right after the tag using the staff list in your data-hl-config attribute.

How do I change the heading or number of columns?

Set data-hl-title to any heading text (or an empty string to hide it) and data-hl-columns to a number from 1 to 6 for the desktop grid; the layout collapses to fewer columns on tablets and phones automatically.

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