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.
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.
<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 --><!-- 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.
| Attribute | Default | Description |
|---|---|---|
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
Team Member Grid
Showcase your team with photos, roles, bios, and LinkedIn links in a clean grid.
View toolBusiness & OperationsOrg Chart
Render a clean top-down org chart from a simple JSON snippet.
View toolBusiness & OperationsJob Openings List
Show your open roles anywhere on your site with a simple JSON config.
View toolNeed 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