@nexa/ui — Interactive, no island
Real client-side state — but none of these need an island: the usual progressive activation.
Dialog
function open() { ui.openDialog(document.querySelector(".nx-dialog")); }
function close() { ui.closeDialog(document.querySelector(".nx-dialog")); }
<button onClick={open}>Abrir</button>
<div class="nx-dialog" hidden>
<p>Contenido</p>
<button onClick={close}>Cerrar</button>
</div>ui.openDialog(el)/ui.closeDialog(el) — trapped focus, Escape closes it, returns focus to the previous element. ui is always available, nothing to declare in [imports].
Dialog stacking
// abrir un segundo Dialog desde dentro del primero ya abierto:
// se apila sobre él sin romper ninguno de los dosOpening a Dialog while another is already open stacks it on top (correct z-index) and hides the one below from screen readers (aria-hidden) while it's covered.
ui.confirm() / ui.alert()
async function removeItem() {
if (await ui.confirm("¿Seguro que querés borrar esto?")) {
// borrar
}
}
await ui.alert("Guardado con éxito");Confirm before a destructive action, or notify that something was saved, without writing the <div class="nx-dialog"> by hand. Escape in confirm() counts as canceling; in alert(), as accepting.
Drawer
function openMenu() { ui.openDrawer(document.querySelector(".nx-drawer")); }
<button onClick={openMenu}>Menú</button>
<div class="nx-drawer" hidden>
<a href="/">Inicio</a>
</div>Same accessibility contract as Dialog, meant for sidebars/side menus instead of a centered box.
Toast / snackbar
function saveOk() {
ui.notify({ message: "Guardado con éxito", variant: "success" });
}ui.notify() — not to be confused with platform.notify() (a real OS notification): this one is ephemeral, inside the page. It stacks with others, auto-closes after 4s.
Tabs
function selectTab(event) { ui.selectTab(event); }
<div class="nx-tab-group">
<div class="nx-tab-list" role="tablist">
<button class="nx-tab" data-tab="uno" aria-selected="true" onClick={selectTab}>Uno</button>
<button class="nx-tab" data-tab="dos" onClick={selectTab}>Dos</button>
</div>
<div class="nx-tab-panel" data-tab="uno">Contenido uno</div>
<div class="nx-tab-panel" data-tab="dos" hidden>Contenido dos</div>
</div>ui.selectTab(event) reads which button was clicked and its data-tab, finds the nearest .nx-tab-group, and does the rest.
Accordion
function toggle(event) { ui.toggleAccordionItem(event); }
<div class="nx-accordion">
<div class="nx-accordion-item">
<button class="nx-accordion-trigger" aria-expanded="false" onClick={toggle}>Pregunta</button>
<div class="nx-accordion-panel" hidden>Respuesta</div>
</div>
</div>ui.toggleAccordionItem(event). With data-exclusive="true" on the container, opening one item closes the rest.
Dropdown
function toggleMenu(event) { ui.toggleDropdown(event); }
function pick(event) { ui.selectDropdownOption(event); }
<div class="nx-dropdown">
<button class="nx-dropdown-trigger" aria-expanded="false" onClick={toggleMenu}>Elegir…</button>
<ul class="nx-dropdown-menu" role="listbox" hidden>
<li class="nx-dropdown-option" data-value="m" onClick={pick}>M</li>
</ul>
</div>ui.toggleDropdown(event) / ui.selectDropdownOption(event) — closes on an outside click or Escape, without trapping focus (a dropdown doesn't block the rest of the page).
Sorting and filtering a Table
function sortByColumn(event) { ui.sortTable(event); }
function filterOrders(event) { ui.filterTable(document.getElementById("orders"), event.target.value); }
<input type="search" onInput={filterOrders} />
<table id="orders" class="nx-table">
<thead><tr><th data-sortable onClick={sortByColumn}>Cliente</th></tr></thead>
<tbody>...</tbody>
</table>ui.sortTable(event) sorts by the clicked <th>'s column (numeric if both cells are). ui.filterTable(table, query) hides rows whose text doesn't match. Both client-side, no re-fetch.
Reactive page search
function onSearchInput(event) {
const input = event.currentTarget;
input.pageSearch ??= ui.createPageSearch(document.getElementById("catalogo"));
input.pageSearch.setQuery(event.target.value);
}
<input type="search" onInput={onSearchInput} />
<div id="catalogo">
<article data-searchable>Zapatillas Nezha</article>
</div>ui.createPageSearch(container) filters data-searchable elements with a real @nexa/reactivity Signal underneath. The controller hangs off the <input> itself because every chunk is an isolated module.