Accessible Components
GPT 5.6 terra Pro · full

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 21,716 bytes · SHA-256 a581b8fd9f26
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A11y Patterns — Component Showcase</title>
<style>
:root{--ink:#18232f;--muted:#667584;--line:#d9e1e7;--surface:#fff;--canvas:#f7f9fb;--navy:#123a5a;--teal:#087e8b;--teal-pale:#e6f5f5;--gold:#d49127;--radius:14px;--s1:6px;--s2:12px;--s3:20px;--s4:32px;--s5:52px;--speed:220ms;--shadow:0 16px 42px rgba(20,47,68,.14)}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;color:var(--ink);background:var(--canvas);font:16px/1.55 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}button,input{font:inherit}button{color:inherit}button:not(:disabled),[role=tab]{cursor:pointer}a{color:var(--navy)}
:focus-visible{outline:3px solid var(--gold);outline-offset:3px}.skip-link{position:absolute;top:10px;left:10px;z-index:100;transform:translateY(-160%);padding:9px 14px;border-radius:7px;background:var(--ink);color:#fff;text-decoration:none;font-weight:750;transition:transform var(--speed)}.skip-link:focus{transform:translateY(0)}
.shell{display:grid;grid-template-columns:255px minmax(0,1fr);max-width:1440px;margin:auto;min-height:100vh}.sidebar{position:sticky;top:0;align-self:start;height:100vh;padding:36px 25px;border-right:1px solid var(--line);background:#fbfcfd}.brand{display:flex;gap:10px;align-items:center;margin:0 0 37px;font-size:18px;font-weight:800;letter-spacing:-.02em}.brand-mark{width:29px;height:29px;border-radius:8px;background:var(--teal);box-shadow:9px 9px 0 #b5e2e2}.eyebrow{text-transform:uppercase;letter-spacing:.12em;font-size:11px;font-weight:800;color:var(--teal)}nav ul{padding:0;margin:13px 0;list-style:none}nav a{display:block;padding:8px 10px;margin:3px 0;border-radius:7px;text-decoration:none;color:var(--muted);font-size:14px;font-weight:650}nav a:hover,nav a:focus-visible{background:var(--teal-pale);color:var(--navy)}.sidebar-note{position:absolute;bottom:28px;left:25px;right:25px;color:var(--muted);font-size:12px}.content{max-width:1010px;padding:62px 7vw 90px}header{padding-bottom:55px;border-bottom:1px solid var(--line)}h1{max-width:720px;margin:10px 0 15px;font-size:clamp(37px,5vw,59px);line-height:1.07;letter-spacing:-.055em}.lede{max-width:690px;margin:0;color:var(--muted);font-size:18px}.section{padding:64px 0 25px}.section-head{display:flex;align-items:baseline;justify-content:space-between;gap:20px;margin-bottom:21px}.section h2{margin:0;font-size:27px;letter-spacing:-.035em}.number{color:var(--teal);font:800 13px ui-monospace,monospace}.card{padding:clamp(20px,4vw,38px);border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);box-shadow:0 4px 18px rgba(37,62,85,.035)}.demo-label{margin:0 0 22px;color:var(--muted);font-size:13px}.key-contract{display:inline-flex;align-items:center;margin-top:25px;padding:6px 10px;border-radius:5px;background:#f0f4f6;color:#526575;font:700 12px ui-monospace,SFMono-Regular,monospace}.btn{min-height:42px;padding:9px 15px;border:1px solid transparent;border-radius:8px;background:var(--navy);color:#fff;font-weight:750;letter-spacing:.01em}.btn:hover{background:#0e304b}.btn-secondary{background:#fff;border-color:#b8c5ce;color:var(--ink)}.btn-secondary:hover{background:#f4f7f8}.status{min-height:25px;margin:13px 0 0;color:var(--teal);font-size:14px;font-weight:650}
/* Modal */.modal-backdrop{position:fixed;inset:0;z-index:20;display:grid;place-items:center;padding:22px;background:rgba(14,30,43,.58);opacity:0;visibility:hidden;transition:opacity var(--speed),visibility var(--speed)}.modal-backdrop.is-open{opacity:1;visibility:visible}.modal{width:min(500px,100%);padding:30px;border-radius:15px;background:#fff;box-shadow:var(--shadow);transform:translateY(10px);transition:transform var(--speed)}.modal-backdrop.is-open .modal{transform:none}.modal h3{margin:0 0 10px;font-size:23px;letter-spacing:-.025em}.modal p{color:var(--muted)}.modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:26px}
/* Menu */.menu-wrap{position:relative;display:inline-block}.menu-button{display:flex;align-items:center;gap:10px}.chev{font-size:12px}.menu{position:absolute;z-index:3;top:calc(100% + 7px);left:0;width:224px;margin:0;padding:6px;border:1px solid var(--line);border-radius:10px;background:#fff;box-shadow:var(--shadow);list-style:none}.menu[hidden]{display:none}.menuitem{display:block;width:100%;padding:10px 11px;border:0;border-radius:6px;background:transparent;text-align:left;font-size:14px}.menuitem:hover,.menuitem:focus-visible{background:var(--teal-pale);color:#075966}
/* Tabs */.tablist{display:flex;gap:3px;border-bottom:1px solid var(--line)}[role=tab]{position:relative;padding:11px 14px;border:0;background:transparent;color:var(--muted);font-weight:750}[role=tab][aria-selected=true]{color:var(--navy)}[role=tab][aria-selected=true]::after{position:absolute;right:8px;bottom:-1px;left:8px;height:3px;border-radius:3px 3px 0 0;background:var(--teal);content:""}.tabpanel{min-height:134px;padding:25px 4px 3px}.tabpanel h3{margin:0 0 6px;font-size:20px}.tabpanel p{max-width:650px;margin:0;color:var(--muted)}.text-link{display:inline-block;margin-top:13px;font-weight:700}
/* Combobox */.combo-wrap{max-width:500px}.combo-label{display:block;margin-bottom:7px;font-size:14px;font-weight:750}.combo-input{width:100%;height:45px;padding:9px 12px;border:1px solid #aebdc7;border-radius:8px;color:var(--ink);background:#fff}.combo-input:focus{border-color:var(--teal)}.listbox{max-height:205px;overflow:auto;margin:7px 0 0;padding:5px;border:1px solid var(--line);border-radius:9px;background:#fff;box-shadow:0 9px 22px rgba(31,56,75,.1);list-style:none}.listbox[hidden]{display:none}.option{padding:8px 10px;border-radius:5px;font-size:14px}.option.active,.option:hover{background:var(--teal-pale);color:#075966}.empty{padding:9px 10px;color:var(--muted);font-size:14px}
/* Accordion */.accordion{border-top:1px solid var(--line)}.accordion-item{border-bottom:1px solid var(--line)}.accordion-trigger{display:flex;align-items:center;justify-content:space-between;width:100%;padding:17px 2px;border:0;background:transparent;text-align:left;font-weight:780}.plus{color:var(--teal);font-size:22px;line-height:1;transition:transform var(--speed)}.accordion-trigger[aria-expanded=true] .plus{transform:rotate(45deg)}.accordion-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--speed);overflow:hidden}.accordion-panel.open{grid-template-rows:1fr}.accordion-panel>div{min-height:0;overflow:hidden}.accordion-panel p{margin:0;padding:0 2px 18px;color:var(--muted)}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;transition-duration:1ms!important;animation-duration:1ms!important}}@media(max-width:760px){.shell{display:block}.sidebar{position:relative;height:auto;padding:22px;border-right:0;border-bottom:1px solid var(--line)}.brand{margin-bottom:17px}.sidebar nav ul{display:flex;gap:3px;flex-wrap:wrap}.sidebar nav a{margin:0}.sidebar-note{display:none}.content{padding:43px 24px 70px}.section{padding-top:50px}}
</style>
</head>
<body>
<a class="skip-link" href="#main">Skip to main content</a>
<div class="shell" id="page-shell">
<aside class="sidebar" aria-label="Component navigation"><div class="brand"><span class="brand-mark" aria-hidden="true"></span> A11y Patterns</div><div class="eyebrow">Components</div><nav><ul><li><a href="#modal-demo">Modal dialog</a></li><li><a href="#menu-demo">Dropdown menu</a></li><li><a href="#tabs-demo">Tabs</a></li><li><a href="#combobox-demo">Combobox</a></li><li><a href="#accordion-demo">Accordion</a></li></ul></nav><p class="sidebar-note">Five practical patterns,<br>built for every input.</p></aside>
<main class="content" id="main" tabindex="-1">
<header><div class="eyebrow">Inclusive interface primitives</div><h1>Components that welcome every interaction.</h1><p class="lede">A deliberately small collection of accessible patterns. Each example is live, keyboard-first, and designed to make correct semantics feel natural.</p></header>
<section class="section" id="modal-demo" aria-labelledby="modal-heading"><div class="section-head"><h2 id="modal-heading"><span class="number">01 / </span>Modal dialog</h2></div><div class="card"><p class="demo-label">A modal announces itself, contains focus, and gives focus back when its work is done.</p><button class="btn" id="modal-trigger" type="button" aria-haspopup="dialog">Open confirmation dialog</button><span class="key-contract">Tab trap · Esc close · backdrop close</span></div></section>
<section class="section" id="menu-demo" aria-labelledby="menu-heading"><div class="section-head"><h2 id="menu-heading"><span class="number">02 / </span>Dropdown menu</h2></div><div class="card"><p class="demo-label">An action menu with predictable movement and an explicit return path.</p><div class="menu-wrap"><button class="btn menu-button" id="menu-button" type="button" aria-haspopup="menu" aria-expanded="false" aria-controls="action-menu">Page actions <span class="chev" aria-hidden="true">▼</span></button><ul class="menu" id="action-menu" role="menu" aria-labelledby="menu-button" hidden><li role="none"><button class="menuitem" role="menuitem" type="button">Duplicate draft</button></li><li role="none"><button class="menuitem" role="menuitem" type="button">Share preview</button></li><li role="none"><button class="menuitem" role="menuitem" type="button">Archive page</button></li></ul></div><p class="status" id="menu-status" role="status" aria-live="polite"></p><span class="key-contract">↓↑ navigate · Home End · Enter select · Esc close</span></div></section>
<section class="section" id="tabs-demo" aria-labelledby="tabs-heading"><div class="section-head"><h2 id="tabs-heading"><span class="number">03 / </span>Tabs</h2></div><div class="card"><p class="demo-label">One active stop in the tab order; arrows move between related views.</p><div class="tablist" role="tablist" aria-label="Design system documentation"><button role="tab" id="tab-foundation" aria-selected="true" aria-controls="panel-foundation" tabindex="0">Foundation</button><button role="tab" id="tab-guidance" aria-selected="false" aria-controls="panel-guidance" tabindex="-1">Guidance</button><button role="tab" id="tab-release" aria-selected="false" aria-controls="panel-release" tabindex="-1">Release notes</button></div><div class="tabpanel" role="tabpanel" id="panel-foundation" aria-labelledby="tab-foundation"><h3>Built from semantic ground up</h3><p>Native buttons, labels and landmarks create a stable base before ARIA enhances a pattern.</p><a class="text-link" href="#accordion-demo">Explore disclosure guidance</a></div><div class="tabpanel" role="tabpanel" id="panel-guidance" aria-labelledby="tab-guidance" hidden><h3>Interaction is part of the API</h3><p>Every component documents expected keys, focus movement and how a person exits the experience.</p><a class="text-link" href="#combobox-demo">See autocomplete behavior</a></div><div class="tabpanel" role="tabpanel" id="panel-release" aria-labelledby="tab-release" hidden><h3>Version 2.4 is ready</h3><p>Improved focus states and motion preferences are now part of every shipped primitive.</p><a class="text-link" href="#modal-demo">Review dialog behavior</a></div><span class="key-contract">← → switch tabs · Tab enters active panel</span></div></section>
<section class="section" id="combobox-demo" aria-labelledby="combobox-heading"><div class="section-head"><h2 id="combobox-heading"><span class="number">04 / </span>Combobox</h2></div><div class="card"><p class="demo-label">Type to narrow a list. Focus stays in the input while the active option is announced.</p><div class="combo-wrap"><label class="combo-label" for="city-input">Choose a project city</label><input class="combo-input" id="city-input" role="combobox" type="text" autocomplete="off" aria-autocomplete="list" aria-expanded="false" aria-controls="city-list" aria-activedescendant="" placeholder="Start typing a city"><ul class="listbox" id="city-list" role="listbox" aria-label="Project cities" hidden></ul></div><p class="status" id="combo-status" role="status" aria-live="polite"></p><span class="key-contract">Type filter · ↓↑ highlight · Enter select · Esc revert</span></div></section>
<section class="section" id="accordion-demo" aria-labelledby="accordion-heading"><div class="section-head"><h2 id="accordion-heading"><span class="number">05 / </span>Accordion</h2></div><div class="card"><p class="demo-label">Independent disclosures let people open the detail they need, in any combination.</p><div class="accordion"><div class="accordion-item"><h3><button class="accordion-trigger" id="acc-button-1" type="button" aria-expanded="false" aria-controls="acc-panel-1">Can more than one section stay open?<span class="plus" aria-hidden="true">+</span></button></h3><div class="accordion-panel" id="acc-panel-1" role="region" aria-labelledby="acc-button-1" hidden><div><p>Yes. These are disclosures, not exclusive tabs. Expand as many answers as help you compare information.</p></div></div></div><div class="accordion-item"><h3><button class="accordion-trigger" id="acc-button-2" type="button" aria-expanded="false" aria-controls="acc-panel-2">Does the motion respect preferences?<span class="plus" aria-hidden="true">+</span></button></h3><div class="accordion-panel" id="acc-panel-2" role="region" aria-labelledby="acc-button-2" hidden><div><p>It does. The height transition is reduced to virtually instant when reduced motion is requested by the operating system.</p></div></div></div><div class="accordion-item"><h3><button class="accordion-trigger" id="acc-button-3" type="button" aria-expanded="false" aria-controls="acc-panel-3">What makes the regions discoverable?<span class="plus" aria-hidden="true">+</span></button></h3><div class="accordion-panel" id="acc-panel-3" role="region" aria-labelledby="acc-button-3" hidden><div><p>Each control names its associated region, reports its expanded state, and uses a real button for familiar keyboard support.</p></div></div></div></div><span class="key-contract">Enter / Space toggle · multiple sections allowed</span></div></section>
</main></div>
<div class="modal-backdrop" id="modal-backdrop" aria-hidden="true"><div class="modal" id="confirm-dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-desc"><h3 id="dialog-title">Publish this component set?</h3><p id="dialog-desc">Your documentation will be available to the whole product team. You can revise it any time.</p><div class="modal-actions"><button class="btn btn-secondary" id="dialog-cancel" type="button">Cancel</button><button class="btn" id="dialog-confirm" type="button">Publish changes</button></div></div></div>
<script>
'use strict';
// Tunable behavior constants
const CONFIG={animationMs:220, cities:['Amsterdam','Auckland','Barcelona','Berlin','Boston','Copenhagen','Dublin','Helsinki','Lisbon','London','Melbourne','Montreal','New York','Oslo','Paris','Portland','Prague','Seoul','Singapore','Tokyo']};
const $=(s,p=document)=>p.querySelector(s);const $$=(s,p=document)=>[...p.querySelectorAll(s)];
const visibleFocusables=root=>$$('button:not([disabled]),[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])',root).filter(el=>!el.hidden&&el.getClientRects().length);
// Modal: focus containment and restoration
const trigger=$('#modal-trigger'),backdrop=$('#modal-backdrop'),dialog=$('#confirm-dialog');let lastModalFocus=null;
function openModal(){lastModalFocus=document.activeElement;backdrop.classList.add('is-open');backdrop.setAttribute('aria-hidden','false');$('#page-shell').inert=true;setTimeout(()=>$('#dialog-cancel').focus(),0)}
function closeModal(){backdrop.classList.remove('is-open');backdrop.setAttribute('aria-hidden','true');$('#page-shell').inert=false;lastModalFocus?.focus()}
trigger.addEventListener('click',openModal);$('#dialog-cancel').addEventListener('click',closeModal);$('#dialog-confirm').addEventListener('click',()=>{closeModal();trigger.textContent='Changes published';});
backdrop.addEventListener('mousedown',e=>{if(e.target===backdrop)closeModal()});dialog.addEventListener('keydown',e=>{if(e.key==='Escape'){e.preventDefault();closeModal()}if(e.key==='Tab'){const items=visibleFocusables(dialog),first=items[0],last=items.at(-1);if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus()}else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus()}}});
// Menu: roving focus inside a transient menu
const menuBtn=$('#menu-button'),menu=$('#action-menu'),menuItems=$$('[role="menuitem"]',menu),menuStatus=$('#menu-status');
function openMenu(focusIndex=null){menu.hidden=false;menuBtn.setAttribute('aria-expanded','true');if(focusIndex!==null)menuItems[focusIndex].focus()}
function closeMenu(returnFocus=false){menu.hidden=true;menuBtn.setAttribute('aria-expanded','false');if(returnFocus)menuBtn.focus()}
menuBtn.addEventListener('click',()=>menu.hidden?openMenu():closeMenu());menuBtn.addEventListener('keydown',e=>{if(['ArrowDown','Enter',' '].includes(e.key)){e.preventDefault();openMenu(0)}else if(e.key==='ArrowUp'){e.preventDefault();openMenu(menuItems.length-1)}else if(e.key==='Escape')closeMenu(true)});
menu.addEventListener('keydown',e=>{const i=menuItems.indexOf(document.activeElement);let next=null;if(e.key==='ArrowDown')next=(i+1)%menuItems.length;if(e.key==='ArrowUp')next=(i-1+menuItems.length)%menuItems.length;if(e.key==='Home')next=0;if(e.key==='End')next=menuItems.length-1;if(next!==null){e.preventDefault();menuItems[next].focus()}if(e.key==='Escape'){e.preventDefault();closeMenu(true)}if(e.key==='Tab')closeMenu(false)});menuItems.forEach(item=>item.addEventListener('click',()=>{menuStatus.textContent=`Action selected: ${item.textContent}.`;closeMenu(true)}));document.addEventListener('pointerdown',e=>{if(!menu.hidden&&!$('.menu-wrap').contains(e.target))closeMenu()});
// Tabs: one tab stop, automatic activation
const tabs=$$('[role="tab"]');function activateTab(tab,focus=false){tabs.forEach(t=>{const on=t===tab;t.setAttribute('aria-selected',on);t.tabIndex=on?0:-1;$(`#${t.getAttribute('aria-controls')}`).hidden=!on});if(focus)tab.focus()}
tabs.forEach((tab,i)=>{tab.addEventListener('click',()=>activateTab(tab));tab.addEventListener('keydown',e=>{let n;if(e.key==='ArrowRight')n=(i+1)%tabs.length;else if(e.key==='ArrowLeft')n=(i-1+tabs.length)%tabs.length;else if(e.key==='Home')n=0;else if(e.key==='End')n=tabs.length-1;else return;e.preventDefault();activateTab(tabs[n],true)})});
// Combobox: aria-activedescendant remains on input
const input=$('#city-input'),list=$('#city-list'),comboStatus=$('#combo-status');let filtered=[],active=-1,committed='';
function renderOptions(){const q=input.value.trim().toLowerCase();filtered=CONFIG.cities.filter(x=>x.toLowerCase().includes(q));active=filtered.length?0:-1;list.innerHTML=filtered.length?filtered.map((city,i)=>`<li id="city-option-${CONFIG.cities.indexOf(city)}" class="option${i===active?' active':''}" role="option" aria-selected="${i===active}">${city}</li>`).join(''):'<li class="empty" role="option" aria-disabled="true">No matching cities</li>';list.hidden=false;input.setAttribute('aria-expanded','true');syncActive()}
function syncActive(){const opts=$$('[role="option"]',list).filter(x=>!x.getAttribute('aria-disabled'));opts.forEach((o,i)=>{o.classList.toggle('active',i===active);o.setAttribute('aria-selected',i===active)});const option=opts[active];input.setAttribute('aria-activedescendant',option?.id||'');option?.scrollIntoView({block:'nearest'})}
function closeCombo(revert=false){if(revert)input.value=committed;list.hidden=true;input.setAttribute('aria-expanded','false');input.setAttribute('aria-activedescendant','');active=-1}
function choose(city){committed=city;input.value=city;comboStatus.textContent=`Selected ${city}.`;closeCombo()}
input.addEventListener('input',renderOptions);input.addEventListener('focus',renderOptions);input.addEventListener('keydown',e=>{if(e.key==='ArrowDown'){e.preventDefault();if(list.hidden)renderOptions();else if(filtered.length){active=(active+1)%filtered.length;syncActive()}}else if(e.key==='ArrowUp'){e.preventDefault();if(list.hidden)renderOptions();else if(filtered.length){active=(active-1+filtered.length)%filtered.length;syncActive()}}else if(e.key==='Enter'&&active>=0&&!list.hidden){e.preventDefault();choose(filtered[active])}else if(e.key==='Escape'&&!list.hidden){e.preventDefault();closeCombo(true)}});list.addEventListener('mousedown',e=>e.preventDefault());list.addEventListener('click',e=>{const o=e.target.closest('.option');if(o)choose(o.textContent)});input.addEventListener('blur',()=>setTimeout(()=>closeCombo(),120));
// Accordion: animate first, hide after close transition
$$('.accordion-trigger').forEach(button=>button.addEventListener('click',()=>{const panel=$(`#${button.getAttribute('aria-controls')}`),opening=button.getAttribute('aria-expanded')==='false';button.setAttribute('aria-expanded',opening);if(opening){panel.hidden=false;requestAnimationFrame(()=>panel.classList.add('open'))}else{panel.classList.remove('open');setTimeout(()=>{if(button.getAttribute('aria-expanded')==='false')panel.hidden=true},CONFIG.animationMs)}}));
</script>
</body></html>
<!-- agent-meta {"model":"openai/gpt-5.6-terra-pro","provider":"openrouter","persona":"full","sessionId":"75167c61-8cb1-445a-8cb0-c2e36420bac3","tokensIn":348641,"tokensOut":13682,"tokensTotal":362323,"cost":0.2113202,"turns":7,"toolCalls":12,"failedToolCalls":2,"timestamp":"2026-08-01T05:26:50.996Z"} -->