Zazz Design Framework
Scripts

Scripting

How Zazz scripts are structured, documented, and initialized: the vanilla-JavaScript conventions behind every browser-native behavior.

Scripts in zazz/scripts/ are served directly at /zazz/scripts/*.js, with no bundler or transpiler. They are native ES modules and run as-is in modern browsers. A single entry module, main.js, imports every component script, so a page loads behavior with one <script type="module" src="./zazz/scripts/main.js"> tag (see Structuring your head tag).

Philosophy

  • Vanilla JS only. No framework, no npm runtime deps, no build step — just native ES modules. Cross-script dependencies use import; external libraries Zazz doesn't ship (the Embla CDN UMD bundles) are still read as globals loaded by prior <script> tags.
  • HTML-first. Markup and data attributes drive behavior. Authors configure components in HTML; scripts discover and enhance the DOM.
  • Progressive enhancement. Feature-detect APIs before use. When unsupported, degrade gracefully.
  • Minimal surface area. Export a small public API. Keep helpers private.

File structure

Every script follows this layout:

"use strict";

/**
 * @fileoverview Module title.
 * @description What this module does.
 */

import { Dependency } from "./dependency.js"; // only when this module needs another

// Implementation

// Auto-initialize when DOM is ready (only in browser environment)
if (typeof window !== "undefined" && typeof document !== "undefined") {
  // ...
}

// Attach to window for the documented public API, then export for module consumers
// (the main.js entry and any sibling script that imports this one).
if (typeof window !== "undefined") {
  window.MyExport = MyExport;
}

export { MyExport };

Module exports

Each script attaches its public API to window (the documented surface) and provides a matching named export that the main.js bundle and sibling modules import:

FileGlobalExport shape
utils.jswindow.Utils{ parseValue, parseDataAttributes }
reveal.jswindow.RevealReveal class
embla.jswindow.EmblaInit{ init, initRoot, addDotBtnsAndClickHandlers }
navigation.js(none)Side-effect only; no export

The web-component scripts (carousel.js, lightbox.js, password.js, tabs.js) export their element class and register the custom element as a side effect.

Auto-initialization

Scripts that enhance the page on load use a guarded auto-init block:

if (typeof window !== "undefined" && typeof document !== "undefined") {
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
}

Initialization is idempotent: guarded with attributes (data-embla-init), flags, or instance checks so re-running is safe.

Data-attribute configuration

Component scripts read configuration from HTML data attributes rather than JS options objects:

  • Use a consistent prefix per component: data-embla-*, data-reveal-*.
  • Parse attributes with Utils.parseDataAttributes(node, "data-embla-").
  • Boolean flags can be bare attributes (data-embla-autoplay) or explicit values (data-embla-keyboard="false").

Dependencies and load order

Cross-script dependencies are real ES imports, so the module graph resolves order — you load main.js and nothing else needs sequencing:

ScriptImportsNotes
utils.jsProvides window.Utils
reveal.jsStandalone
embla.jsutils.jsAlso needs the Embla CDN globals (see below)
carousel.jsembla.js<embla-carousel> calls EmblaInit.initRoot
lightbox.jscarousel.js<media-lightbox> coordinates carousel elements
password.jsStandalone (<input-password>)
tabs.jsStandalone (<tab-group>)
navigation.jsApp-level; inert in component preview iframes

The one ordering the module graph can't enforce is the Embla CDN UMD bundles: embla.js reads them as globals, so their <script defer> tags must precede the main.js module in the document. Where these tags go is covered in Structuring your head tag.

DOM interaction patterns

  • Query within scope. Accept an optional root element so init can target a subtree.
  • Early returns for guards. Check required elements and skip gracefully.
  • Store instances on DOM nodes when external access is needed: emblaNode._emblaApi = emblaApi.
  • Observe DOM changes with MutationObserver for elements hidden at init time (closed dialogs).
  • Delegate events at document level when triggers can appear anywhere.
  • Respect focus and input context. Skip keyboard handlers in form fields or contenteditable.

Classes vs functions

  • Use a class when the module manages persistent instance state (Reveal with observers and config).
  • Use functions for stateless init and helpers (initEmblaCarousels, parseValue).
  • Use private class fields (#observers, #getObserver) for encapsulation.

JSDoc conventions

Every JSDoc block must include @description. Tag order:

@description
@param
@returns
@private
@see
@example

Required tags

TagRequired on
@fileoverviewEvery file
@descriptionEvery JSDoc block
@paramFunctions with parameters
@returnsFunctions that return a value
@privateNon-exported helpers
@typedefConfig/option objects
@namespaceExport objects (Utils, EmblaInit)

Section dividers

Use thin single-line markers to group related code:

// --- Dot navigation ---

Do not use banner block comments (/* ==== ... ==== */).

Syntax and style

  • Double quotes for strings.
  • Semicolons required.
  • Optional chaining (?.) and nullish coalescing where they simplify guards.
  • No TypeScript source files; types live in JSDoc and globals.d.ts.
  • Type-checking enabled via tsconfig.json (checkJs: true).

On this page