Navigation and Menus

Museum Guide Command Palette

Complete the command palette for a museum tour scenario: includes real content, interactive controls, clear state feedback, and responsive layouts.

Navigation and MenusCommand PaletteHTMLCSSJavaScript
With JavaScript enabled, you can edit, run, and save this example. The fixed source is as follows:
<!doctype html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Museum Guide Command Palette</title>
  <style>
    :root { --ink:#102c44; --paper:#eef6f8; --accent:#e05263; --secondary:#79b7c7; --line:color-mix(in srgb, var(--ink) 18%, transparent); --shadow:0 24px 60px color-mix(in srgb, var(--ink) 16%, transparent); color-scheme:light; }
    * { box-sizing:border-box; }
    html { min-width:320px; background:var(--paper); }
    body { min-height:100vh; margin:0; overflow-x:hidden; color:var(--ink); font-family:Didot,"Songti SC",serif; background:linear-gradient(30deg,transparent 48%,color-mix(in srgb,var(--accent) 9%,transparent) 49% 51%,transparent 52%),var(--paper);background-size:52px 90px; }
    button,input,select,textarea { color:inherit; font:inherit; }
    button,[role="button"],input,select,textarea { accent-color:var(--accent); }
    button { cursor:pointer; }
    button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible { outline:3px solid var(--accent); outline-offset:3px; }
    [hidden] { display:none !important; }
    @media (max-width:700px) { .mast h1 { line-height:1.08 !important; letter-spacing:-.035em !important; overflow-wrap:anywhere; } }
    @media (prefers-reduced-motion:reduce) { *,*::before,*::after { scroll-behavior:auto !important; animation-duration:.01ms !important; animation-iteration-count:1 !important; transition-duration:.01ms !important; } }
    .page{width:min(1040px,calc(100% - 36px));margin:auto;padding:64px 0}.mast{display:grid;justify-items:center;text-align:center;border-block:3px double var(--accent);padding:26px;gap:20px;margin-bottom:32px}.mast h1{font-family:Didot,"Songti SC",serif;font-weight:700;font-size:clamp(38px,8vw,86px);letter-spacing:.035em;margin:0;line-height:1}.eyebrow{display:block;color:var(--accent);font:800 11px/1 ui-monospace,monospace;letter-spacing:.3em}.intro{max-width:600px;margin-inline:auto;line-height:1.8}.scenario{padding:8px 26px;border-inline:1px solid var(--accent);letter-spacing:.16em}.demo{border:4px double var(--accent);border-radius:0;clip-path:polygon(24px 0,calc(100% - 24px) 0,100% 24px,100% calc(100% - 24px),calc(100% - 24px) 100%,24px 100%,0 calc(100% - 24px),0 24px);position:relative;min-height:440px;overflow:hidden;padding:clamp(28px,5vw,60px);background:var(--paper)}@media(max-width:700px){.page{width:calc(100% - 20px);padding:24px 0}.mast{padding:18px}.demo{padding:18px}}
    .panel{padding:24px;border-block:1px solid var(--ink);background:transparent}.stack{display:grid;gap:22px}.row{display:flex;flex-wrap:wrap;gap:16px}.action{padding:8px 0;border:0;border-bottom:1px solid currentColor;background:transparent;letter-spacing:.08em}.action.primary,.action.is-active{color:var(--accent);border-bottom-width:4px}.status{min-height:28px;margin-top:20px;font-style:italic}.metric{font-family:Didot,serif;font-size:clamp(38px,8vw,76px)}.muted{max-width:54ch;line-height:1.8}.meter{position:relative;height:3px;background:var(--line)}.meter>i{position:absolute;inset:0 auto 0 0;background:var(--accent)}.chip{padding-bottom:3px;border-bottom:1px solid var(--ink);font-size:13px}
    .composition-gallery{margin:0;padding:clamp(8px,2vw,18px);border:1px solid var(--ink);background:color-mix(in srgb,var(--paper) 78%,transparent)}.gallery-caption{display:grid;grid-template-columns:auto 1fr auto;gap:16px;align-items:center;padding:15px 4px 2px;font-size:12px}.gallery-caption::before,.gallery-caption::after{content:"";height:1px;background:var(--line)}.gallery-caption strong{font:800 11px/1.2 ui-monospace,monospace;letter-spacing:.16em;text-transform:uppercase}.composition-gallery .demo{box-shadow:none}.composition-gallery .demo::before{content:"";position:absolute;inset:10px;border:1px dashed color-mix(in srgb,var(--accent) 36%,transparent);pointer-events:none}
    .command{max-width:720px;margin:auto;padding:0;overflow:hidden}.command-input{display:grid;grid-template-columns:auto 1fr auto;gap:12px;align-items:center;padding:18px;border-bottom:1px solid var(--line)}.command-input input{width:100%;border:0;outline:0;background:transparent;font-size:18px}.sr{display:block}.sr>span{position:absolute;clip:rect(0 0 0 0)}kbd{padding:3px 7px;border:1px solid var(--line);border-radius:6px;background:var(--paper);font:700 11px/1.2 ui-monospace,monospace}.command-list{display:grid;max-height:320px;overflow:auto;padding:10px}.command-list button{display:flex;justify-content:space-between;gap:16px;padding:13px;border:0;border-radius:10px;text-align:left;background:transparent}.command-list button:hover,.command-list button:focus{background:color-mix(in srgb,var(--secondary) 35%,transparent)}.command-list span{display:grid;gap:4px}.command-list small{color:color-mix(in srgb,var(--ink) 60%,transparent)}.workflow-dispatch{display:grid;grid-template-columns:minmax(210px,.48fr) minmax(0,1.52fr);gap:18px}.dispatch-console{display:grid;align-content:start;gap:16px;padding:22px;border:1px solid var(--line);border-radius:18px;background:color-mix(in srgb,var(--secondary) 12%,var(--paper))}.dispatch-console h2{margin:.2em 0}.dispatch-console form,.dispatch-console label{display:grid;gap:7px}.dispatch-console input,.dispatch-console select{min-width:0;width:100%;padding:10px;border:1px solid var(--line);border-radius:8px;background:var(--paper)}.dispatch-console fieldset{display:flex;gap:12px;border:1px solid var(--line);border-radius:10px}.dispatch-console fieldset label{display:flex;align-items:center}.dispatch-workbench{--dispatch-load:.22;min-width:0;padding:14px;border-left:8px solid color-mix(in srgb,var(--accent) calc(var(--dispatch-load)*100%),var(--line));background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 8%,transparent),transparent 28%)}.dispatch-workbench[data-dispatch-priority="urgent"]{box-shadow:inset 0 0 0 2px var(--accent)}@media(max-width:900px){.workflow-dispatch{grid-template-columns:1fr}.dispatch-console{order:2}}
  </style>
</head>
<body data-aesthetic="art-deco" data-composition="gallery-frame">
  <main class="page">
    <header class="mast"><div><span class="eyebrow">art-deco / Command palette / INTERMEDIATE</span><h1>Museum Guide Command Palette</h1><p class="intro">Complete the command palette for a museum tour scenario: includes real content, interactive controls, clear state feedback, and responsive layouts.</p></div><span class="scenario">Museum Guide</span></header>
    <figure class="composition-gallery"><section class="demo" aria-label="Museum Guide Command Palette Interactive Area"><div class="workflow-dispatch"><aside class="dispatch-console"><span class="eyebrow">Museum Guide DISPATCH</span><h2>Visitor Route Control Desk</h2><form id="workflowDispatchForm"><label>Task Type<select id="workflowDispatchKind"><option>Permanent Exhibition Hall</option><option>Special Exhibition Hall</option><option>Family and Children’s Hall</option></select></label><label>Dwell Time<input id="workflowDispatchAmount" type="number" min="1" max="9999" value="27"><span>minutes</span></label><fieldset><legend>Processing priority</legend><label><input type="radio" name="workflowPriority" value="normal" checked>Regular</label><label><input type="radio" name="workflowPriority" value="urgent">Urgent</label></fieldset><button class="action primary" type="submit">Start Tour</button></form><label>Current task<select id="workflowDispatchActive" aria-label="Switch current task"><option value="1">Permanent Exhibition Hall</option><option value="2">Special Exhibition Hall</option></select></label><output id="workflowDispatchSummary">Waiting for Dispatch</output></aside><section id="workflowCore" class="workflow-core dispatch-workbench" data-dispatch-priority="normal"><!-- 11DZ_CORE_START:navigation-menus-command-palette --><div class="command panel"><div class="command-input"><span aria-hidden="true">⌘</span><label class="sr"><span>Search commands</span><input id="commandQuery" type="search" placeholder="Enter a command or keyword" autocomplete="off" aria-controls="commandList"></label><kbd>Esc</kbd></div><div id="commandList" class="command-list" role="listbox" aria-label="Available Commands"><button type="button" role="option" aria-selected="true" tabindex="0" data-core-bridge="navigation-menus-command-palette"><span><b>Museum guide inspiration</b><small>Execute quick actions</small></span><kbd>1</kbd></button><button type="button" role="option" aria-selected="false" tabindex="-1"><span><b>Museum guide log</b><small>Navigate to Content</small></span><kbd>2</kbd></button><button type="button" role="option" aria-selected="false" tabindex="-1"><span><b>Museum Guide Collaboration</b><small>Execute quick actions</small></span><kbd>3</kbd></button><button type="button" role="option" aria-selected="false" tabindex="-1"><span><b>Museum guide progress</b><small>Navigate to Content</small></span><kbd>4</kbd></button><button type="button" role="option" aria-selected="false" tabindex="-1"><span><b>Museum guide reminder</b><small>Execute quick actions</small></span><kbd>5</kbd></button><button type="button" role="option" aria-selected="false" tabindex="-1"><span><b>Museum Guide Collection</b><small>Navigate to Content</small></span><kbd>6</kbd></button><button type="button" role="option" aria-selected="false" tabindex="-1"><span><b>Museum Guide Overview</b><small>Execute quick actions</small></span><kbd>7</kbd></button></div><div id="commandEmpty" hidden>No matching commands</div></div><!-- 11DZ_CORE_END --></section></div><p id="status" class="status" role="status" aria-live="polite"></p></section><figcaption class="gallery-caption"><strong>Museum Guide / GALLERY 03</strong></figcaption></figure>
  </main>
  <script>
    'use strict';
    const statusOutput = document.querySelector('#status');
    const announce = message => { statusOutput.textContent = message; };
    /* 11DZ_CORE_SCRIPT_START:navigation-menus-command-palette */
const commandInput = document.querySelector('#commandQuery'); const commands = [...document.querySelectorAll('#commandList button')]; const emptyCommand = document.querySelector('#commandEmpty'); let activeCommand=0;
    function selectCommand(index,focus=false){const visible=commands.filter(command=>!command.hidden);if(!visible.length)return;const command=visible[(index+visible.length)%visible.length];commands.forEach(item=>{const selected=item===command;item.setAttribute('aria-selected',String(selected));item.tabIndex=selected?0:-1;});activeCommand=visible.indexOf(command);if(focus)command.focus();}
    function filterCommands() { const query = commandInput.value.normalize('NFKC').trim().toLowerCase(); let count = 0; commands.forEach(command => { const searchable = command.querySelector('b').textContent.normalize('NFKC').toLowerCase(); command.hidden = Boolean(query && !searchable.includes(query)); if (!command.hidden) count += 1; }); emptyCommand.hidden = count !== 0; activeCommand=0;selectCommand(0);announce('Available Commands ' + count + ' item'); }
    commandInput.addEventListener('input',filterCommands); document.addEventListener('keydown',event => { if (event.key === 'Escape') { commandInput.value = ''; filterCommands(); commandInput.focus(); } });document.querySelector('#commandList').addEventListener('keydown',event=>{if(!['ArrowDown','ArrowUp','Home','End'].includes(event.key))return;event.preventDefault();const visible=commands.filter(command=>!command.hidden);const next=event.key==='Home'?0:event.key==='End'?visible.length-1:activeCommand+(event.key==='ArrowDown'?1:-1);selectCommand(next,true);}); commands.forEach(command => {command.addEventListener('focus',()=>selectCommand(commands.filter(item=>!item.hidden).indexOf(command)));command.addEventListener('click',() => announce('Executed:' + command.querySelector('b').textContent));});
/* 11DZ_CORE_SCRIPT_END */
const workflowBridgeSpec={"name":"navigation-menus-command-palette","selector":"[data-core-bridge=\"navigation-menus-command-palette\"]","mode":"click","key":"Enter"};const workflowBridgeCore=document.querySelector('#workflowCore');const workflowBridgeControl=workflowBridgeCore.querySelector(workflowBridgeSpec.selector);if(!workflowBridgeControl)throw new Error('Missing core bridge: '+workflowBridgeSpec.name);const workflowBridgeControlledId=workflowBridgeControl.getAttribute('aria-controls');const workflowBridgeControlled=workflowBridgeControlledId?document.getElementById(workflowBridgeControlledId):null;workflowBridgeCore.dataset.workflowBridge=workflowBridgeSpec.name;workflowBridgeCore.dataset.workflowVersion='0';function workflowBridgeSerialize(){const visualNode=workflowBridgeCore.firstElementChild||workflowBridgeCore;const visual=getComputedStyle(visualNode);const outputs=[...workflowBridgeCore.querySelectorAll('output,[role="status"],[aria-live],meter,progress')].map(node=>node.textContent.trim()+'|'+node.getAttribute('aria-valuenow')+'|'+node.getAttribute('value'));const canvas=[...workflowBridgeCore.querySelectorAll('canvas')].map(node=>{try{return node.toDataURL().slice(-160);}catch{return 'canvas-unavailable';}});return JSON.stringify({html:workflowBridgeCore.innerHTML,status:statusOutput.textContent,outputs,canvas,visual:[visual.display,visual.visibility,visual.opacity,visual.transform,visual.filter,visual.backgroundColor,visual.width,visual.height],scroll:workflowBridgeSpec.mode==='scroll'?workflowBridgeControl.scrollTop:null});}let workflowBridgeLastEffect=workflowBridgeSerialize();function workflowBridgeSummary(){const label=(workflowBridgeControl.getAttribute('aria-label')||workflowBridgeControl.textContent||workflowBridgeControl.value||workflowBridgeSpec.name).trim().replace(/\s+/g,' ').slice(0,72);return label+' · Core Version '+workflowBridgeCore.dataset.workflowVersion;}function workflowBridgeCommit(){const effect=workflowBridgeSerialize();if(effect===workflowBridgeLastEffect)return {changed:false,summary:workflowBridgeSummary()};workflowBridgeLastEffect=effect;workflowBridgeCore.dataset.workflowVersion=String(Number(workflowBridgeCore.dataset.workflowVersion||0)+1);return {changed:true,summary:workflowBridgeSummary()};}function workflowBridgeScheduleCommit(){queueMicrotask(workflowBridgeCommit);setTimeout(workflowBridgeCommit,0);}workflowBridgeControl.addEventListener("click",workflowBridgeScheduleCommit);const workflowBridgeStateTargets=new Set(workflowBridgeCore.querySelectorAll('dialog'));if(workflowBridgeControlled&&workflowBridgeCore.contains(workflowBridgeControlled))workflowBridgeStateTargets.add(workflowBridgeControlled);workflowBridgeStateTargets.forEach(target=>['close','cancel','toggle'].forEach(eventName=>target.addEventListener(eventName,workflowBridgeScheduleCommit)));function workflowBridgeActivate(){workflowBridgeControl.focus();workflowBridgeControl.click();const result=workflowBridgeCommit();setTimeout(workflowBridgeCommit,0);return result;}function workflowBridgeReset(){workflowBridgeCore.dataset.workflowVersion='0';workflowBridgeLastEffect=workflowBridgeSerialize();}
const workflowDispatchCore=document.querySelector('#workflowCore');const workflowDispatchForm=document.querySelector('#workflowDispatchForm');const workflowDispatchSelect=document.querySelector('#workflowDispatchActive');const workflowDispatchJobs=new Map([["1",{"label":"Permanent Exhibition Hall","amount":12,"priority":"normal","touches":0}],["2",{"label":"Special Exhibition Hall","amount":21,"priority":"urgent","touches":0}]]);let workflowDispatchSerial=workflowDispatchJobs.size;function workflowDispatchDrive(job,activate=false){workflowDispatchCore.dataset.dispatchPriority=job.priority;workflowDispatchCore.style.setProperty('--dispatch-load',Math.min(.95,.16+job.amount/120));const bridge=activate?workflowBridgeActivate():{changed:false,summary:workflowBridgeSummary()};document.querySelector('#workflowDispatchSummary').textContent=job.label+' · '+job.amount+' minutes · '+bridge.summary;announce('Current task for the visit route:'+job.label);}function workflowDispatchRender(activeId,activate=false){workflowDispatchSelect.replaceChildren(...[...workflowDispatchJobs].map(([id,job])=>{const option=document.createElement('option');option.value=id;option.textContent=job.label+(job.priority==='urgent'?' · Urgent':'');return option;}));workflowDispatchSelect.value=activeId;workflowDispatchDrive(workflowDispatchJobs.get(activeId),activate);}workflowDispatchForm.addEventListener('submit',event=>{event.preventDefault();const data=new FormData(workflowDispatchForm);const id=String(++workflowDispatchSerial);workflowDispatchJobs.set(id,{label:document.querySelector('#workflowDispatchKind').value,amount:Math.max(1,Number(document.querySelector('#workflowDispatchAmount').value)||1),priority:data.get('workflowPriority'),touches:0});workflowDispatchRender(id,true);});workflowDispatchSelect.addEventListener('change',()=>workflowDispatchDrive(workflowDispatchJobs.get(workflowDispatchSelect.value),true));workflowDispatchCore.addEventListener('focusin',()=>{const job=workflowDispatchJobs.get(workflowDispatchSelect.value);if(!job)return;job.touches+=1;document.querySelector('#workflowDispatchSummary').textContent=job.label+' · '+job.amount+' minutes · '+workflowBridgeSummary();});workflowDispatchRender('1',false);
workflowBridgeReset();
  </script>
</body>
</html>
Page feedbackFound an issue or have suggestions for improvement?
Feedback features need to be used in the online version

Please enable JavaScript in the online version before submitting; local features of the current tool or example are not affected.

Go to the online version for feedback