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/web

Styling

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;
}
Same accessibility, different API
Web Components use HTML attributes and events instead of React props and callbacks, but provide identical accessibility features: keyboard navigation, ARIA roles, focus management, and screen reader support.

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-label

Slots

(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

labelhinterrortypeplaceholdervaluedisabledrequired

Events

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

labelhinterrorrowsplaceholdervaluedisabledrequired

Events

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

labelcheckedindeterminatedisabledvaluenamerequired

Events

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

legendvalueorientationdisabledrequired

Slots

(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

labelcheckeddisabledsizevaluename

Events

change

Methods

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-label

Slots

(default - option elements)

Events

changecompa11y-select-opencompa11y-select-closecompa11y-select-change

Methods

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-label

Slots

(default - option elements)

Events

compa11y-combobox-opencompa11y-combobox-closecompa11y-combobox-selectcompa11y-combobox-changecompa11y-combobox-clear

Methods

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-label

Slots

(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-escape

Slots

titledescriptionactions(default)

Events

compa11y-dialog-opencompa11y-dialog-close

Methods

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-menu>

Dropdown menu with keyboard navigation, focus return to trigger, and aria-haspopup='menu' on the trigger slot.

Attributes

open

Slots

trigger(default - menu items with role='menuitem')

Events

compa11y-menu-opencompa11y-menu-closecompa11y-menu-select

Methods

show()close()toggle()
<compa11y-menu>
  <button slot="trigger">Actions</button>
  <button role="menuitem">Edit</button>
  <button role="menuitem">Duplicate</button>
  <div role="separator"></div>
  <button role="menuitem" aria-disabled="true">Delete</button>
</compa11y-menu>

<compa11y-tabs>

Tab interface with roving tabindex, arrow key navigation, automatic or manual activation mode, and orientation support.

Attributes

orientationactivation-modeselected-index

Slots

tabpanel

Events

compa11y-tabs-change

Methods

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-visually-hidden>

Hides content visually while keeping it in the DOM for screen readers. No ARIA attributes needed.

Attributes

focusable

Slots

(default)
<!-- Hidden label for an icon button -->
<button>
  <span aria-hidden="true">&times;</span>
  <compa11y-visually-hidden>Close dialog</compa11y-visually-hidden>
</button>

<!-- Focusable — content appears when focused -->
<compa11y-visually-hidden focusable>
  <a href="#main-content">Skip to main content</a>
</compa11y-visually-hidden>

<compa11y-alert>

Static feedback element. Uses role='alert' (assertive) for error/warning and role='status' (polite) for info/success. Optional dismiss button.

Attributes

typetitledismissible

Slots

(default - alert body content)

Events

dismiss

Methods

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-toasts

Events

compa11y-toast-addcompa11y-toast-remove

Methods

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-tooltip>

Lightweight tooltip using role='tooltip' with aria-describedby. Opens on hover and keyboard focus, dismisses on Escape.

Attributes

labelsidealigndelay-opendelay-closedisabled

Slots

(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-escape

Slots

titledescriptionactions(default)

Events

compa11y-drawer-opencompa11y-drawer-close

Methods

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-label

Events

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-level

Slots

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

valueminmaxstepprecisiondisabledlabel

Events

changecompa11y-numberfield-change

Methods

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-label

Events

inputsearchclearcompa11y-search-change

Methods

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-sizedisabled

Slots

triggerdropzone(default)

Events

changecompa11y-files-change

Methods

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

heading

Slots

(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-steps

Slots

(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-label

Slots

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-datedisabledplaceholder

Events

changecompa11y-date-change

Methods

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

valueformatstepdisabledlabel

Events

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-label

Slots

(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

openplaceholder

Slots

(default - groups and items)

Events

compa11y-command-selectcompa11y-command-opencompa11y-command-close

Methods

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-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

placeholderreadonlydisabled

Slots

toolbar(default - editor content)

Events

changecompa11y-rte-change

Methods

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>