Enhancement

Toggle

Show and hide a target element from one or more trigger buttons

Import
@parallelogram-js/core/components/Toggle
Selector
[data-toggle]
Stylesheet
@parallelogram-js/core/styles/toggle.css

Playground

A capture toggle closes when you click or tab outside it.

<button type="button" class="button" data-toggle data-toggle-target="#account-menu" data-toggle-capture>
  Account
</button>
<nav id="account-menu" class="menu">
  <a href="#profile">Profile</a>
  <a href="#bookings">Bookings</a>
  <a href="#sign-out">Sign out</a>
</nav>

Toggles in the same group close each other.

Yes, up to 48 hours before your booking.

Tables of up to twelve can be booked online.

<div class="accordion">
  <button type="button" data-toggle data-toggle-target="#refunds" data-toggle-group="faq">Can I get a refund?</button>
  <div id="refunds"><p>Yes, up to 48 hours before your booking.</p></div>
  <button type="button" data-toggle data-toggle-target="#groups" data-toggle-group="faq">Do you take group bookings?</button>
  <div id="groups"><p>Tables of up to twelve can be booked online.</p></div>
</div>

About

This is the disclosure pattern: triggers get aria-expanded and an aria-controls link to the target, which is given an id when it has none. The target has the open class while open and the hidden attribute while closed. The shipped stylesheet keeps closed targets hidden whatever display page styles give them, leaves open targets their own display, and animates the change. Toggle waits for those animations instead of a fixed delay, so the duration lives only in CSS.

Toggles are independent unless they share a group, in which case opening one closes the others. A capture toggle, such as a dropdown, also closes when the user clicks or moves focus outside it. Escape closes the open toggle that holds focus and returns focus to its trigger, and following a page link inside an open target closes it. Unmounting a trigger puts back the attributes Toggle gave it, and unmounting the last trigger for a target does the same for the target.

Toggle closes targets when it mounts, so a target that starts closed shows until then and the page jumps. Write data-toggle-state="closed" on the target in the markup to hide it from the first paint instead. The stylesheet only hides it while scripts are enabled, so it still shows without JavaScript, and it shows again if Toggle fails to load, though not if Toggle is never registered.

For new dropdowns consider <button popovertarget> with popover, and for accordions Accordion, which animates <details name="…">; both work without JavaScript.

Accessibility

Triggers must be buttons. Toggle sets aria-expanded and aria-controls, closes on Escape and returns focus to the trigger that opened the target.

Usage

Register the component and the framework loads it the first time a page contains [data-toggle].

import { Parallelogram } from '@parallelogram-js/core';

const app = Parallelogram.create();
app.components.add('[data-toggle]', () => import('@parallelogram-js/core/components/Toggle'));
app.run();

The framework is optional. Toggle.enhanceAll() mounts the component on every matching element by itself, for a page that wants one enhancement rather than a framework:

import Toggle from '@parallelogram-js/core/components/Toggle';

Toggle.enhanceAll();

It takes a selector and the component's options, and returns the instance it mounted them with. What it leaves out is what the framework adds: loading a component only on the pages that use it, mounting again after a page swap, and ordering with dependsOn.

Its styles come from the package stylesheet:

@import '@parallelogram-js/core/styles/toggle.css';

Attributes

NameTypeDefaultDescription
data-toggleflagnoneMarks a button as a trigger
data-toggle-targetrequiredselectornoneThe element the trigger shows and hides
data-toggle-groupstringnoneToggles that share a group name close each other
data-toggle-capturebooleanfalseClose on a click or focus outside the trigger and target For every instance, Toggle.defaults.capture.
data-toggle-close-navigationbooleantrueClose when a page link inside the target is followed For every instance, Toggle.defaults.closeOnNavigation.
data-toggle-close-escapebooleantrueClose with Escape while focus is in the trigger or target, or rests on the page For every instance, Toggle.defaults.closeOnEscape.
data-toggle-manualbooleanfalseClose only from a trigger or the group; also allowed on the target For every instance, Toggle.defaults.manual.
data-toggle-animatebooleantrueWait for the stylesheet's animations before changing state For every instance, Toggle.defaults.animateToggle.
data-toggle-enhancedset by the componentflagnonePresent on a trigger while Toggle is mounted on it
data-toggle-stateset by the componentclosed | opening | open | closingnoneThe target's state. Write closed in the markup to hide a target that starts closed before Toggle loads On target.

Events

NameDetailDescription
toggle:show{ target: HTMLElement; trigger: HTMLElement; timestamp: number }Dispatched on the trigger when its target opens DOM event and event bus
toggle:hide{ target: HTMLElement; trigger: HTMLElement; timestamp: number }Dispatched on the trigger when its target closes DOM event and event bus
toggle:mount{ element: HTMLElement; target: HTMLElement; isOpen: boolean; timestamp: number }A trigger was set up Event bus

CSS custom properties

NameDefaultDescription
--toggle-transition-duration0.75sLength of the open and close animations
--toggle-transition-easingnoneEasing of the open and close animations