Get Pro Access

Interactivity

Progressive enhancement via @kiwa-ui/enhance.

The progressive enhancement model

Kiwa UI components render to plain HTML with data-* attributes that describe their state and behavior. That HTML works on its own. Dialogs open with a server roundtrip, dropdowns fall back to native <details>, and so on.

When you want richer interaction (keyboard nav, focus trap, ARIA state, fluid open/close), load the matching behavior from @kiwa-ui/enhance. It scans the DOM for the data attributes and wires everything up. No framework, no virtual DOM, no hydration step.

Install enhance

Every behavior is a separate export, so bundlers can tree-shake what you don't use. Import from the root package or from per-behavior subpath exports.

pnpm add @kiwa-ui/enhance

Wire it up

Drop a module script into your Hono layout that imports and calls the behaviors you need. Each call scans the DOM once and attaches listeners.

<script type="module">
  import { dialog, dropdown, tabs, clipboard } from '@kiwa-ui/enhance'

  dialog()
  dropdown()
  tabs()
  clipboard()
</script>

Call the behaviors you actually use. Don't blanket-import everything. For apps with a lot of dynamic content, call a behavior again after inserting new DOM.

Available behaviors

26 behaviors ship with @kiwa-ui/enhance. Each maps to a primitive or block that uses the matching data attribute.

BehaviorDescription
dialogModal dialogs with focus trap and ESC close.
dropdownMenus with keyboard nav and typeahead.
tabsTab panels with roving focus.
accordionExpand/collapse sections.
collapsibleStandalone show/hide.
toggleTwo-state buttons.
tooltipHover/focus tooltips with smart positioning.
popoverFloating panels anchored to triggers.
sheetSlide-out side panels.
sliderRange input with keyboard support.
commandCommand palette / fuzzy search.
alert-dialogConfirmation dialogs.
context-menuRight-click menus.
hover-cardRich tooltips on hover.
carouselHorizontal scroll with snap.
sidebarCollapsible app sidebar.
sidebar-mobileMobile drawer variant.
popover-submenuNested popover menus.
date-pickerCalendar input.
selectCustom select dropdown.
editorTipTap-based rich text (optional peer deps).
selectable-tableRow selection with shift-click.
chart-tooltipTooltips for chart primitives.
themeDark/light mode toggle.
clipboardCopy-to-clipboard buttons.
toastTransient notifications.

Data-attribute conventions

Every behavior follows the same attribute shape so the markup reads the same across primitives.

AttributeDescription
data-<behavior>The root container.
data-<behavior>-triggerThe element that opens/toggles the surface.
data-<behavior>-contentThe panel/menu/content region.
data-stateanddata-<behavior>-openReflect current state for CSS styling.
<button data-dialog-trigger='welcome'>Open</button>

<div data-dialog='welcome' data-state='closed'>
  <div data-dialog-overlay />
  <div data-dialog-content>
    <h2>Welcome</h2>
    <button data-dialog-close>Close</button>
  </div>
</div>

Without enhance vs with enhance

Writing your own enhancers

Enhancers are just functions that query for a data attribute and attach listeners. Use the ones in packages/enhance/src/ as a reference. They're all ~50-200 lines of plain TypeScript. A minimal example:

// enhance-copy.ts
export function copy() {
  document.querySelectorAll<HTMLButtonElement>('[data-copy]').forEach((btn) => {
    btn.addEventListener('click', async () => {
      const text = btn.getAttribute('data-copy-text') ?? ''
      await navigator.clipboard.writeText(text)
      btn.setAttribute('data-copy-copied', 'true')
      setTimeout(() => btn.removeAttribute('data-copy-copied'), 1500)
    })
  })
}