JavaScript Guidelines

Architecture overview

The design system uses a two-layer JavaScript architecture:

Code
Layer 1 — Shared bundle (compiled once by Vite) src/handoff/js/main.js ├── js/components/accordion.js — jQuery slide accordion ├── js/components/default-slider.js — Slick: .js-default-slider variants ├── js/components/article-card-slider.js— Slick: article card carousel ├── js/components/header.js — sticky header, notification banner ├── js/components/main-nav.js — primary navigation ├── js/components/mobile-nav.js — mobile nav drawer ├── js/components/hamburger-icon.js — hamburger toggle ├── js/components/search-bar.js — site search ├── js/components/offcanvas.js — Bootstrap offcanvas panel ├── js/components/jump-nav.js — in-page anchor navigation ├── js/components/tabs.js — tab switching (.js-toggle-tab) ├── js/components/selects.js — Select2 dropdowns ├── js/components/popup.js — Magnific Popup lightbox ├── js/components/reveal.js — ScrollReveal entrance animations ├── js/components/parallax.js — parallax scrolling effects ├── js/components/observer.js — IntersectionObserver utility ├── js/components/smooth-scroll.js — smooth anchor scrolling ├── js/components/comparison-slider.js — before/after comparison slider ├── js/components/compare-slider.js — compare slider variant ├── js/components/carousel-content.js — content carousel ├── js/components/cta-columns-slider.js— CTA columns slider ├── js/components/showcase-slider.js — product showcase slider ├── js/components/ticker-slider.js — ticker/marquee slider ├── js/components/video-carousel.js — video carousel ├── js/components/featured-content.js — featured content ├── js/components/speaker-card.js — speaker card expand/collapse ├── js/components/intro-label.js — intro label animation ├── js/components/gravity-forms.js — Gravity Forms enhancements ├── js/components/consent.js — cookie consent ├── js/components/pointer-events.js — pointer events utility └── js/utilities/svg-inline-loader.js — inline SVG injection Layer 2 — Per-block scripts (compiled per-block by Handoff) src/handoff/blocks/{name}/script.js — imports one or more Layer 1 utilities — may add block-specific custom logic — Vite deduplicates: each utility appears only once in the final bundle

The shared bundle is built once and deployed as public/api/component/main.js. Each block's script.js imports the utilities it needs. Vite deduplicates these at build time — no utility is included twice in the final output, regardless of how many blocks import it.

Dependencies

LibraryVersionPurpose
jQuery3.7.1DOM utilities, event handling, plugin compatibility
Bootstrap5.3.5Collapse, modal, dropdown, offcanvas plugins
Slick Carousel1.8.1Carousels and sliders
Magnific Popup1.2.0Lightbox, video popups, inline modals
Select24.1.0-rc.0Enhanced select dropdowns with search
ScrollReveal4.0.9Scroll-triggered entrance animations
axe-core4.11.0Automated accessibility testing
url-search-params-polyfill8.2.5URLSearchParams API polyfill

Build tooling

JavaScript is bundled with Vite via the handoff.config.js build pipeline:

JavaScript
1// src/handoff/handoff.config.js 2entries: { 3 js: './js/main.js', 4 bundle: './js/main.js', 5}

Build commands:

Bash
1npm run start # development server with live reload 2npm run build:components # build component bundle 3npm run build:app # build the documentation site

Shared utilities

Each file in js/components/ handles one concern and is auto-initialized on DOMContentLoaded. Blocks import the utilities they need in their script.js.

FileTriggerUsed by blocks
accordion.js.c-accordion__toggleaccordion-simple, callout, content, feature
default-slider.js.js-default-slider, .js-default-slider-altbanner, testimonial-slider
article-card-slider.js.js-article-card-sliderposts-featured
header.js.c-header__bottom (sticky), .c-notification__closeheader
main-nav.js.c-main-navheader
mobile-nav.js.c-mobile-navheader
hamburger-icon.js.c-hamburgerheader
search-bar.js.c-site-searchheader
offcanvas.js.c-offcanvasheader
jump-nav.js.c-jump-navheader, hero-product
tabs.js.js-toggle-tab, .c-tabsany block with tabs
selects.js.c-select select, .event-form-select, .c-provider-finder__selectcta-provider, download, downloads-*, footer, grid-three-column, hero-contact, hero-news, hero-search-provider, posts-latest
popup.js.js-open-content-popup, .js-open-video, .eb-js-showcase-sliderdownload, downloads-*, hero-contact
reveal.js.will-slide-right, .will-slide-left, .will-fade-inglobal (any block)
parallax.js[data-parallax]global (any block)
smooth-scroll.js.js-smooth-scrollglobal (any block)
speaker-card.js.c-speaker-cardcontent-bio-list
observer.jsIntersectionObserver setupglobal utility
gravity-forms.js.gform_wrapperany block with a Gravity Form
consent.js.c-consentnotification banner
svg-inline-loader.jsinline <use> SVG referencesmost hero and event blocks

The @shared annotation

When a script.js delegates entirely to a shared utility, document it with a JSDoc @shared tag instead of leaving a bare import. This lets Handoff's component preview surface meaningful context.

JavaScript
1/** 2 * Accordion Simple Block 3 * 4 * Expandable content panels using jQuery slide animations. 5 * 6 * @shared accordion 7 * Trigger: .c-accordion__toggle (scoped to .c-accordion blocks on this page) 8 * Behavior: Expands one panel at a time; collapses siblings 9 * 10 * @see /guidelines/javascript/shared-utilities 11 */ 12import '../../js/components/accordion.js';

See Component Scripts for the full annotation pattern.

Multi-instance pattern

When writing custom block-level JavaScript, always support multiple instances of the same block on one page. Scope all DOM queries to the block element:

JavaScript
1document.addEventListener('DOMContentLoaded', () => { 2 document.querySelectorAll('[data-component="my-block"]').forEach(block => { 3 const trigger = block.querySelector('.js-trigger'); 4 const panel = block.querySelector('.js-panel'); 5 6 if (!trigger || !panel) return; 7 8 trigger.addEventListener('click', () => { 9 const isOpen = panel.classList.contains('is-open'); 10 panel.classList.toggle('is-open', !isOpen); 11 trigger.setAttribute('aria-expanded', String(!isOpen)); 12 }); 13 }); 14});

Never query document.getElementById or scope queries to document when you need per-instance behavior — always use block.querySelector.

Coding style

  • Use const and let — never var in new code
  • Prefer vanilla document.querySelector over jQuery $() in new scripts
  • Wrap initialization in DOMContentLoaded or iterate with .forEach(block => ...)
  • Remove all console.log before committing
  • Use class="js-*" selectors for JavaScript hooks — not id="*"
  • Always include a JSDoc header on each script.js with @shared or @requires, and @see links

Sub-pages