Web Components
Use compa11y in any HTML page with custom elements from @compa11y/web. No build tools required.
Setup
Add a single script tag to get started. All components register automatically.
<!-- Via unpkg -->
<script src="https://unpkg.com/@compa11y/web"></script>
<!-- Via jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/@compa11y/web"></script>
<!-- Or install via npm -->
npm install @compa11y/webStyling
Web Components use Shadow DOM. Style them using CSS custom properties or the ::part() pseudo-element.
/* CSS custom properties */
compa11y-switch {
--compa11y-switch-checked-bg: #10b981;
--compa11y-switch-width: 3rem;
}
/* Shadow DOM parts */
compa11y-button::part(button) {
border-radius: 9999px;
font-weight: 600;
}Components
<compa11y-button>
Accessible button with variants, loading state, and keyboard support. Renders a spinner with aria-hidden and visually hidden 'Loading' text when loading.
Attributes
variantsizedisabledloadingaria-labelSlots
(default - button content)<compa11y-button variant="primary" size="md">Save Changes</compa11y-button>
<compa11y-button loading aria-label="Saving">Save</compa11y-button>
<compa11y-button disabled>Cannot Click</compa11y-button><compa11y-input>
Text input with auto-associated label, hint, error state (aria-invalid, role='alert'), and required support.
Attributes
labelhinterrortypeplaceholdervaluedisabledrequiredEvents
inputchangecompa11y-input-change<compa11y-input
label="Email"
type="email"
hint="We won't share your email"
required
></compa11y-input>
<compa11y-input
label="Username"
error="Username is already taken"
></compa11y-input><compa11y-textarea>
Multi-line text input with the same label, hint, and error pattern as compa11y-input.
Attributes
labelhinterrorrowsplaceholdervaluedisabledrequiredEvents
inputchangecompa11y-textarea-change<compa11y-textarea
label="Description"
hint="Maximum 500 characters"
rows="4"
required
></compa11y-textarea><compa11y-checkbox>
Checkbox with indeterminate state, label association, and screen reader announcements. Group with compa11y-checkbox-group for fieldset/legend semantics.
Attributes
labelcheckedindeterminatedisabledvaluenamerequiredEvents
changecompa11y-checkbox-change<compa11y-checkbox label="Accept terms" required></compa11y-checkbox>
<compa11y-checkbox label="Select all" indeterminate></compa11y-checkbox>
<!-- Group with fieldset + legend -->
<compa11y-checkbox-group legend="Select toppings">
<compa11y-checkbox value="cheese" label="Cheese"></compa11y-checkbox>
<compa11y-checkbox value="peppers" label="Peppers"></compa11y-checkbox>
</compa11y-checkbox-group><compa11y-radio-group>
Radio group with roving tabindex, arrow key navigation, and selection announcement. Each radio is an compa11y-radio child.
Attributes
legendvalueorientationdisabledrequiredSlots
(default - compa11y-radio elements)Events
changecompa11y-radiogroup-change<compa11y-radio-group legend="Size" orientation="horizontal" value="md">
<compa11y-radio value="sm" label="Small"></compa11y-radio>
<compa11y-radio value="md" label="Medium"></compa11y-radio>
<compa11y-radio value="lg" label="Large"></compa11y-radio>
</compa11y-radio-group><compa11y-switch>
Toggle switch with role='switch', label, and screen reader state announcements ('on' / 'off').
Attributes
labelcheckeddisabledsizevaluenameEvents
changeMethods
toggle()setChecked(boolean)<compa11y-switch label="Dark mode"></compa11y-switch>
<compa11y-switch label="Enable notifications" checked></compa11y-switch>
<compa11y-switch size="lg" label="Auto-save"></compa11y-switch>
<script>
document.querySelector("compa11y-switch")
.addEventListener("change", (e) => {
console.log("Checked:", e.target.checked);
});
</script><compa11y-select>
Dropdown select following the ARIA combobox/listbox pattern. Supports keyboard navigation, type-ahead, and auto-positioning above or below the trigger.
Attributes
placeholdervaluedisabledaria-labelSlots
(default - option elements)Events
changecompa11y-select-opencompa11y-select-closecompa11y-select-changeMethods
show()close()<compa11y-select placeholder="Choose a size" aria-label="Select size">
<option value="sm">Small</option>
<option value="md">Medium</option>
<option value="lg" disabled>Large (unavailable)</option>
</compa11y-select><compa11y-combobox>
Searchable combobox with real-time filtering, keyboard navigation, and result count announcements as the user types.
Attributes
placeholdervalueclearabledisabledaria-labelSlots
(default - option elements)Events
compa11y-combobox-opencompa11y-combobox-closecompa11y-combobox-selectcompa11y-combobox-changecompa11y-combobox-clearMethods
show()close()clear()<compa11y-combobox placeholder="Search fruits..." clearable aria-label="Search fruits">
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="cherry">Cherry</option>
</compa11y-combobox><compa11y-listbox>
Persistent listbox (always visible) with single and multi-select modes, grouping via compa11y-optgroup, and full keyboard support including Shift+Arrow range selection.
Attributes
valuemultipleorientationdisabledaria-labelSlots
(default - compa11y-option and compa11y-optgroup elements)Events
changecompa11y-listbox-change<!-- Single select -->
<compa11y-listbox aria-label="Favorite fruit" value="apple">
<compa11y-option value="apple">Apple</compa11y-option>
<compa11y-option value="banana">Banana</compa11y-option>
</compa11y-listbox>
<!-- Multi-select with groups -->
<compa11y-listbox multiple aria-label="Toppings">
<compa11y-optgroup label="Meats">
<compa11y-option value="pepperoni">Pepperoni</compa11y-option>
<compa11y-option value="sausage">Sausage</compa11y-option>
</compa11y-optgroup>
<compa11y-optgroup label="Vegetables">
<compa11y-option value="peppers">Peppers</compa11y-option>
<compa11y-option value="olives">Olives</compa11y-option>
</compa11y-optgroup>
</compa11y-listbox><compa11y-dialog>
Modal dialog with focus trapping, scroll lock, focus restoration to trigger, and screen reader announcements on open/close.
Attributes
opentriggerclose-on-outside-clickclose-on-escapeSlots
titledescriptionactions(default)Events
compa11y-dialog-opencompa11y-dialog-closeMethods
show()close()<button id="open-btn">Open Dialog</button>
<compa11y-dialog trigger="#open-btn">
<h2 slot="title">Confirm Action</h2>
<p slot="description">This cannot be undone.</p>
<p>Are you sure you want to proceed?</p>
<div slot="actions">
<button>Cancel</button>
<button>Confirm</button>
</div>
</compa11y-dialog><compa11y-tabs>
Tab interface with roving tabindex, arrow key navigation, automatic or manual activation mode, and orientation support.
Attributes
orientationactivation-modeselected-indexSlots
tabpanelEvents
compa11y-tabs-changeMethods
select(index)next()previous()<compa11y-tabs>
<button slot="tab" role="tab" aria-controls="panel-1">Profile</button>
<button slot="tab" role="tab" aria-controls="panel-2">Settings</button>
<div slot="panel" role="tabpanel" id="panel-1">Profile content</div>
<div slot="panel" role="tabpanel" id="panel-2">Settings content</div>
</compa11y-tabs>
<!-- Vertical with manual activation -->
<compa11y-tabs orientation="vertical" activation-mode="manual">
<!-- ... -->
</compa11y-tabs><compa11y-skip-link>
Navigation aid for keyboard users. Visually hidden until focused; focuses and scrolls to the target element on activation.
Attributes
targetlabelSlots
(default - link label text)<!-- Place as the first child of <body> -->
<compa11y-skip-link target="#main-content">Skip to main content</compa11y-skip-link>
<nav><!-- navigation --></nav>
<main id="main-content"><!-- content --></main>
<!-- Multiple skip links -->
<compa11y-skip-link target="#main-content">Skip to content</compa11y-skip-link>
<compa11y-skip-link target="#search">Skip to search</compa11y-skip-link><compa11y-alert>
Static feedback element. Uses role='alert' (assertive) for error/warning and role='status' (polite) for info/success. Optional dismiss button.
Attributes
typetitledismissibleSlots
(default - alert body content)Events
dismissMethods
dismiss()<!-- Error alert — assertive live region -->
<compa11y-alert type="error" title="Payment failed">
Your card was declined. Please try a different payment method.
</compa11y-alert>
<!-- Success alert — polite live region -->
<compa11y-alert type="success" title="Saved!">
Your changes have been saved successfully.
</compa11y-alert>
<!-- Dismissible info alert -->
<compa11y-alert type="info" dismissible>
A new version is available.
</compa11y-alert>
<script>
document.querySelector("compa11y-alert")
.addEventListener("dismiss", () => console.log("dismissed"));
</script><compa11y-toast>
Notification container with ARIA live region. Toasts use role='alert' for errors/warnings and role='status' for info/success. Timer pauses on hover/focus.
Attributes
positiondurationmax-toastsEvents
compa11y-toast-addcompa11y-toast-removeMethods
add(options)remove(id)clear()<compa11y-toast position="bottom-right" duration="5000" max-toasts="5"></compa11y-toast>
<script>
const toast = document.querySelector("compa11y-toast");
toast.add({ type: "success", title: "Saved", description: "Changes saved." });
toast.add({
type: "error",
title: "Error",
description: "Something went wrong.",
duration: 0, // Won't auto-dismiss
});
toast.add({
type: "info",
title: "Update available",
action: { label: "Reload", onClick: () => location.reload() },
});
toast.clear();
</script><compa11y-link>
Accessible anchor with automatic rel='noopener noreferrer' for external links, screen reader '(opens in new tab)' text, and underline variants.
Attributes
hrefexternalunderlinedisabledSlots
(default - link content)<compa11y-link href="/docs">Documentation</compa11y-link>
<compa11y-link href="https://github.com" external>
GitHub
</compa11y-link>
<compa11y-link href="/pricing" underline="hover">Pricing</compa11y-link><compa11y-tooltip>
Lightweight tooltip using role='tooltip' with aria-describedby. Opens on hover and keyboard focus, dismisses on Escape.
Attributes
labelsidealigndelay-opendelay-closedisabledSlots
(default - trigger element)<compa11y-tooltip label="Save your changes" side="top">
<compa11y-button variant="primary">Save</compa11y-button>
</compa11y-tooltip>
<compa11y-tooltip label="Delete permanently" side="bottom">
<compa11y-button variant="danger">Delete</compa11y-button>
</compa11y-tooltip><compa11y-drawer>
Side panel overlay (modal dialog) with focus trapping, slide animation from any edge, and focus restoration on close.
Attributes
opensidetriggerclose-on-outside-clickclose-on-escapeSlots
titledescriptionactions(default)Events
compa11y-drawer-opencompa11y-drawer-closeMethods
show()close()<compa11y-button id="open-drawer">Open Settings</compa11y-button>
<compa11y-drawer trigger="#open-drawer" side="right">
<h2 slot="title">Settings</h2>
<p slot="description">Manage your preferences.</p>
<p>Drawer content goes here.</p>
<div slot="actions">
<compa11y-button variant="outline">Cancel</compa11y-button>
<compa11y-button variant="primary">Save</compa11y-button>
</div>
</compa11y-drawer><compa11y-slider>
Range input with single or dual thumbs, ARIA valuemin/valuemax/valuenow, and full keyboard support (Arrow, Home, End, PageUp/PageDown).
Attributes
valueminmaxstepdisabledorientationaria-labelEvents
changecompa11y-slider-change<!-- Single value -->
<compa11y-slider value="50" min="0" max="100" aria-label="Volume"></compa11y-slider>
<!-- Range slider -->
<compa11y-slider value="20,80" min="0" max="200" step="5" aria-label="Price range"></compa11y-slider><compa11y-progress-bar>
Progress indicator with role='progressbar', aria-valuenow, and status variants. Omit value for indeterminate mode.
Attributes
valuestatusshow-labelstripedanimatedaria-label<!-- Determinate -->
<compa11y-progress-bar value="65" show-label aria-label="Upload progress"></compa11y-progress-bar>
<!-- Success -->
<compa11y-progress-bar value="100" status="success" aria-label="Build complete"></compa11y-progress-bar>
<!-- Indeterminate -->
<compa11y-progress-bar aria-label="Loading"></compa11y-progress-bar><compa11y-skeleton>
Loading placeholder with aria-busy='true'. Supports text, circular, and rectangular variants with wave or pulse animation.
Attributes
variantwidthheightcountanimation<!-- Text lines -->
<compa11y-skeleton variant="text" count="3"></compa11y-skeleton>
<!-- Avatar circle -->
<compa11y-skeleton variant="circular" width="48" height="48"></compa11y-skeleton>
<!-- Image placeholder -->
<compa11y-skeleton variant="rectangular" width="100%" height="200"></compa11y-skeleton><compa11y-empty-state>
Semantic empty state with heading, description, and optional action slot. Proper heading hierarchy for screen readers.
Attributes
titledescriptionheading-levelSlots
iconaction<compa11y-empty-state
title="No results found"
description="Try adjusting your search or filter."
>
<compa11y-button slot="action" variant="primary">Clear filters</compa11y-button>
</compa11y-empty-state><compa11y-number-field>
Numeric input with role='spinbutton', increment/decrement buttons, and min/max/step constraints. Arrow Up/Down to adjust.
Attributes
valueminmaxstepprecisiondisabledlabelEvents
changecompa11y-numberfield-changeMethods
increment()decrement()<compa11y-number-field label="Quantity" value="1" min="1" max="99"></compa11y-number-field>
<compa11y-number-field label="Price ($)" value="9.99" min="0" step="0.01" precision="2"></compa11y-number-field><compa11y-search-field>
Search input wrapped in role='search' landmark with clear button, Escape to clear, and Enter to submit.
Attributes
valueplaceholderdisabledaria-labelEvents
inputsearchclearcompa11y-search-changeMethods
clear()<compa11y-search-field
placeholder="Search components..."
aria-label="Search components"
></compa11y-search-field>
<script>
document.querySelector("compa11y-search-field")
.addEventListener("search", (e) => {
console.log("Searching:", e.detail.value);
});
</script><compa11y-file-upload>
File upload with drag-and-drop dropzone, file validation, accessible file list with remove buttons, and screen reader announcements.
Attributes
acceptmultiplemax-sizedisabledSlots
triggerdropzone(default)Events
changecompa11y-files-changeMethods
clear()<compa11y-file-upload accept="image/*" max-size="5000000">
<p slot="dropzone">Drag an image here or click to browse</p>
</compa11y-file-upload>
<script>
document.querySelector("compa11y-file-upload")
.addEventListener("compa11y-files-change", (e) => {
console.log("Files:", e.detail.files);
});
</script><compa11y-error-summary>
Error summary with role='alert' that lists validation errors as links to their corresponding fields. Auto-focuses on mount.
Attributes
headingSlots
(default - error list items)Events
compa11y-error-click<compa11y-error-summary heading="Please fix the following errors">
<a href="#name" data-field="name">Enter your full name</a>
<a href="#email" data-field="email">Enter a valid email address</a>
</compa11y-error-summary>
<compa11y-input id="name" label="Full name" error="Enter your full name"></compa11y-input>
<compa11y-input id="email" label="Email" error="Enter a valid email address"></compa11y-input><compa11y-stepper>
Multi-step progress indicator with aria-current='step', step status tracking, and optional clickable navigation for completed steps.
Attributes
current-steporientationclickable-stepsSlots
(default - step items)Events
compa11y-step-change<compa11y-stepper current-step="1" clickable-steps>
<div data-step="cart" data-status="complete">Cart</div>
<div data-step="shipping" data-status="active">Shipping</div>
<div data-step="payment" data-status="pending">Payment</div>
<div data-step="confirm" data-status="pending">Confirmation</div>
</compa11y-stepper><compa11y-data-grid>
Interactive data grid with ARIA grid pattern, cell-level keyboard navigation, sortable columns, and optional inline editing.
Attributes
modearia-labelSlots
toolbar(default)Events
compa11y-sort-changecompa11y-cell-edit<compa11y-data-grid aria-label="Team members">
<div slot="toolbar">
<compa11y-button variant="outline" size="sm">Export</compa11y-button>
</div>
<!-- Grid columns and rows defined via JavaScript -->
</compa11y-data-grid>
<script>
const grid = document.querySelector("compa11y-data-grid");
grid.columns = [
{ key: "name", header: "Name", sortable: true },
{ key: "email", header: "Email", sortable: true },
{ key: "role", header: "Role" },
];
grid.data = [
{ name: "Alice", email: "alice@example.com", role: "Admin" },
{ name: "Bob", email: "bob@example.com", role: "Editor" },
];
</script><compa11y-date-picker>
Date picker with full calendar keyboard navigation, single and range selection, popover or modal overlay, and min/max constraints.
Attributes
valuemodeprecisionoverlaymin-datemax-datedisabledplaceholderEvents
changecompa11y-date-changeMethods
show()close()<!-- Single date -->
<compa11y-date-picker placeholder="DD/MM/YYYY"></compa11y-date-picker>
<!-- Date range -->
<compa11y-date-picker mode="range" min-date="2024-01-01"></compa11y-date-picker>
<script>
document.querySelector("compa11y-date-picker")
.addEventListener("compa11y-date-change", (e) => {
console.log("Selected:", e.detail.value);
});
</script><compa11y-time-picker>
Time input with spinbutton segments for hours, minutes, and AM/PM. Supports 12h and 24h formats with step intervals.
Attributes
valueformatstepdisabledlabelEvents
changecompa11y-time-change<compa11y-time-picker label="Meeting time" format="12h" step="15"></compa11y-time-picker>
<compa11y-time-picker label="Alarm" format="24h" value="07:30"></compa11y-time-picker><compa11y-tree-view>
Hierarchical tree view with ARIA treeview pattern, expand/collapse, single/multi-select, type-ahead, and full keyboard navigation.
Attributes
selection-modearia-labelSlots
(default)Events
compa11y-selection-changecompa11y-expanded-change<compa11y-tree-view selection-mode="single" aria-label="File explorer">
<!-- Tree data set via JavaScript -->
</compa11y-tree-view>
<script>
const tree = document.querySelector("compa11y-tree-view");
tree.data = [
{ id: "src", label: "src", children: [
{ id: "index", label: "index.ts" },
{ id: "app", label: "App.tsx" },
] },
{ id: "readme", label: "README.md" },
];
tree.addEventListener("compa11y-selection-change", (e) => {
console.log("Selected:", e.detail.ids);
});
</script><compa11y-command-palette>
Command palette (Cmd+K / Ctrl+K) with search, grouped commands, and keyboard navigation. Modal dialog with combobox pattern.
Attributes
openplaceholderSlots
(default - groups and items)Events
compa11y-command-selectcompa11y-command-opencompa11y-command-closeMethods
show()close()<compa11y-command-palette placeholder="Search actions...">
<div data-group="Navigation">
<div data-command="home">Go to Home</div>
<div data-command="settings">Go to Settings</div>
</div>
<div data-group="Actions">
<div data-command="new">Create New Document</div>
</div>
</compa11y-command-palette>
<script>
const palette = document.querySelector("compa11y-command-palette");
document.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
palette.show();
}
});
</script><compa11y-carousel>
Content carousel with aria-roledescription='carousel', autoplay with pause control, pagination, and full keyboard navigation.
Attributes
aria-labelloopautoplayautoplay-intervalorientationSlots
slidecontrolsEvents
compa11y-slide-changeMethods
next()previous()goTo(index)pause()resume()<compa11y-carousel aria-label="Feature highlights" loop autoplay>
<div slot="slide">
<h3>Accessible by Default</h3>
<p>Every component ships with ARIA, keyboard, and screen reader support.</p>
</div>
<div slot="slide">
<h3>React + Web Components</h3>
<p>Use the same primitives in React or any framework.</p>
</div>
<div slot="slide">
<h3>Zero Configuration</h3>
<p>Install, import, render.</p>
</div>
</compa11y-carousel><compa11y-rich-text-editor>
WYSIWYG editor with accessible toolbar (roving tabindex), formatting keyboard shortcuts (Ctrl+B, Ctrl+I, etc.), and character count with aria-live.
Attributes
placeholderreadonlydisabledSlots
toolbar(default - editor content)Events
changecompa11y-rte-changeMethods
bold()italic()underline()undo()redo()<compa11y-rich-text-editor placeholder="Start writing...">
<!-- Toolbar is auto-generated with bold, italic, underline, headings,
lists, link, undo/redo buttons -->
</compa11y-rich-text-editor>
<script>
document.querySelector("compa11y-rich-text-editor")
.addEventListener("compa11y-rte-change", (e) => {
console.log("Content:", e.detail.value);
});
</script>