⌇ Seamble

The same guide AI assistants read through Seamble's MCP server. View as Markdown

Example: tabs

The next MAX_TABS sections become tabs, named by each section's first heading, with arrow-key navigation. It hides Squarespace sections, so it restores them in edit mode. Copy onEditMode from Squarespace into the same IIFE. Put the constants and init in the starter in place of its ID line and init. Reads: no attributes.

const ID = "__ID__";
const MAX_TABS = 3;

function init(el) {
  const panels = [];
  for (let s = el.closest(".page-section")?.nextElementSibling; s && panels.length < MAX_TABS; s = s.nextElementSibling)
    if (s.matches(".page-section")) panels.push(s);
  if (!panels.length) return void (el.innerHTML = `<p class="sb-empty">Add sections below this block. Each becomes a tab.</p>`);
  el.innerHTML = `<div class="sb-tabs" role="tablist"></div>`;
  const list = el.firstElementChild, added = [];
  const tabs = panels.map((p, i) => {
    if (!p.id) { p.id = `sb-${ID}-panel-${i}`; added.push(p); }
    const b = document.createElement("button");
    Object.assign(b, { type: "button", id: `sb-${ID}-tab-${i}`, className: "sb-tab" });
    b.setAttribute("role", "tab"); b.setAttribute("aria-controls", p.id);
    b.textContent = p.querySelector("h1, h2, h3, h4")?.textContent.trim().slice(0, 32) || `Tab ${i + 1}`;
    p.setAttribute("role", "tabpanel"); p.setAttribute("aria-labelledby", b.id);
    p.dataset.sbPanelOf = ID;
    return list.appendChild(b);
  });
  const show = (i) => tabs.forEach((t, j) => {
    t.setAttribute("aria-selected", String(i === j));
    t.tabIndex = i === j ? 0 : -1;
    t.classList.toggle("is-active", i === j);
    panels[j].hidden = i !== j;
  });
  list.addEventListener("click", (e) => { const i = tabs.indexOf(e.target.closest(".sb-tab")); if (i >= 0) show(i); });
  list.addEventListener("keydown", (e) => {
    const step = { ArrowRight: 1, ArrowLeft: -1 }[e.key];
    if (!step) return;
    const i = (tabs.indexOf(document.activeElement) + step + tabs.length) % tabs.length;
    show(i); tabs[i].focus();
  });
  show(0);
  onEditMode(() => {
    for (const p of panels) {
      p.hidden = false; p.removeAttribute("role"); p.removeAttribute("aria-labelledby"); delete p.dataset.sbPanelOf;
    }
    for (const p of added) p.removeAttribute("id");
    el.innerHTML = "";
  });
}
[data-sb-panel-of="__ID__"][hidden] { display: none !important; }
[data-seamble-id="__ID__"] .sb-tabs { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; padding-bottom: 8px; border-bottom: 2px solid color-mix(in srgb, currentColor 15%, transparent); }
[data-seamble-id="__ID__"] .sb-tab { font: inherit; color: inherit; background: none; border: 0; border-radius: 5px; padding: 8px 16px; min-height: 44px; cursor: pointer; }
[data-seamble-id="__ID__"] .sb-tab:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
[data-seamble-id="__ID__"] .sb-tab.is-active { background: var(--sb-accent, var(--primaryButtonBackgroundColor, #111)); color: var(--sb-on-accent, var(--siteBackgroundColor, #fff)); }
[data-seamble-id="__ID__"] .sb-empty { opacity: 0.6; font-size: 0.9em; }