Accessible Components
GPT 6 Luna · 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 33,633 bytes · SHA-256 a50df6f391e3
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>Forma — Accessible UI patterns</title>
<style>
/* Tunable design tokens */
:root{--ink:#182526;--muted:#637373;--line:#dce5e1;--paper:#f8faf8;--surface:#fff;--teal:#087e78;--teal-dark:#05635f;--mint:#dff4ef;--gold:#c98536;--focus:#d17a1d;--radius:14px;--small-radius:9px;--space:8px;--ease:220ms;--shadow:0 18px 55px #17332a16;--sidebar:258px;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:28px}body{margin:0;background:var(--paper);color:var(--ink);font-size:15px;line-height:1.6}button,input{font:inherit}button{cursor:pointer;color:inherit}a{color:inherit} :focus-visible{outline:3px solid var(--focus)!important;outline-offset:3px!important;box-shadow:0 0 0 5px #f8c77b55!important}
.skip-link{position:fixed;z-index:1000;top:12px;left:12px;padding:10px 16px;background:var(--ink);color:#fff;border-radius:8px;transform:translateY(-160%);transition:transform .15s}.skip-link:focus{transform:translateY(0)}
.layout{min-height:100vh}.sidebar{position:fixed;inset:0 auto 0 0;width:var(--sidebar);background:#f1f6f3;border-right:1px solid var(--line);padding:30px 22px;display:flex;flex-direction:column;z-index:5}.brand{display:flex;align-items:center;gap:11px;text-decoration:none;font-size:16px;font-weight:750;letter-spacing:-.035em}.brand-mark{width:32px;height:32px;border-radius:10px;background:var(--teal);color:white;display:grid;place-items:center;font-size:16px;box-shadow:0 5px 12px #087e7830}.brand small{display:block;font-size:10px;letter-spacing:.11em;text-transform:uppercase;color:var(--muted);font-weight:700;margin-top:1px}.nav-label{margin:49px 0 12px 10px;color:#84918c;font-size:10px;font-weight:750;letter-spacing:.14em;text-transform:uppercase}.side-nav{display:grid;gap:5px}.side-nav a{display:flex;align-items:center;gap:12px;padding:10px 11px;text-decoration:none;border-radius:9px;color:#52615e;font-size:13px;font-weight:550;transition:background .15s,color .15s}.side-nav a:hover,.side-nav a:focus-visible{background:#e3eeea;color:var(--teal-dark)}.nav-num{font-size:10px;color:#8b9994;font-variant-numeric:tabular-nums;letter-spacing:.06em}.side-foot{margin-top:auto;color:#87938f;font-size:11px;line-height:1.5;border-top:1px solid var(--line);padding:17px 4px 0}.side-foot b{color:#52615e;font-weight:650}
main{margin-left:var(--sidebar);max-width:1180px;padding:63px clamp(30px,6.5vw,96px) 90px}.eyebrow{color:var(--teal);font-size:11px;letter-spacing:.15em;text-transform:uppercase;font-weight:760;margin:0 0 14px}.hero{max-width:760px;padding:13px 0 48px}.hero h1{font-size:clamp(38px,5.2vw,61px);line-height:1.05;letter-spacing:-.055em;margin:0 0 20px;font-weight:690}.hero p{font-size:17px;color:var(--muted);max-width:620px;margin:0}.hero-meta{display:flex;gap:9px;align-items:center;margin-top:24px;font-size:12px;color:#788681}.pill{display:inline-flex;align-items:center;gap:7px;padding:5px 10px;background:#e7f3ef;border-radius:99px;color:#276b62;font-size:11px;font-weight:700}.pill:before{content:"";width:6px;height:6px;background:#40a287;border-radius:50%}
.component{border-top:1px solid var(--line);padding:37px 0 46px;scroll-margin-top:24px}.section-head{display:flex;justify-content:space-between;align-items:flex-start;gap:20px;margin-bottom:20px}.section-title-wrap{display:flex;align-items:flex-start;gap:14px}.section-number{display:grid;place-items:center;width:31px;height:31px;border:1px solid var(--line);background:white;border-radius:9px;color:#7b8984;font-size:11px;font-weight:700;margin-top:2px}.component h2{font-size:24px;line-height:1.25;letter-spacing:-.035em;margin:0;font-weight:670}.component .intro{color:var(--muted);font-size:13px;margin:6px 0 0}.spec-tag{color:#83918b;font-size:10px;letter-spacing:.09em;text-transform:uppercase;border:1px solid var(--line);border-radius:99px;padding:5px 9px;white-space:nowrap;margin-top:3px}.demo-card{border:1px solid var(--line);background:var(--surface);border-radius:var(--radius);box-shadow:0 4px 16px #253a3210;overflow:visible}.demo-pad{padding:27px 29px}.demo-label{font-size:10px;color:#8a9791;text-transform:uppercase;letter-spacing:.13em;font-weight:750;margin:0 0 16px}.caption{display:flex;align-items:center;gap:9px;border-top:1px solid #edf1ee;padding:13px 20px;color:#667570;font-size:12px;min-height:45px}.caption strong{color:#304541;font-weight:650}.key{font:600 10px ui-monospace,SFMono-Regular,Menlo,monospace;background:#f3f6f4;border:1px solid #dce4df;border-radius:4px;padding:2px 5px;color:#52625d;white-space:nowrap}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;min-height:40px;padding:9px 15px;background:var(--teal);color:white;border:1px solid var(--teal);border-radius:8px;font-weight:650;font-size:13px;transition:background .15s,transform .15s}.btn:hover{background:var(--teal-dark);border-color:var(--teal-dark)}.btn:active{transform:translateY(1px)}.btn-secondary{background:white;color:#344a45;border-color:#cfdad5}.btn-secondary:hover{background:#f3f8f5;border-color:#aabbb4}.btn svg{width:15px;height:15px}.demo-inline{display:flex;align-items:center;flex-wrap:wrap;gap:15px}.subtle-note{color:#788681;font-size:12px}.status{color:#276b62;font-size:12px;font-weight:600}.status:empty{display:none}
/* Menu */
.menu-wrap{position:relative;display:inline-block}.menu-trigger[aria-expanded="true"]{background:#eaf5f1;color:var(--teal-dark);border-color:#aacfc3}.menu-pop{position:absolute;z-index:8;top:calc(100% + 8px);left:0;min-width:206px;padding:6px;background:white;border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow)}.menu-pop[hidden]{display:none}.menuitem{width:100%;display:flex;align-items:center;gap:10px;border:0;background:transparent;text-align:left;padding:9px 10px;border-radius:6px;font-size:13px;color:#334843}.menuitem:hover,.menuitem:focus,.menuitem[aria-current="true"]{background:#edf6f2;color:var(--teal-dark);outline:none}.menuitem .mi-icon{width:18px;color:#82918b;text-align:center;font-size:14px}.menu-sep{height:1px;background:#edf1ee;margin:5px 4px}
/* Tabs */
.tabs{max-width:710px}.tablist{display:flex;gap:4px;border-bottom:1px solid var(--line);padding:0 0 0 3px}.tab{position:relative;border:0;background:transparent;color:#718079;padding:10px 16px 12px;font-size:13px;font-weight:620;border-radius:7px 7px 0 0}.tab:hover{color:var(--teal-dark);background:#f5f9f6}.tab[aria-selected="true"]{color:var(--teal-dark)}.tab[aria-selected="true"]:after{content:"";position:absolute;height:2px;left:12px;right:12px;bottom:-1px;background:var(--teal);border-radius:2px}.tabpanel{padding:22px 4px 2px;min-height:132px}.tabpanel[hidden]{display:none}.panel-kicker{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--teal);font-weight:750;margin:0 0 7px}.tabpanel h3{font-size:17px;letter-spacing:-.02em;margin:0 0 6px}.tabpanel p{margin:0;color:var(--muted);font-size:13px;max-width:590px}.panel-link{display:inline-block;margin-top:12px;color:var(--teal-dark);font-size:12px;font-weight:650;text-decoration-thickness:1px;text-underline-offset:3px}
/* Combobox */
.combo-area{max-width:450px;position:relative}.combo-label{display:block;font-size:12px;font-weight:650;color:#40534e;margin-bottom:7px}.combo-wrap{position:relative}.combo-input{width:100%;height:43px;padding:0 42px 0 13px;border:1px solid #cbd8d2;border-radius:8px;background:white;color:var(--ink);font-size:13px}.combo-input::placeholder{color:#9aa6a0}.combo-input:focus{border-color:var(--teal);outline:3px solid #087e7827}.combo-chevron{position:absolute;right:13px;top:50%;transform:translateY(-50%);color:#72817a;pointer-events:none;font-size:12px}.listbox{position:absolute;z-index:7;top:calc(100% + 5px);left:0;right:0;max-height:220px;overflow:auto;background:white;border:1px solid var(--line);border-radius:9px;padding:5px;box-shadow:var(--shadow)}.listbox[hidden]{display:none}.option{padding:8px 10px;border-radius:6px;font-size:13px;color:#344842;cursor:pointer}.option[aria-selected="true"],.option.is-active{background:#e8f5f0;color:var(--teal-dark)}.option[aria-selected="true"]:after{content:"Selected";float:right;font-size:10px;color:#64877c}.empty-option{padding:11px;color:#72817a;font-size:12px}.combo-help{margin:8px 0 0;color:#7c8984;font-size:11px}.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}
/* Accordion */
.accordion{max-width:710px;border:1px solid var(--line);border-radius:11px;overflow:hidden;background:white}.acc-item+.acc-item{border-top:1px solid #e8eeea}.acc-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;text-align:left;border:0;background:white;padding:15px 17px;font-weight:640;font-size:13px;color:#334843}.acc-trigger:hover{background:#f8fbf9}.acc-trigger[aria-expanded="true"]{color:var(--teal-dark);background:#fbfdfc}.acc-chevron{color:#789088;transition:transform var(--ease) ease;font-size:14px}.acc-trigger[aria-expanded="true"] .acc-chevron{transform:rotate(180deg)}.acc-region{overflow:hidden;height:0;transition:height var(--ease) ease}.acc-region[hidden]{display:none}.acc-copy{padding:0 17px 16px;color:var(--muted);font-size:12px;max-width:670px}.acc-copy p{margin:0 0 8px}.acc-copy a{color:var(--teal-dark);font-weight:620;text-underline-offset:3px}
/* Modal */
body.dialog-open{overflow:hidden}.scrim{position:fixed;inset:0;z-index:30;background:#14232089;display:grid;place-items:center;padding:22px;animation:fadeIn .16s ease}.scrim[hidden]{display:none}.dialog{width:min(100%,490px);background:white;border:1px solid #e1e9e5;border-radius:16px;box-shadow:0 28px 100px #061c1766;padding:28px;animation:dialogIn .2s ease}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:15px}.dialog h2{font-size:23px;letter-spacing:-.035em;margin:0;line-height:1.2}.dialog p{font-size:13px;color:var(--muted);margin:13px 0 20px}.dialog-close{border:1px solid var(--line);border-radius:7px;background:#fff;width:34px;height:34px;display:grid;place-items:center;font-size:18px;color:#667770}.dialog-close:hover{background:#f2f7f4}.dialog-actions{display:flex;justify-content:flex-end;gap:9px;padding-top:17px;border-top:1px solid #edf1ee}.dialog-note{font-size:11px!important;margin:13px 0 0!important;color:#778680!important}
.toast{position:fixed;right:24px;bottom:22px;z-index:20;padding:11px 15px;background:#203c36;color:white;border-radius:9px;box-shadow:var(--shadow);font-size:12px;animation:toastIn .2s ease}.toast[hidden]{display:none}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes dialogIn{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}@keyframes toastIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
@media(max-width:800px){:root{--sidebar:210px}.sidebar{padding:24px 15px}main{padding:48px 30px 70px}}
@media(max-width:620px){.sidebar{position:relative;width:100%;height:auto;padding:14px 18px;border-right:0;border-bottom:1px solid var(--line)}.brand small,.nav-label,.side-foot{display:none}.side-nav{display:flex;overflow-x:auto;gap:3px;margin-top:12px;padding-bottom:2px}.side-nav a{flex:none;padding:7px 9px;font-size:11px;gap:7px}.nav-num{display:none}main{margin-left:0;padding:35px 20px 60px}.hero{padding-bottom:34px}.hero h1{font-size:42px}.hero p{font-size:15px}.component{padding:29px 0 35px}.demo-pad{padding:21px 18px}.section-head{gap:10px}.spec-tag{display:none}.component h2{font-size:21px}.caption{flex-wrap:wrap;gap:6px}.scrim{padding:14px}.dialog{padding:22px}}
</style>
</head>
<body>
<a class="skip-link" href="#main">Skip to main content</a>
<div class="layout">
<aside class="sidebar" aria-label="Showcase navigation">
<a class="brand" href="#top" aria-label="Forma, back to top"><span class="brand-mark" aria-hidden="true">f</span><span>forma<small>accessible patterns</small></span></a>
<p class="nav-label">Components</p>
<nav class="side-nav" aria-label="Components">
<a href="#dialog-section"><span class="nav-num">01</span> Dialog</a><a href="#menu-section"><span class="nav-num">02</span> Menu</a><a href="#tabs-section"><span class="nav-num">03</span> Tabs</a><a href="#combo-section"><span class="nav-num">04</span> Combobox</a><a href="#accordion-section"><span class="nav-num">05</span> Accordion</a>
</nav>
<div class="side-foot"><b>Built for everyone.</b><br>Thoughtful interaction, by default.</div>
</aside>
<main id="main" tabindex="-1">
<header class="hero" id="top"><p class="eyebrow">Pattern library · 01—05</p><h1>Accessible, by<br>design.</h1><p>A small collection of familiar interface patterns, built with care for keyboard users, assistive technology, and the details in between.</p><div class="hero-meta"><span class="pill">Vanilla · WCAG-minded</span><span>Five practical patterns</span></div></header>
<section class="component" id="dialog-section" aria-labelledby="dialog-heading">
<div class="section-head"><div class="section-title-wrap"><span class="section-number" aria-hidden="true">01</span><div><h2 id="dialog-heading">Modal dialog</h2><p class="intro">A focused moment for decisions that need your attention.</p></div></div><span class="spec-tag">Focus managed</span></div>
<div class="demo-card"><div class="demo-pad"><p class="demo-label">Live example</p><div class="demo-inline"><button class="btn" id="open-dialog"><span>Open dialog</span><svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M3 8h10M8 3l5 5-5 5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></button><span class="subtle-note">The page behind it becomes inert.</span></div></div><div class="caption"><strong>Keyboard</strong> <span class="key">Tab</span> stays inside · <span class="key">Esc</span> closes · focus returns to trigger</div></div>
</section>
<section class="component" id="menu-section" aria-labelledby="menu-heading">
<div class="section-head"><div class="section-title-wrap"><span class="section-number" aria-hidden="true">02</span><div><h2 id="menu-heading">Dropdown menu</h2><p class="intro">A compact set of commands, fully navigable without a pointer.</p></div></div><span class="spec-tag">Menu button</span></div>
<div class="demo-card"><div class="demo-pad"><p class="demo-label">Live example</p><div class="demo-inline"><div class="menu-wrap"><button class="btn btn-secondary menu-trigger" id="menu-trigger" aria-haspopup="menu" aria-expanded="false" aria-controls="action-menu">Actions <span aria-hidden="true">⌄</span></button><div class="menu-pop" id="action-menu" role="menu" aria-labelledby="menu-trigger" hidden><button class="menuitem" role="menuitem" tabindex="-1" data-action="A new draft is ready"><span class="mi-icon" aria-hidden="true">+</span>New draft</button><button class="menuitem" role="menuitem" tabindex="-1" data-action="A copy has been created"><span class="mi-icon" aria-hidden="true">▣</span>Make a copy</button><div class="menu-sep" role="separator"></div><button class="menuitem" role="menuitem" tabindex="-1" data-action="This item is now archived"><span class="mi-icon" aria-hidden="true">↗</span>Archive</button></div></div><span id="menu-status" class="status" role="status" aria-live="polite"></span></div></div><div class="caption"><strong>Keyboard</strong> <span class="key">↓</span>/<span class="key">↑</span> move · <span class="key">Home</span>/<span class="key">End</span> jump · <span class="key">Esc</span> close · <span class="key">Tab</span> onward</div></div>
</section>
<section class="component" id="tabs-section" aria-labelledby="tabs-heading">
<div class="section-head"><div class="section-title-wrap"><span class="section-number" aria-hidden="true">03</span><div><h2 id="tabs-heading">Tabs</h2><p class="intro">Related content, grouped in a concise and predictable switcher.</p></div></div><span class="spec-tag">Automatic activation</span></div>
<div class="demo-card"><div class="demo-pad"><p class="demo-label">Live example</p><div class="tabs"><div class="tablist" role="tablist" aria-label="Workspace settings" aria-orientation="horizontal"><button class="tab" id="tab-overview" role="tab" aria-selected="true" aria-controls="panel-overview" tabindex="0">Overview</button><button class="tab" id="tab-members" role="tab" aria-selected="false" aria-controls="panel-members" tabindex="-1">Members</button><button class="tab" id="tab-preferences" role="tab" aria-selected="false" aria-controls="panel-preferences" tabindex="-1">Preferences</button></div><section class="tabpanel" id="panel-overview" role="tabpanel" aria-labelledby="tab-overview" tabindex="0"><p class="panel-kicker">Your workspace</p><h3>A clear view of what matters.</h3><p>Bring projects, people, and useful updates together in one calm, considered space.</p><a class="panel-link" href="#combo-section">Explore the patterns <span aria-hidden="true">→</span></a></section><section class="tabpanel" id="panel-members" role="tabpanel" aria-labelledby="tab-members" tabindex="0" hidden><p class="panel-kicker">People & access</p><h3>Good work is a team effort.</h3><p>Invite collaborators, choose their access level, and keep every contributor in the loop.</p><a class="panel-link" href="#accordion-section">Learn about access <span aria-hidden="true">→</span></a></section><section class="tabpanel" id="panel-preferences" role="tabpanel" aria-labelledby="tab-preferences" tabindex="0" hidden><p class="panel-kicker">Make it yours</p><h3>Set up a rhythm that fits.</h3><p>Fine-tune your notifications and display preferences to make this workspace feel like home.</p><a class="panel-link" href="#dialog-section">Review preferences <span aria-hidden="true">→</span></a></section></div></div><div class="caption"><strong>Keyboard</strong> <span class="key">←</span>/<span class="key">→</span> switch tabs · <span class="key">Tab</span> enters active panel</div></div>
</section>
<section class="component" id="combo-section" aria-labelledby="combo-heading">
<div class="section-head"><div class="section-title-wrap"><span class="section-number" aria-hidden="true">04</span><div><h2 id="combo-heading">Autocomplete</h2><p class="intro">A searchable list that keeps focus exactly where you expect it.</p></div></div><span class="spec-tag">Combobox · list autocomplete</span></div>
<div class="demo-card"><div class="demo-pad"><p class="demo-label">Live example</p><div class="combo-area"><label class="combo-label" for="country-input">Choose a country</label><div class="combo-wrap"><input class="combo-input" id="country-input" type="text" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="country-listbox" aria-activedescendant="" autocomplete="off" placeholder="Start typing a country…"><span class="combo-chevron" aria-hidden="true">⌄</span><div class="listbox" id="country-listbox" role="listbox" aria-label="Countries" hidden></div></div><p class="combo-help" id="combo-help">Type to filter · use ↑ and ↓ to browse suggestions.</p><div class="sr-only" id="combo-live" role="status" aria-live="polite" aria-atomic="true"></div></div></div><div class="caption"><strong>Keyboard</strong> <span class="key">↑</span>/<span class="key">↓</span> browse · <span class="key">Enter</span> select · <span class="key">Esc</span> revert & close</div></div>
</section>
<section class="component" id="accordion-section" aria-labelledby="accordion-heading">
<div class="section-head"><div class="section-title-wrap"><span class="section-number" aria-hidden="true">05</span><div><h2 id="accordion-heading">Accordion</h2><p class="intro">Disclosure rows that let you reveal only what you need.</p></div></div><span class="spec-tag">Independent panels</span></div>
<div class="demo-card"><div class="demo-pad"><p class="demo-label">Live example</p><div class="accordion">
<div class="acc-item"><h3 style="margin:0"><button class="acc-trigger" id="acc-trigger-1" aria-expanded="false" aria-controls="acc-panel-1"><span>What makes a pattern accessible?</span><span class="acc-chevron" aria-hidden="true">⌄</span></button></h3><div class="acc-region" id="acc-panel-1" role="region" aria-labelledby="acc-trigger-1" hidden><div class="acc-copy"><p>Accessible patterns pair clear semantics with thoughtful behavior: meaningful names, visible focus, and interactions that work with a keyboard and assistive technology.</p><a href="#dialog-section">See the dialog pattern</a></div></div></div>
<div class="acc-item"><h3 style="margin:0"><button class="acc-trigger" id="acc-trigger-2" aria-expanded="false" aria-controls="acc-panel-2"><span>Can I open more than one section?</span><span class="acc-chevron" aria-hidden="true">⌄</span></button></h3><div class="acc-region" id="acc-panel-2" role="region" aria-labelledby="acc-trigger-2" hidden><div class="acc-copy"><p>Yes. Each disclosure is independent, so opening one never closes another. This is useful when people need to compare details across sections.</p><a href="#tabs-section">Explore tabs, too</a></div></div></div>
<div class="acc-item"><h3 style="margin:0"><button class="acc-trigger" id="acc-trigger-3" aria-expanded="false" aria-controls="acc-panel-3"><span>What about reduced motion?</span><span class="acc-chevron" aria-hidden="true">⌄</span></button></h3><div class="acc-region" id="acc-panel-3" role="region" aria-labelledby="acc-trigger-3" hidden><div class="acc-copy"><p>All motion respects your system preference. When reduced motion is enabled, transitions and animations become effectively instantaneous.</p><a href="#main">Back to the beginning</a></div></div></div>
</div></div><div class="caption"><strong>Keyboard</strong> <span class="key">Enter</span>/<span class="key">Space</span> toggle · multiple sections may stay open</div></div>
</section>
<footer style="border-top:1px solid var(--line);padding-top:20px;color:#82908a;font-size:11px">Designed with care <span aria-hidden="true">·</span> Semantic HTML, no dependencies.</footer>
</main>
</div>
<div class="scrim" id="dialog-scrim" hidden><section class="dialog" id="demo-dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-description" tabindex="-1"><div class="dialog-head"><div><p class="eyebrow" style="margin-bottom:8px">A quick check-in</p><h2 id="dialog-title">Ready to make it official?</h2></div><button class="dialog-close" type="button" aria-label="Close dialog">×</button></div><p id="dialog-description">This focused dialog keeps the next step clear. You can close it at any time and return right where you started.</p><div class="dialog-actions"><button class="btn btn-secondary" type="button" id="dialog-cancel">Not now</button><button class="btn" type="button" id="dialog-confirm">Sounds good</button></div><p class="dialog-note">Tip: Tab cycles within this dialog. Press Escape to dismiss.</p></section></div>
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script>
'use strict';
/* Shared utilities */
const $=(s,root=document)=>root.querySelector(s), $$=(s,root=document)=>[...root.querySelectorAll(s)];
const focusableSelector='a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
let toastTimer;function announceToast(message){const toast=$('#toast');toast.textContent=message;toast.hidden=false;clearTimeout(toastTimer);toastTimer=setTimeout(()=>toast.hidden=true,2800)}
/* Modal: focus containment, inert background, restoration */
const scrim=$('#dialog-scrim'), dialog=$('#demo-dialog'), dialogTrigger=$('#open-dialog');let returnFocus=null;
function openDialog(){if(!scrim.hidden)return;returnFocus=document.activeElement;$('#main').inert=true;$('.sidebar').inert=true;$('.skip-link').inert=true;document.body.classList.add('dialog-open');scrim.hidden=false;$('.dialog-close').focus()}
function closeDialog(){if(scrim.hidden)return;scrim.hidden=true;$('#main').inert=false;$('.sidebar').inert=false;$('.skip-link').inert=false;document.body.classList.remove('dialog-open');if(returnFocus?.isConnected)returnFocus.focus()}
dialogTrigger.addEventListener('click',openDialog);$$('.dialog-close,#dialog-cancel,#dialog-confirm').forEach(b=>b.addEventListener('click',()=>{if(b.id==='dialog-confirm')announceToast('Great — your choice has been saved.');closeDialog()}));
scrim.addEventListener('click',e=>{if(e.target===scrim)closeDialog()});
document.addEventListener('keydown',e=>{if(scrim.hidden)return;if(e.key==='Escape'){e.preventDefault();closeDialog();return}if(e.key==='Tab'){const nodes=$$(focusableSelector,dialog).filter(el=>!el.hidden&&el.getClientRects().length);if(!nodes.length){e.preventDefault();dialog.focus();return}const first=nodes[0],last=nodes[nodes.length-1];if(e.shiftKey&&(document.activeElement===first||!dialog.contains(document.activeElement))){e.preventDefault();last.focus()}else if(!e.shiftKey&&(document.activeElement===last||!dialog.contains(document.activeElement))){e.preventDefault();first.focus()}}});
/* Menu button: explicit menu keyboard model */
const menuButton=$('#menu-trigger'),menu=$('#action-menu');let menuItems=[];
function refreshMenuItems(){menuItems=$$('[role="menuitem"]',menu)}function openMenu(first=true){menu.hidden=false;menuButton.setAttribute('aria-expanded','true');refreshMenuItems();if(first)menuItems[0]?.focus()}
function closeMenu(restore=true){if(menu.hidden)return;menu.hidden=true;menuButton.setAttribute('aria-expanded','false');if(restore)menuButton.focus()}
menuButton.addEventListener('click',()=>menu.hidden?openMenu():closeMenu(false));menuButton.addEventListener('keydown',e=>{if(e.key==='ArrowDown'){e.preventDefault();openMenu(true)}else if(e.key==='ArrowUp'){e.preventDefault();openMenu(false);refreshMenuItems().at(-1)?.focus()}else if(e.key==='Enter'||e.key===' '){e.preventDefault();if(menu.hidden)openMenu(true)}});
menu.addEventListener('keydown',e=>{const i=menuItems.indexOf(document.activeElement);let next=null;if(e.key==='ArrowDown')next=(i+1)%menuItems.length;else if(e.key==='ArrowUp')next=(i-1+menuItems.length)%menuItems.length;else if(e.key==='Home')next=0;else if(e.key==='End')next=menuItems.length-1;else if(e.key==='Escape'){e.preventDefault();closeMenu();return}else if(e.key==='Tab'){closeMenu(false);return}else if(e.key===' '||e.key==='Enter'){e.preventDefault();document.activeElement.click();return}if(next!==null){e.preventDefault();menuItems[next].focus()}});
menu.addEventListener('click',e=>{const item=e.target.closest('[role="menuitem"]');if(!item)return;$('#menu-status').textContent=item.dataset.action;closeMenu(true)});
/* Tabs: automatic activation with roving tabindex */
const tabs=$$('[role="tab"]');function activateTab(tab,moveFocus=false){tabs.forEach(t=>{const selected=t===tab;t.setAttribute('aria-selected',String(selected));t.tabIndex=selected?0:-1;const panel=document.getElementById(t.getAttribute('aria-controls'));panel.hidden=!selected});if(moveFocus)tab.focus()}
tabs.forEach((tab,index)=>tab.addEventListener('keydown',e=>{let target;if(e.key==='ArrowRight')target=tabs[(index+1)%tabs.length];else if(e.key==='ArrowLeft')target=tabs[(index-1+tabs.length)%tabs.length];else if(e.key==='Home')target=tabs[0];else if(e.key==='End')target=tabs[tabs.length-1];if(target){e.preventDefault();activateTab(target,true)}}));tabs.forEach(tab=>tab.addEventListener('click',()=>activateTab(tab)));
/* Combobox: aria-activedescendant keeps DOM focus on the text input */
const countries=['Argentina','Australia','Brazil','Canada','Chile','China','Colombia','Denmark','Egypt','Finland','France','Germany','Ghana','India','Indonesia','Ireland','Italy','Japan','Kenya','Mexico','Netherlands','New Zealand','Nigeria','Norway','Peru','Philippines','Portugal','Singapore','South Africa','South Korea','Spain','Sweden','Switzerland','Thailand','United Kingdom','United States'];
const combo=$('#country-input'),listbox=$('#country-listbox'),live=$('#combo-live');let filtered=[],activeIndex=-1,selectedValue='',draftValue='';
function renderOptions(){listbox.replaceChildren();filtered.forEach((name,i)=>{const opt=document.createElement('div');opt.id='country-option-'+i;opt.className='option';opt.setAttribute('role','option');opt.setAttribute('aria-selected',String(name===selectedValue));opt.textContent=name;if(i===activeIndex)opt.classList.add('is-active');opt.addEventListener('mousedown',e=>e.preventDefault());opt.addEventListener('click',()=>selectOption(i));listbox.append(opt)});if(!filtered.length){const empty=document.createElement('div');empty.className='empty-option';empty.textContent='No matching countries';listbox.append(empty)}combo.setAttribute('aria-activedescendant',activeIndex>=0&&filtered[activeIndex]?'country-option-'+activeIndex:'');}
function showCombo(){listbox.hidden=false;combo.setAttribute('aria-expanded','true')}
function closeCombo(revert=false,restore=false){if(revert)combo.value=selectedValue;listbox.hidden=true;combo.setAttribute('aria-expanded','false');activeIndex=-1;combo.setAttribute('aria-activedescendant','');if(restore)combo.focus()}
function updateCombo(){const q=combo.value.trim().toLocaleLowerCase();filtered=countries.filter(c=>c.toLocaleLowerCase().includes(q));activeIndex=-1;renderOptions();showCombo();live.textContent=filtered.length?`${filtered.length} ${filtered.length===1?'suggestion':'suggestions'} available.`:'No matching countries.'}
function moveCombo(delta){if(listbox.hidden){filtered=countries.filter(c=>c.toLocaleLowerCase().includes(combo.value.trim().toLocaleLowerCase()));if(!filtered.length)filtered=[...countries];showCombo()}if(!filtered.length)return;activeIndex=activeIndex<0?(delta>0?0:filtered.length-1):(activeIndex+delta+filtered.length)%filtered.length;renderOptions();const active=$('#'+CSS.escape('country-option-'+activeIndex));active?.scrollIntoView({block:'nearest'});}
function selectOption(i){if(!filtered[i])return;selectedValue=filtered[i];combo.value=selectedValue;live.textContent=`${selectedValue} selected.`;closeCombo(false,false)}
combo.addEventListener('input',()=>{draftValue=combo.value;updateCombo()});combo.addEventListener('focus',()=>{draftValue=combo.value});combo.addEventListener('keydown',e=>{if(e.key==='ArrowDown'){e.preventDefault();moveCombo(1)}else if(e.key==='ArrowUp'){e.preventDefault();moveCombo(-1)}else if(e.key==='Enter'&&!listbox.hidden){e.preventDefault();if(activeIndex>=0)selectOption(activeIndex);else if(filtered.length===1)selectOption(0)}else if(e.key==='Escape'&&!listbox.hidden){e.preventDefault();closeCombo(true,false);live.textContent='Suggestions dismissed.'}else if(e.key==='Escape'&&combo.value!==selectedValue){combo.value=selectedValue;live.textContent='Input reverted to previous selection.'}});
combo.addEventListener('blur',()=>{setTimeout(()=>{if(!listbox.hidden)closeCombo(false,false)},0)});
combo.addEventListener('click',()=>{if(listbox.hidden){draftValue=combo.value;updateCombo()}});
/* Accordion: animate height, then apply hidden after closing completes */
function setAccordion(button,open){const panel=document.getElementById(button.getAttribute('aria-controls'));const reduce=matchMedia('(prefers-reduced-motion: reduce)').matches;button.setAttribute('aria-expanded',String(open));if(panel._finish){panel.removeEventListener('transitionend',panel._finish);panel._finish=null}clearTimeout(panel._timer);if(open){panel.hidden=false;panel.style.height=panel.getBoundingClientRect().height+'px';requestAnimationFrame(()=>{panel.style.height=panel.scrollHeight+'px'});const finish=e=>{if(e.propertyName!=='height'||button.getAttribute('aria-expanded')!=='true')return;panel.style.height='auto';panel.removeEventListener('transitionend',finish);panel._finish=null};if(reduce){panel.style.height='auto'}else{panel._finish=finish;panel.addEventListener('transitionend',finish);panel._timer=setTimeout(()=>{if(button.getAttribute('aria-expanded')==='true')panel.style.height='auto';panel.removeEventListener('transitionend',finish);panel._finish=null},320)}}else{panel.style.height=panel.scrollHeight+'px';requestAnimationFrame(()=>{panel.style.height='0px'});const finish=e=>{if(e.propertyName!=='height'||button.getAttribute('aria-expanded')!=='false')return;panel.hidden=true;panel.style.height='';panel.removeEventListener('transitionend',finish);panel._finish=null};if(reduce){panel.hidden=true;panel.style.height=''}else{panel._finish=finish;panel.addEventListener('transitionend',finish);panel._timer=setTimeout(()=>{if(button.getAttribute('aria-expanded')==='false'){panel.hidden=true;panel.style.height=''}panel.removeEventListener('transitionend',finish);panel._finish=null},320)}}}
$$('.acc-trigger').forEach(button=>button.addEventListener('click',()=>setAccordion(button,button.getAttribute('aria-expanded')!=='true')));
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-6-luna","provider":"openrouter","persona":"full","sessionId":"1373e110-0e9f-4090-b774-a8f5aa23af85","tokensIn":328142,"tokensOut":18689,"tokensTotal":346831,"cost":0.015402585,"turns":17,"toolCalls":22,"failedToolCalls":1,"timestamp":"2026-09-23T00:20:03.034Z"} -->