-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
203 lines (186 loc) · 10.2 KB
/
index.html
File metadata and controls
203 lines (186 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Combobox - Resonance Labs</title>
<link rel="stylesheet" href="/resonance-labs/assets/style/global.css" />
<link rel="stylesheet" href="combobox.css" />
<link
rel="icon"
href="/resonance-labs/assets/images/favicon.ico"
type="image/x-icon"
/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.css" />
<script src="/resonance-labs/assets/js/component-layout.js" defer></script>
</head>
<body>
<header>
<a href="/resonance-labs/index.html" class="back-link">
<img src="/resonance-labs/assets/images/logo.svg" alt="Resonance Logo" />
</a>
</header>
<main>
<section>
<h1>Combobox</h1>
<p class="component-tagline">A combobox combines a text input with a dropdown listbox, letting users either type a value or select from filtered suggestions.</p>
</section>
<section>
<h2>Demo</h2>
<p>Click or tab into the input field and start typing to filter the list of options. Use the <kbd>Down Arrow</kbd> key to move into the suggestion list, <kbd>Up</kbd> and <kbd>Down</kbd> to navigate between options, <kbd>Enter</kbd> to select one, and <kbd>Escape</kbd> to dismiss the list and return focus to the input.</p>
<div class="mount"></div>
</section>
<section>
<h2>What to Observe</h2>
<ul>
<li>As you type, the suggestion list updates and screen readers announce the number of available options.</li>
<li>Arrow key navigation moves through the list without losing the typed value in the input.</li>
<li>The currently highlighted option is announced by assistive technology as focus moves through the list.</li>
<li>Pressing <kbd>Escape</kbd> closes the list and returns focus cleanly to the text input.</li>
<li>Selecting an option populates the input field and closes the list, communicating the completed selection.</li>
</ul>
</section>
<section>
<h2>Anatomy</h2>
<p class="anatomy-placeholder">[Anatomy image placeholder — will be added when assets are available]</p>
<ol>
<li><strong>Text input</strong> — The editable field where the user types to filter options; carries <code>role="combobox"</code> and controls the popup visibility via <code>aria-expanded</code>.</li>
<li><strong>Label</strong> — A <code><label></code> associated with the input that names the control for all users.</li>
<li><strong>Listbox popup</strong> — The dropdown container with <code>role="listbox"</code> that holds the filterable options.</li>
<li><strong>Option items</strong> — Individual items inside the listbox, each carrying <code>role="option"</code> and an <code>aria-selected</code> state.</li>
<li><strong>Toggle button</strong> — An optional button to open or close the popup without typing, often represented as a chevron icon.</li>
</ol>
</section>
<section>
<h2>Accessibility Behavior</h2>
<ul>
<li>The input must carry <code>aria-expanded</code> to communicate whether the suggestion list is open or closed.</li>
<li>The active option in the list must be indicated via <code>aria-activedescendant</code> on the input, so screen readers track focus without moving DOM focus out of the input.</li>
<li>Each option must have <code>role="option"</code> and an <code>aria-selected</code> attribute that updates when the option is chosen.</li>
<li>The number of available suggestions should be announced when the list opens or updates.</li>
<li>Keyboard users must be able to open, navigate, select from, and close the list without a mouse.</li>
<li>If no results match the typed value, that empty state must be communicated rather than silently hiding all options.</li>
</ul>
</section>
<section>
<h2>Common Mistakes</h2>
<ul>
<li>Omitting <code>aria-expanded</code> on the input, leaving screen reader users unaware that a suggestion list exists.</li>
<li>Moving DOM focus into the listbox instead of using <code>aria-activedescendant</code>, which clears the typed text and disorients users.</li>
<li>Filtering options visually but not updating the accessible list, so screen readers announce stale or hidden items.</li>
<li>Failing to handle <kbd>Escape</kbd>, trapping keyboard users inside an open list they cannot dismiss.</li>
<li>Using a plain <code><ul></code> without ARIA roles, making the suggestion list invisible to assistive technology.</li>
</ul>
</section>
<section>
<h2>Why This Matters</h2>
<p>Comboboxes are frequently used in search bars, address fields, and product filters — high-traffic interactions where inaccessibility has a large impact. When the ARIA states are missing or incorrect, screen reader users cannot tell whether a list is open, which option is highlighted, or whether their selection was accepted. This effectively prevents them from completing tasks that sighted users accomplish in seconds, reinforcing a two-tiered experience on the most visible parts of a product.</p>
</section>
<section>
<h2>Accessibility Validation</h2>
<p>This component is validated against internal accessibility criteria aligned with WCAG standards, using our internally developed system, <strong>Resonance Specs</strong>.</p>
<p>To learn more, please contact us.</p>
</section>
<section>
<h2>Code</h2>
<div class="code-tabs" role="tablist">
<button class="code-tabs__tab code-tabs__tab--active" role="tab" aria-selected="true" data-tab="html">HTML</button>
<button class="code-tabs__tab" role="tab" aria-selected="false" data-tab="css">CSS</button>
<button class="code-tabs__tab" role="tab" aria-selected="false" data-tab="js">JS</button>
</div>
<div class="code-preview" id="panel-html" role="tabpanel">
<pre class="line-numbers"><code id="code-html" class="language-markup"></code></pre>
<button class="code-preview__copy" data-target="code-html">Copy Code</button>
</div>
<div class="code-preview" id="panel-css" role="tabpanel" hidden>
<pre class="line-numbers"><code id="code-css" class="language-css"></code></pre>
<button class="code-preview__copy" data-target="code-css">Copy Code</button>
</div>
<div class="code-preview" id="panel-js" role="tabpanel" hidden>
<pre class="line-numbers"><code id="code-js" class="language-javascript"></code></pre>
<button class="code-preview__copy" data-target="code-js">Copy Code</button>
</div>
</section>
<section>
<h2>Reference Implementation</h2>
<ul class="btn-group">
<li>
<a class="btn btn-primary" href="/resonance-labs/index.html">Back to Home</a>
</li>
<li>
<a
class="btn btn-secondary"
href="https://github.com/Accenture/resonance-labs"
>GitHub Code</a
>
</li>
</ul>
</section>
</main>
<footer>
<p><strong>Created and maintained by Accenture Song</strong></p>
<p>© 2026 Accenture - Resonance</p>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
<script>
// Fetch HTML fragment
fetch("./combobox.html")
.then(function (r) { return r.text(); })
.then(function (html) {
document.querySelector(".mount").innerHTML = html;
// Populate HTML code preview (strip leading comment block)
var rawHtml = html.replace(/<!--[\s\S]*?-->\n*/, "").trim();
var codeHtml = document.getElementById("code-html");
codeHtml.textContent = rawHtml;
Prism.highlightElement(codeHtml);
// Load component JS
var s = document.createElement("script");
s.src = "./combobox.js";
document.body.appendChild(s);
});
// Fetch CSS
fetch("./combobox.css")
.then(function (r) { return r.text(); })
.then(function (css) {
var codeCss = document.getElementById("code-css");
codeCss.textContent = css;
Prism.highlightElement(codeCss);
});
// Fetch JS
fetch("./combobox.js")
.then(function (r) { return r.text(); })
.then(function (js) {
var codeJs = document.getElementById("code-js");
codeJs.textContent = js;
Prism.highlightElement(codeJs);
});
// Tab switching
var tabs = document.querySelectorAll(".code-tabs__tab");
var codePanels = document.querySelectorAll("#panel-html, #panel-css, #panel-js");
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
tabs.forEach(function (t) {
t.classList.remove("code-tabs__tab--active");
t.setAttribute("aria-selected", "false");
});
codePanels.forEach(function (p) { p.hidden = true; });
tab.classList.add("code-tabs__tab--active");
tab.setAttribute("aria-selected", "true");
document.getElementById("panel-" + tab.dataset.tab).hidden = false;
});
});
// Copy buttons
document.querySelectorAll(".code-preview__copy").forEach(function (btn) {
btn.addEventListener("click", function () {
var codeEl = document.getElementById(btn.dataset.target);
navigator.clipboard.writeText(codeEl.textContent).then(function () {
btn.textContent = "Copied!";
setTimeout(function () { btn.textContent = "Copy Code"; }, 2000);
});
});
});
</script>
</body>
</html>