Axoloth Style

Behavior / Overview

Make data-axo-* interactive.

Axoloth Style and Axoloth Behavior are deliberately separate. CSS supplies layout and presentation. The optional JavaScript package reads declarative attributes, manages accessible state, and returns controllers you can refresh or destroy.

Style package

Structure and appearance

  • axo-tabsLayout and visual states.
  • axo-dialogLayer positioning and transitions.
  • axo-drawerDrawer placement and open classes.

Behavior package

State and interaction

  • data-axo-*Connects triggers, panels, and targets.
  • init*Attaches event listeners and ARIA state.
  • destroy()Removes listeners during cleanup.

Attributes do nothing until an initializer runs. The package never auto-initializes.

Install

Choose npm or native ES modules.

npm and a bundler

npm install @quertys/axoloth-style @quertys/axoloth-behavior

CDN and native ES modules

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@quertys/axoloth-style@0.9.0/src/axoloth.css"
/>

<script type="module">
  import { initAxolothBehaviors } from
    'https://cdn.jsdelivr.net/npm/@quertys/axoloth-behavior@0.6.0/src/index.js';

  const axoloth = initAxolothBehaviors();
  window.addEventListener('pagehide', () => axoloth.destroy(), { once: true });
</script>

Pin both package versions for predictable production deployments.

Initialize Everything

One call for every available behavior.

Call the root initializer after the DOM exists. It returns the individual controllers plus one aggregate destroy() method.

import '@quertys/axoloth-style/axoloth.css';
import { initAxolothBehaviors } from '@quertys/axoloth-behavior';

const axoloth = initAxolothBehaviors();

// Available controllers:
axoloth.tabs.refresh();
axoloth.accordion.refresh();

// Remove every listener when this page or view is disposed.
window.addEventListener('pagehide', () => axoloth.destroy(), { once: true });

Initializing behavior demos...

Manual Initialization

Import only the component you use.

Every initializer accepts an optional root. Scoping to a section keeps ownership and cleanup explicit in multi-page apps or component systems.

import { initTabs } from '@quertys/axoloth-behavior/tabs';
import { initAccordion } from '@quertys/axoloth-behavior/accordion';
import { initDialog } from '@quertys/axoloth-behavior/dialog';
import { initDrawer } from '@quertys/axoloth-behavior/drawer';

const feature = document.querySelector('#account-feature');
const controllers = [
  initTabs(feature),
  initAccordion(feature),
  initDialog(feature),
  initDrawer(feature),
];

// Call refresh() after inserting new matching markup.
controllers[0].refresh();
controllers[1].refresh();

// Clean up before replacing or unmounting the feature.
controllers.forEach((controller) => controller.destroy());

Vanilla Example 01

Tabs

Matching data-axo-tab and data-axo-tab-panel values connect each trigger to its panel. Arrow keys, Home, and End are supported.

<div id="account-tabs" class="axo-tabs" data-axo-tabs>
  <div class="axo-tab-list" aria-label="Account sections">
    <button class="axo-tab" type="button"
      data-axo-tab="profile" aria-selected="true">Profile</button>
    <button class="axo-tab" type="button"
      data-axo-tab="security">Security</button>
  </div>
  <section class="axo-tab-panel" data-axo-tab-panel="profile">
    Profile content
  </section>
  <section class="axo-tab-panel" data-axo-tab-panel="security">
    Security content
  </section>
</div>

<script type="module">
  import { initTabs } from
    'https://cdn.jsdelivr.net/npm/@quertys/axoloth-behavior@0.6.0/src/tabs.js';
  const tabs = initTabs(document.querySelector('#account-tabs'));
  window.addEventListener('pagehide', () => tabs.destroy(), { once: true });
</script>

Profile

Update the public information shown on your account.

Security

Review passkeys and active sessions.

Vanilla Example 02

Accordion

Each trigger and panel share an identifier. The default group keeps one item open and remains collapsible.

<div id="faq" class="axo-accordion" data-axo-accordion>
  <section class="axo-accordion-item axo-surface">
    <h3>
      <button class="axo-accordion-trigger" type="button"
        data-axo-accordion-trigger="install" aria-expanded="true">
        How do I install it?
        <span class="axo-accordion-indicator" aria-hidden="true">+</span>
      </button>
    </h3>
    <div class="axo-accordion-panel"
      data-axo-accordion-panel="install">Install both packages with npm.</div>
  </section>
</div>

<script type="module">
  import { initAccordion } from
    'https://cdn.jsdelivr.net/npm/@quertys/axoloth-behavior@0.6.0/src/accordion.js';
  const accordion = initAccordion(document.querySelector('#faq'));
  window.addEventListener('pagehide', () => accordion.destroy(), { once: true });
</script>

Install both packages, then initialize behavior after the DOM exists.

Call destroy before replacing the initialized root or leaving the view.

Vanilla Example 03

Dialog

The controller handles ARIA state, focus trapping, Escape, backdrop dismissal, body scroll locking, and focus return.

<div id="dialog-example">
  <button type="button" class="axo-button"
    data-axo-dialog-toggle="confirm-dialog"
    aria-controls="confirm-dialog" aria-expanded="false">Open dialog</button>
  <div id="confirm-dialog" class="axo-dialog"
    data-axo-dialog-id="confirm-dialog"
    aria-labelledby="confirm-title" aria-hidden="true">
    <div class="axo-dialog-backdrop"
      data-axo-dialog-dismiss="confirm-dialog" aria-hidden="true"></div>
    <section class="axo-dialog-panel axo-surface">
      <header class="axo-dialog-header">
        <h2 id="confirm-title">Confirm action</h2>
      </header>
      <div class="axo-dialog-body">This dialog traps focus while open.</div>
      <footer class="axo-dialog-footer">
        <button class="axo-button" type="button"
          data-axo-dialog-dismiss="confirm-dialog">Close</button>
      </footer>
    </section>
  </div>
</div>

<script type="module">
  import { initDialog } from
    'https://cdn.jsdelivr.net/npm/@quertys/axoloth-behavior@0.6.0/src/dialog.js';
  const dialog = initDialog(document.querySelector('#dialog-example'));
  window.addEventListener('pagehide', () => dialog.destroy(), { once: true });
</script>

Dialog preview

Open the layer, press Tab, then close it with Escape or the backdrop.

Vanilla Example 04

Drawer

Matching toggle, target, and dismiss values create a temporary viewport panel with focus management and a dismissible backdrop.

<div id="drawer-example">
  <button class="axo-button" type="button"
    data-axo-drawer-toggle="main-drawer"
    aria-controls="main-drawer" aria-expanded="false">Open drawer</button>
  <aside id="main-drawer" class="axo-drawer axo-drawer-right axo-surface"
    data-axo-drawer-id="main-drawer"
    aria-label="Main navigation" aria-hidden="true">
    <button class="axo-button" type="button"
      data-axo-drawer-dismiss="main-drawer">Close</button>
    <a class="axo-sidebar-item axo-link" href="#dashboard">Dashboard</a>
  </aside>
  <div class="axo-drawer-backdrop"
    data-axo-drawer-dismiss="main-drawer"></div>
</div>

<script type="module">
  import { initDrawer } from
    'https://cdn.jsdelivr.net/npm/@quertys/axoloth-behavior@0.6.0/src/drawer.js';
  const drawer = initDrawer(document.querySelector('#drawer-example'));
  window.addEventListener('pagehide', () => drawer.destroy(), { once: true });
</script>

Drawer preview

Open the right-side panel, then dismiss it with Escape or the backdrop.

Cleanup

Keep the controller that owns the listeners.

Call destroy() before unmounting, replacing an initialized root, or initializing it again. Use refresh() for newly inserted tabs or accordion items.

const axoloth = initAxolothBehaviors(document.querySelector('#feature'));

// Later, after adding matching markup:
axoloth.tabs.refresh();
axoloth.accordion.refresh();

// Before the feature is removed:
axoloth.destroy();

Troubleshooting

When attributes do not work.

  • The component is styled but clicks do nothing Only Axoloth Style is loaded. Import Axoloth Behavior and call the matching initializer after the markup exists.
  • The script is loaded but attributes still do nothing The package does not auto-initialize. Call initAxolothBehaviors() or a per-component initializer from a module script.
  • A trigger cannot find its panel Match identifiers exactly: a drawer toggle value must equal its drawer ID, and tab values must equal their panel values.
  • New markup is ignored Call refresh() after inserting new tabs, accordion items, dropdowns, or toast regions.
  • An interaction fires twice The same root was initialized more than once. Destroy its previous controller before initializing again.
  • SSR reports that document is missing Initialize inside the client lifecycle after the DOM exists, and keep your own DOM selectors out of server execution.