Hands-on accessibility patterns per UI component type. Keyboard interactions, ARIA attributes, focus management, and screen reader behavior for common components...
<button>, never <div onclick> or <a> for actionsaria-pressed="true|false"aria-label="descriptive text"aria-disabled="true" + aria-busy="true", keep label readableEnter and Space activate<a href>)<button>)<a> without href. Never use <a href="#" onclick> for actions.role="dialog" + aria-modal="true" + aria-labelledby="title-id"Escape closes the modalaria-hidden="true" on content behind modal, or inert attributearia-haspopup="true" + aria-expanded="true|false"role="menu", items: role="menuitem"Enter/Space opens, ArrowDown/ArrowUp navigates, Escape closesrole="tablist"role="tab" + aria-selected="true|false" + aria-controls="panel-id"role="tabpanel" + aria-labelledby="tab-id"ArrowLeft/ArrowRight between tabs, Tab moves into panel contenttabindex="0", inactive tabs: tabindex="-1"role="combobox" + aria-expanded + aria-controls="listbox-id" + aria-autocomplete="list|both"role="listbox" > role="option"aria-activedescendant="option-id" on the inputArrowDown/ArrowUp navigate options, Enter selects, Escape closes listrole="status" (polite) for info, role="alert" (assertive) for errorsaria-live="polite" or aria-live="assertive"<label> with for attribute (or wrapping)aria-describedby pointing to error text + aria-invalid="true"aria-required="true" (or HTML required)<fieldset> + <legend><button> with aria-expanded="true|false" + aria-controls="panel-id"role="region" + aria-labelledby="trigger-id"Enter/Space toggles, standard focus order (no arrow key nav needed)Contain focus within a region (modals, drawers):
Tab from last: move to firstShift+Tab from first: move to lastinert attribute on background content when availableWhen closing an overlay, return focus to the trigger:
document.activeElement before openingFirst focusable element on the page, hidden until focused:
<main> or #content:focus, hidden otherwiseFor widget-internal navigation (tabs, toolbars, menus):
tabindex="0"tabindex="-1"Tab exits the widget entirelyaria-live="polite": announced after current speech finishes (status updates, search results count)aria-live="assertive": interrupts current speech (errors, urgent alerts)For screen-reader-only text:
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Never use display: none or visibility: hidden for SR-only content — those hide from screen readers too.
aria-busy="true" on the container, announce "Loading" via live region<h1> or <main>For every interactive component: