Accessible Web Components — Complete Guide
In this tutorial, you will learn about Accessible Web Components. We cover key concepts, practical examples, and best practices to help you master this topic.
Accessible Web Components require proper ARIA attributes, keyboard handling, focus management, and semantic structure for assistive technology compatibility.
What You'll Learn
- How to add ARIA roles and attributes to custom elements
- How to manage focus within components
- How to handle keyboard navigation
- How to test component Accessibility
Why It Matters
Custom elements have no built-in semantics. A <custom-button> is treated as a generic element by screen readers. You must provide the semantic meaning through ARIA.
flowchart LR A[Accessible Component] --> B[ARIA Attributes] A --> C[Keyboard Support] A --> D[Focus Management] A --> E[Semantic Structure] B --> F[role, aria-label, aria-expanded] C --> G[Enter, Space, Arrow, Escape] D --> H[tabindex, focus()]
ARIA for Custom Elements
class AccessibleButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.btn { padding: 8px 16px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; }
.btn:focus-visible { outline: 2px solid #2ecc71; outline-offset: 2px; }
</style>
<div class="btn" role="button" tabindex="0">
<slot></slot>
</div>
`;
}
connectedCallback() {
const btn = this.shadowRoot.querySelector('.btn');
btn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.click();
}
});
btn.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('btn-click', { bubbles: true, composed: true }));
});
// Inherit ARIA from attributes
if (this.hasAttribute('aria-label')) {
btn.setAttribute('aria-label', this.getAttribute('aria-label'));
}
if (this.hasAttribute('disabled')) {
btn.setAttribute('aria-disabled', 'true');
}
}
get disabled() { return this.hasAttribute('disabled'); }
set disabled(val) {
if (val) {
this.setAttribute('disabled', '');
this.shadowRoot.querySelector('.btn').setAttribute('aria-disabled', 'true');
} else {
this.removeAttribute('disabled');
this.shadowRoot.querySelector('.btn').removeAttribute('aria-disabled');
}
}
}
customElements.define('accessible-button', AccessibleButton);
Focus Management
class AccessibleDialog extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); }
.dialog { background: white; padding: 24px; border-radius: 8px; max-width: 500px; margin: 10vh auto; }
:host([open]) .overlay { display: flex; }
</style>
<div class="overlay" role="dialog" aria-modal="true">
<div class="dialog">
<slot></slot>
</div>
</div>
`;
}
connectedCallback() {
this.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.hasAttribute('open')) {
this.close();
}
});
}
open() {
this.setAttribute('open', '');
this._previousFocus = document.activeElement;
setTimeout(() => {
const firstFocusable = this.shadowRoot.querySelector('button, [tabindex]:not([tabindex="-1"])');
if (firstFocusable) firstFocusable.focus();
}, 100);
}
close() {
this.removeAttribute('open');
if (this._previousFocus) {
this._previousFocus.focus();
}
}
}
customElements.define('accessible-dialog', AccessibleDialog);
Common Mistakes
- Forgetting ARIA roles on custom interactive elements
- Not managing focus when showing/hiding content
- Ignoring keyboard navigation (Enter, Space, Escape, Arrows)
- Missing focus indicators (focus-visible styles)
Practice Questions
- What ARIA role should a custom button have? role="button".
- How do you make a div focusable? tabindex="0".
- What keyboard events must a custom button handle? Enter and Space.
Mini Project
Build an accessible dropdown menu component with proper ARIA (combobox, listbox, option roles), keyboard navigation (Arrow keys, Enter, Escape), and focus management. Test with a screen reader.
What's Next
Lesson 15: Component Communication
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro