Live: try it with the mouse and the keyboard (Tab, arrows, Escape).

Tabs UI component for websites and web apps, shown in Fernway, a fictional productivity product. Shows a tab list with a count badge and a disabled tab, the selected panel, and every tab state (default, hover, focus-visible, selected, disabled). Editorial style: warm paper tones, Instrument Serif headlines with Inter body text and tabular figures, square corners. Comes with copyable HTML, CSS and a few lines of plain JavaScript (keyboard support and ARIA per the WAI-ARIA Authoring Practices), a live demo, and a DESIGN.md with light and dark colour tokens.
#fffbf6 · surface #f5ebde · text #231b0e · muted #6d6151 · primary #a63930 · accent #00859c#1a140a · surface #2d2418 · text #f3eee8 · muted #baafa0 · primary #ee8578 · accent #62d4efPaste the HTML where the component goes, the CSS into your stylesheet and the script before </body>. The tokens block holds the light and dark colours; swap in your own.
<div class="mgd-tabs">
<div class="mgd-tabs__list" role="tablist" aria-label="Q4 launch project">
<button class="mgd-tabs__tab" type="button" role="tab" id="tab-overview" aria-controls="panel-overview" aria-selected="true" tabindex="0">Overview</button>
<button class="mgd-tabs__tab" type="button" role="tab" id="tab-tasks" aria-controls="panel-tasks" aria-selected="false" tabindex="-1">Tasks <span class="mgd-tabs__count">12</span></button>
<button class="mgd-tabs__tab" type="button" role="tab" id="tab-files" aria-controls="panel-files" aria-selected="false" tabindex="-1">Files</button>
<button class="mgd-tabs__tab" type="button" role="tab" id="tab-activity" aria-controls="panel-activity" aria-selected="false" tabindex="-1">Activity</button>
<button class="mgd-tabs__tab" type="button" role="tab" id="tab-billing" aria-controls="panel-billing" aria-selected="false" tabindex="-1" aria-disabled="true">Billing</button>
</div>
<div class="mgd-tabs__panel" role="tabpanel" id="panel-overview" aria-labelledby="tab-overview" tabindex="0"><h3>Q4 launch</h3><p>Ship the new onboarding to every Fernway workspace by 12 December.</p><ul class="mgd-tabs__rows"><li>Owner<span>Mara Okafor</span></li><li>Due<span>12 Dec</span></li><li>Progress<span>7 of 12 tasks</span></li></ul></div>
<div class="mgd-tabs__panel" role="tabpanel" id="panel-tasks" aria-labelledby="tab-tasks" tabindex="0" hidden><h3>12 open tasks</h3><p>Sorted by due date.</p><ul class="mgd-tabs__rows"><li>Write welcome email copy<span>Teodora · Fri</span></li><li>Record the 60-second tour<span>Sam · Mon</span></li><li>QA the invite flow on mobile<span>Lin · Tue</span></li></ul></div>
<div class="mgd-tabs__panel" role="tabpanel" id="panel-files" aria-labelledby="tab-files" tabindex="0" hidden><h3>Files</h3><p>4 files, 18 MB.</p><ul class="mgd-tabs__rows"><li>onboarding-flow.fig<span>12 MB</span></li><li>launch-brief.pdf<span>2 MB</span></li></ul></div>
<div class="mgd-tabs__panel" role="tabpanel" id="panel-activity" aria-labelledby="tab-activity" tabindex="0" hidden><h3>Activity</h3><p>Latest first.</p><ul class="mgd-tabs__rows"><li>Jonah moved 3 tasks to Done<span>2h ago</span></li><li>Priya commented on the tour script<span>5h ago</span></li></ul></div>
<div class="mgd-tabs__panel" role="tabpanel" id="panel-billing" aria-labelledby="tab-billing" tabindex="0" hidden><h3>Billing</h3><p>Only workspace admins can see billing.</p></div>
</div>/* Tokens (light + dark) */
:root{--bg:#fffbf6;--surface:#f5ebde;--border:#d5cabb;--text:#231b0e;--muted:#6d6151;--primary:#a63930;--on-primary:#ffffff;--accent:#00859c;--font-h:"Instrument Serif",Georgia,serif;--font-b:"Inter",system-ui,sans-serif;--hw:400;
--c-bw:1px;--c-line:var(--text);--c-r:2px;--c-pill:2px;--c-sh:none;--c-pop:0 10px 30px rgb(0 0 0 / .12);--c-tt:none;--c-ls:0;--c-fw:500;--c-fs:14px;--c-ring:2px solid var(--text);--c-ind:1px;color-scheme:light}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#1a140a;--surface:#2d2418;--border:#443b2e;--text:#f3eee8;--muted:#baafa0;--primary:#ee8578;--on-primary:#111111;--accent:#62d4ef;color-scheme:dark}
}
:root[data-theme="dark"]{--bg:#1a140a;--surface:#2d2418;--border:#443b2e;--text:#f3eee8;--muted:#baafa0;--primary:#ee8578;--on-primary:#111111;--accent:#62d4ef;color-scheme:dark}
/* Component */
.mgd-sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.mgd-tabs{max-width:760px}
.mgd-tabs__list{display:flex;gap:4px;overflow-x:auto;scrollbar-width:none;border-bottom:var(--c-bw) solid var(--text)}
.mgd-tabs__tab{flex:none;display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:0 16px;border:0;background:none;color:var(--muted);font:var(--c-fw) var(--c-fs)/1 var(--font-b);text-transform:var(--c-tt);letter-spacing:var(--c-ls);cursor:pointer;border-radius:var(--c-r) var(--c-r) 0 0;margin-bottom:calc(-1 * var(--c-bw));transition:color .12s,background-color .12s,box-shadow .12s}
.mgd-tabs__tab:hover,.mgd-tabs__tab.is-hover{color:var(--text);background:color-mix(in oklab,var(--primary) 7%,transparent)}
.mgd-tabs__tab:focus-visible,.mgd-tabs__tab.is-focus{outline:var(--c-ring);outline-offset:-2px}
.mgd-tabs__tab[aria-selected="true"],.mgd-tabs__tab.is-on{color:var(--text);box-shadow:inset 0 calc(-1 * var(--c-ind)) 0 var(--text)}
.mgd-tabs__tab[aria-disabled="true"],.mgd-tabs__tab.is-off{opacity:.45;cursor:not-allowed;background:none}
.mgd-tabs__count{min-width:22px;height:20px;padding:0 6px;border-radius:999px;display:inline-grid;place-items:center;font:600 11px/1 var(--font-b);letter-spacing:0;background:color-mix(in oklab,var(--accent) 18%,var(--surface));color:var(--text)}
.mgd-tabs__panel{padding:22px 2px 4px}
.mgd-tabs__panel:focus-visible{outline:var(--c-ring);outline-offset:4px;border-radius:4px}
.mgd-tabs__panel h3{margin:0 0 6px;font:var(--hw) 20px/1.2 var(--font-h)}
.mgd-tabs__panel p{margin:0 0 14px;color:var(--muted)}
.mgd-tabs__rows{list-style:none;margin:0;padding:0;display:grid;gap:8px}
.mgd-tabs__rows li{display:flex;justify-content:space-between;gap:12px;padding:12px 14px;border:var(--c-bw) solid var(--c-line);border-radius:var(--c-r);background:var(--surface);box-shadow:var(--c-sh)}
.mgd-tabs__rows span{color:var(--muted);font-size:13px;font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion:reduce){.mgd-tabs,.mgd-tabs *{transition:none!important;animation-duration:2.4s!important}}document.querySelectorAll(".mgd-tabs [role=tablist]").forEach(function (list) {
var tabs = [].slice.call(list.querySelectorAll("[role=tab]"));
var usable = function () { return tabs.filter(function (t) { return t.getAttribute("aria-disabled") !== "true"; }); };
function select(tab) {
if (tab.getAttribute("aria-disabled") === "true") return;
tabs.forEach(function (t) {
var on = t === tab;
t.setAttribute("aria-selected", on); t.tabIndex = on ? 0 : -1;
document.getElementById(t.getAttribute("aria-controls")).hidden = !on;
});
tab.focus();
}
list.addEventListener("click", function (e) { var t = e.target.closest("[role=tab]"); if (t) select(t); });
list.addEventListener("keydown", function (e) {
var u = usable(), i = u.indexOf(document.activeElement), next = null;
if (e.key === "ArrowRight") next = u[(i + 1) % u.length];
else if (e.key === "ArrowLeft") next = u[(i - 1 + u.length) % u.length];
else if (e.key === "Home") next = u[0];
else if (e.key === "End") next = u[u.length - 1];
if (next) { e.preventDefault(); select(next); }
});
});## Component: Tabs
Switch between related panels without leaving the page. Arrow keys move between tabs; the panel follows.
- **Pattern:** WAI-ARIA Authoring Practices, Tabs (https://www.w3.org/WAI/ARIA/apg/patterns/tabs/)
- **Roles and attributes:** role="tablist" with an aria-label; each tab is a button with role="tab", aria-selected and aria-controls; each panel has role="tabpanel", aria-labelledby and tabindex="0". Only the selected tab is in the tab order (roving tabindex).
- **States drawn:** default, hover, focus-visible, selected, disabled
- **Focus:** a visible `:focus-visible` outline (2px solid text), never removed
- **Style knobs (editorial):** border 1px, corners 2px, shadow none, labels sentence case
- **Motion:** transitions are short (≤160ms) and switched off under `prefers-reduced-motion: reduce`
- **Code:** HTML + CSS + a few lines of plain JavaScript (no libraries); colours come only from the tokens above, so light and dark both work
### Keyboard
- **Left / Right arrow:** move to the previous / next tab and show its panel (wraps)
- **Home / End:** first / last tab
- **Tab:** leave the tab list and move into the panel
### Licence
Original Made Good Designs code: free to use and adapt in any project, including commercial ones, with no credit needed. Fonts are Google Fonts under their own licences.Add the Made Good Designs library to your assistant once. Then ask for UI references, brand palettes, font pairings and design tokens in any chat.
https://madegooddesigns.com/inspiration/mcp?ref=ui-tabs-uiSettings → Connectors → Add custom connector → paste the URL.
Settings → Apps & Connectors → Advanced → Developer mode → Create → paste the URL.
One click, then confirm in Cursor.
One click, then confirm in VS Code (Copilot agent mode).
claude mcp add --transport http made-good-designs https://madegooddesigns.com/inspiration/mcp?ref=ui-tabs-ui
gemini mcp add --transport http made-good-designs https://madegooddesigns.com/inspiration/mcp?ref=ui-tabs-ui
Add this to the app's MCP config (Windsurf: mcp_config.json).
Fonts: Instrument Serif + Inter (Google Fonts)
Prompt copied: paste it into the chat that just opened.
Writing about this component?
search_ui_screens (e.g. “billing settings, editorial style”), get_ui_flow for a whole journey, and get_ui_screen_code to pull the HTML and DESIGN.md straight into your project.