Foundations
Events
Every interactive RenDS component dispatches its own CustomEvents. This page is the single source of truth: 38 event names across 51 component-event contracts, their detail shape, and bubbling behavior. Use it to integrate RenDS with your application state, analytics, or another framework.
Convention
Naming
All RenDS custom events follow the prefix ren-. Most are namespaced by component (ren-tab-change, ren-slide-change, ren-files-added); a handful of generic names are reused across components. Their exact flags and payload remain component-specific, so use the catalog below as the contract:
ren-open/ren-close— fired by several overlay-style components. Some close events include{ returnValue }.ren-change— fired when a control's value changes. Its payload reflects that control's value model.
If you need to disambiguate between e.g. a Dialog opening and a Combobox opening on the same page, scope your listener to the host element rather than to document.
Defaults
Bubbles, composed, cancelable
bubbles: trueon virtually every event. RenDS components live in light DOM, so events bubble naturally — listen anywhere up the tree.composedvaries by component. RenDS uses light DOM, so this flag mainly records whether the event is intended to cross a future host boundary.cancelable: falseby default.ren-submitis the documented exception; listeners may mark it prevented, while asynchronous work is coordinated throughdetail.waitUntil().
Listen on the host, not document — usually
For component-specific logic, listen on the component element (combo.addEventListener(…)). For cross-cutting concerns like analytics, listening on document with delegation is fine — every event bubbles by default.
Reference
Event catalog
38 event names across 51 component-event contracts and 26 components. — means the field is not explicitly set (uses the CustomEvent default, which is false).
| Component | Event | bubbles | composed | cancelable | detail |
|---|---|---|---|---|---|
| ren-accordion | ren-accordion-change | true | — | — | { item, isOpen, index } |
| ren-calendar | ren-date-select | true | true | — | { date, range, dates } |
| ren-carousel | ren-slide-change | true | true | — | { index, total, id } |
| ren-color-picker | ren-input | true | true | — | { hex, rgb, hsl, alpha } |
| ren-color-picker | ren-change | true | true | — | { hex, rgb, hsl, alpha } |
| ren-combobox | ren-search | true | — | — | { query } |
| ren-combobox | ren-change | true | — | — | { value, item } |
| ren-combobox | ren-open | true | — | — | — |
| ren-combobox | ren-close | true | — | — | — |
| ren-context-menu | ren-context-menu-open | true | true | — | { x, y, target } |
| ren-date-picker | ren-date-picker-open | true | true | — | — |
| ren-date-picker | ren-date-picker-close | true | true | — | — |
| ren-date-picker | ren-change | true | true | — | { value, formattedValue } |
| ren-date-range-picker | ren-date-range-open | true | true | — | — |
| ren-date-range-picker | ren-date-range-close | true | true | — | — |
| ren-date-range-picker | ren-change | true | true | — | { value, formattedValue, preset, days } |
| ren-dialog | ren-open | true | true | — | — |
| ren-dialog | ren-close | true | true | — | { returnValue } |
| ren-dropzone | ren-files-added | true | — | — | { files } |
| ren-hover-card | ren-hover-card-open | true | true | — | — |
| ren-hover-card | ren-hover-card-close | true | true | — | — |
| ren-menu | ren-menu-select | true | — | — | { item, value } |
| ren-menu | ren-menu-open | true | — | — | — |
| ren-menu | ren-menu-close | true | — | — | — |
| ren-number-field | ren-change | true | true | — | { value, oldValue } |
| ren-otp | ren-change | true | true | — | { value } |
| ren-otp | ren-complete | true | true | — | { value } |
| ren-popover | ren-open | true | — | — | — |
| ren-popover | ren-close | true | — | — | — |
| ren-select | ren-select-open | true | — | — | — |
| ren-select | ren-select-close | true | — | — | — |
| ren-select | ren-select-change | true | — | — | { value, label, item } |
| ren-sheet | ren-open | true | — | — | — |
| ren-sheet | ren-close | true | true | — | { returnValue } |
| ren-slider | ren-slider-input | true | true | — | { value } |
| ren-slider | ren-slider-change | true | true | — | { value } |
| ren-tabs | ren-tab-change | true | true | — | { tab, panel, index, id } |
| ren-toggle-group | ren-toggle-change | true | true | — | { value, items } |
| ren-tooltip | ren-open | true | — | — | — |
| ren-tooltip | ren-close | true | — | — | — |
| ren-command | ren-command-select | true | true | — | { item, value, action } |
| ren-form | ren-submit-error | true | true | — | { error } |
| ren-form | ren-invalid | true | true | — | { errors } |
| ren-form | ren-submit | true | true | true | { values, form, waitUntil } |
| ren-form | ren-field-validated | true | true | — | { name, valid, error } |
| ren-form | ren-step-change | true | true | — | { step, totalSteps } |
| ren-menubar | ren-menubar-select | true | true | — | { item, value, checked } |
| ren-sidebar | ren-sidebar-toggle | true | true | — | { collapsed } |
| ren-table | ren-sort | true | true | — | { column, direction } |
| ren-table | ren-select | true | true | — | { selected } |
| ren-table | ren-filter | true | true | — | { value } |
Patterns
How to listen
Targeted listening (preferred)
Listen on the specific component instance. Best for component-specific logic.
const combo = document.querySelector('ren-combobox[name="country"]');
combo.addEventListener('ren-change', (e) => {
console.log(e.detail.value, e.detail.item);
});
Global delegation
Listen on document and filter by target. Best for cross-cutting concerns (analytics, undo stack, audit log).
document.addEventListener('ren-tab-change', (e) => {
analytics.track('tab_changed', {
component: e.target.id,
tab: e.detail.tabId,
});
});
Filtering by detail
table.addEventListener('ren-select', (e) => {
if (e.detail.selected.length === 0) {
bulkBar.hidden = true;
} else {
bulkBar.hidden = false;
bulkBar.querySelector('.count').textContent = e.detail.selected.length;
}
});
One-shot listening
Use the standard { once: true } option — RenDS events are plain CustomEvents.
dialog.addEventListener('ren-close', () => {
cleanup();
}, { once: true });
Watch out
Common gotchas
- Don't
preventDefault()to veto state changes. RenDS events fire after the change applies; cancellation is not wired. To veto, intercept the user action upstream (e.g., disable the trigger button until your check passes). - Two components, same event. If a Combobox and a Select are on the same page, both dispatch
ren-changewith similar detail. Usee.target.tagNameor scope your listener. - Cross-component
ren-open/ren-close. Dialog, Sheet, Combobox, Hover Card all fire these. Same trick: scope to host or checke.target.tagName. - Form events fire on the
<ren-form>host, not on<form>. Listen on the custom element, not the inner form element.