# Setup

How to import the header template, position it correctly in your layout, and optionally add prebuilt mega menu dropdown patterns using the provided JSON files.

## 1. Import Template

First, import the header and make sure it appears in the correct position in your template.

1. Open your `header.json` file in a text editor or IDE and copy its entire contents.
2. In Etch, open your index template:
   * Navigate to any page
   * In the left-hand sidebar, go to the Template Manager
   * Find your index template and click Edit
3. Paste the JSON into the template and make sure the `<header>` is placed above the `<main>` element.

<figure><img src="https://1451250373-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FK1XjLxAlSabqcZ3vCvkD%2Fuploads%2FKsgEhba87VCJ50CDOroi%2Fimage.png?alt=media&amp;token=805457da-85e0-41e6-acf1-b6278f08bbd3" alt=""><figcaption></figcaption></figure>

## 2. (Optional) Import Dropdown Patterns

Along with the template files, you will see a folder named ‎`Dropdown Patterns`. Inside this folder, you will find several pre-built mega menu dropdown patterns that you can use as a starting point. You can import these patterns into the slots of the Menu Dropdown components.

1. Open the header layers. Inside the Menu Wrapper component, locate the two menu dropdown components and find the one labeled `Mega Menu`.
2. Open the `Dropdown Patterns` folder and copy the contents of one of the `.json` files.
3. Paste the contents into the slot of the `Menu Dropdown` component.
4. Remove the existing `placeholder` layer from that dropdown.

## 4. Why isn't the header a component?

You might notice that the header is not set up as a component. This is intentional.&#x20;

At the moment of release of the menu builder, etch does not support editing components inside of components.&#x20;

The recommended approach is to fully build and finalize your header first. Once you are happy with it, convert it into a component so you can reuse it across templates.

## 5. Getting started with customization

There are five key elements that make up the menu builder. Each one has its own options to help you build menus more quickly. You can see a full list of options and what they do on the Component Options page.

For now, you will get a high-level overview of what each element does and how its styling works.

### 1. Header

This is the header you will use across your entire site. The ‎`<header>` element uses a ‎`.header-wrapper` class, and the container inside it uses a ‎`.header` class.

{% hint style="danger" %}
Treat ‎`.header-wrapper` as an invisible outer container. All styling should be applied to the `.header` element. \
\
This separation gives you room for sticky header animations, mobile animations, and behavior without breaking mobile interactions or any dropdown interactions that follow.
{% endhint %}

It is helpful to think of ‎`.header-wrapper` as the outer shell and ‎`.header` as the actual site header.

Want to change the background color of your header? Make that change on ‎`.header`.

Want to add ‎`backdrop-filter: blur(20px);`? Add that to ‎`.header` as well.

#### Adding Custom CSS styling to elements on desktop and mobile.

{% hint style="warning" %}
If you want to make mobile-specific or desktop-specific CSS changes, this is the component you will edit. The CSS inside is broken out and commented so you can quickly find and adjust the styles you need. \
\
You will also find many variables already written out to help you get things styled quicker.
{% endhint %}

<figure><img src="https://1451250373-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FK1XjLxAlSabqcZ3vCvkD%2Fuploads%2FNyIceAgbomzAAMSn3w6V%2FScreenshot%202026-02-16%20at%203.25.07%E2%80%AFPM.png?alt=media&amp;token=9497b159-905e-4f3e-b4f4-fe898df5f2b4" alt=""><figcaption></figcaption></figure>

#### Custom CSS Tip

at the top right of the CSS editor panel, you will find a hamburger icon which will open a menu for navigating all of the comments inside the CSS. this will make it easier to navigate.&#x20;

<figure><img src="https://1451250373-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FK1XjLxAlSabqcZ3vCvkD%2Fuploads%2FN7jOPl0GtnnrtP0eiqMG%2FScreenshot%202026-02-16%20at%203.27.29%E2%80%AFPM.png?alt=media&amp;token=e1247b0e-b27e-4efe-b7cc-f252ceb29065" alt=""><figcaption></figcaption></figure>

#### More information on header options here:

{% content-ref url="/pages/exwH2NFGhm9xb5KK1okl" %}
[Header (Element)](/customization/publish-your-docs/header-element)
{% endcontent-ref %}

### 2. Menu Wrapper

Think of the Menu Wrapper as your entire navigation system for both mobile and desktop. This element holds the custom CSS that controls how your menu behaves and looks at different breakpoints. You will find most of the custom CSS for the menu here.

To edit the CSS for the Menu Wrapper, right click on the component and click "Edit Component".\
\
The Menu Wrapper is responsible for:

* Handling the breakpoint where your layout changes between mobile and desktop
* Switching classes from ‎`.mobile` to ‎`.desktop` (and back) based on that breakpoint
* Switching behavior of dropdowns on mobile
* Switching navigation modes such as a reveal mode or stacked mode on mobile devices

#### More information on Menu Wrapper options here:

{% content-ref url="/pages/n0ShlbOn7mzjFmLJBqpj" %}
[Menu Wrapper (Component)](/customization/publish-your-docs/menu-wrapper-component)
{% endcontent-ref %}

### 3. Menu Dropdown

The Menu Dropdown is the element you use when you want navigation items to reveal additional content, such as submenus or mega menus.

You can think of it as a container that:

* Holds the content that appears when a user hovers or clicks on a menu item
* Connects to the Menu Wrapper so it behaves correctly at different breakpoints
* Works with the provided dropdown patterns so you can plug in complex layouts with minimal setup

Use this element any time you need a menu item to open a panel, grid of links, or richer content instead of just linking directly to a page.

#### More information on Menu Dropdown options here:

{% content-ref url="/pages/rZpGmjRJyuN0k4hFJfSE" %}
[Menu Dropdown (Component)](/customization/publish-your-docs/menu-dropdown-component)
{% endcontent-ref %}

### 4. Menu Link

This is a very simple element that is a link wrapped in an LI element. Makes it easy to place into your navigation and dropdown's without the added layers.

#### More information on Menu Link options here:

{% content-ref url="/pages/PA89sUXGAvDZ7A30Uez9" %}
[Menu Link (Component)](/customization/publish-your-docs/menu-link-component)
{% endcontent-ref %}

### 5. Toggle

The toggle is responsible for opening and closing the menu. It comes with multiple options such as alternating labels or animated hamburger icons.

#### More information on Toggle options here:

{% content-ref url="/pages/zCnv1XIRPKZyqOiYAlFA" %}
[Toggle (Component)](/customization/publish-your-docs/toggle-component)
{% endcontent-ref %}


# Changelog

## v1.3 - June 18th, 2026

### Changes

1. added overlay mode for the header. Docs here.&#x20;
2. added ability to offset the hero section of a page to leave room for overlay headers
3. added the ability for a header to be overlay and sticky at the same time
4. added stripe like dropdown animation using the 'desktop dropdown animation mode' prop on the menu wrapper component set to 'morph'
5. added the ability to set the a 'menu link' component to open in a new tab
6. reorganized component properties to utilize groups and conditions
7. upgraded the css comments in the 'header wrapper' css to use the etch comment navigation
8. Upgraded the dropdowns to use safety triangle logic for moving between the dropdown triggers and the content instead of just time outs. this means you can move slowly from the trigger and the content and it will keep the content open. [see X post about this](https://x.com/nickarceco/status/2067371464957825157?s=20)

### Upgrade v1.2 -> v1.3

1. import the new header.json into a local install or blank install
2. turn on 'overwrite on paste' in the etch settings on your active install
3. from the blank install, copy and paste the 'menu wrapper', 'menu link', and 'menu dropdown' components and paste them into your active install anywhere. Click save, then delete the components you just pasted in. this should update those components props and internal code.
4. add these attributes to the 'header wrapper' element `data-overlay-header="false" data-overlay-header-offset="false"`
5. Copy the new overlay and sticky header css from your blank install on the 'header wrapper' component and replace the current' data-sticky-header' code with what should look like this.

```css
&[data-overlay-header='true'],
  &[data-sticky-header='true'] {
    inset-block-start: var(--wp-admin--admin-bar--height, 0px);
  }

  &[data-overlay-header='true'] {
    position: absolute;
  }

  &[data-sticky-header='true'] {
    position: sticky;
  }

  &[data-sticky-header='true'][data-overlay-header='true'] {
    position: fixed;
  }

  &[data-overlay-header='true'][data-overlay-header-offset='true'] + main > :first-child > :first-child {
    margin-block-start: var(--header-height);
  }
```

5. turn off 'overwrite on paste' in the etch settings on your active install

***

## v1.2 - March 17th, 2026

### Changes

1. updated the header wrapper JS to close any open dropdowns when the user scrolls down when the header is in sticky mode.
2. added two new dropdowns: dropdown perseus and dropdown vega

### Upgrade v1.1.1 -> v1.2

1. import the new header.json into a local install or blank install
2. copy the javascript from the header wrapper in the blank install
3. paste the javascript into your header wrapper javascript area inside of etch

***

## v1.1.1 - February 20th, 2026

### Changes

1. Resolved an issue where --dropdown-border-color was referrencing itself
2. Added new dropdown link variables to quickly change some styling for links inside a standard dropdown
3. Resolved an issue where --link-font-weight wasnt being used
4. Fixed an issue where dropdowns were using an old --dropdown-border variable which has recently been split into --dropdown-border-width and --dropdown-border-color

### Upgrade Video - v1.1 -> v1.1.1

Video: <https://magnified-web.neetorecord.com/watch/078ee495d144c0064511>

***

## v1.1 - February 16th, 2026

### Changes

1. Moved the custom CSS from the 'Menu Wrapper' -> 'Header Wrapper'. this accomplishes a few things.
   1. makes it easier if the menu wrapper needs a component change the new version can just be dropped in (assuming no changes were made on on the sites instance)
   2. allows for all custom css to be found at a higher level instead of needing to edit the menu wrapper to see it
2. Added some combo selector css to free up some additional lines

***

## v1.0.3 - February 10th, 2026

### Changes

1. did a pass for all the dropdown patterns so that they don't use --radius-s because ACSS does not have these on by default.&#x20;
2. Tightened up spacing on mobile for dropdown's so they werent so all over the place
3. While doing a pass I found a better way to apply padding for more compatibility. Padding for dropdown content has been moved from the .menu-dropdown\_\_content-wrapper -> .menu-dropdown\_\_content. This was moved to make it easier to use overflow: hidden; on the content like in dropdown Lyra

***

## V1.0.2 - February 10th, 2026

### Changes

1. Added the ability to target links within the nav that are are linked to the current page the visitor is on.&#x20;
   1. Current active link gets a class of .active-link
   2. \--link-color-active and --link-bg-active to change the color and background of the active links. You can also search for these variables inside the menu wrapper component to find where to add additional styling
2. Refined Dropdowns
   1. broke --dropdown-border into&#x20;
      1. \--dropdown-border-width
      2. \--dropdown-border-color
   2. added --dropdown-link-min-radius so that the links inside standard dropdowns have a minimum radius before they abide by a concentric radius from the dropdown content
   3. added better alignment for nested dropdowns

      <figure><img src="https://1451250373-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FK1XjLxAlSabqcZ3vCvkD%2Fuploads%2FBCMCwWsZbHubZSbDgylS%2Fimage.png?alt=media&amp;token=aa908bbc-c205-499c-9f9b-a41bb15c28c9" alt=""><figcaption></figcaption></figure>
3. ACSS V4 added padding to the header by default. Added CSS to remove that
4. Removed more found duplicate CSS
5. set .nav\_\_list to have align-items: center; by default to resolve nav items falling out of alignment

***

## V1.0.1 - February 5th, 2026

### Changes

1. Fixed backdrop animation for off canvas mobile menu. Was instantly disappearing on close
2. Updated accordion mode logic so when you open a dropdown in accordion mode it will make sure it stays in view
3. Updated logic so that if menu is open on mobile and user resizes to desktop mode, the mobile menu closes
4. Removed some duplicate CSS
5. Renamed Etch Layer 'Header' -> 'Header Wrapper'&#x20;
6. Renamed Etch Layer 'Header Inner' -> 'Header'

***

## V1 Release - Feb 4th, 2026


# Component Options

The Etch Menu Builder is composed of five core pieces that work together to create a flexible, accessible navigation system. Each component focuses on a specific responsibility and layout, behavior, so you can mix and match them to fit your design.

The next few pages will cover each component, their core behaviors, and customization options.


# Header (Element)

The Header component provides functionality for sticky behavior, hide-on-scroll effects, and accessibility features like skip links. It is configured primarily through data attributes on the header element.

## Example HTML of the header:

The `.header-wrapper` is to be treated like an invisible container not meant for styling but for functionality. Styling should be delegated to the .header when needed.

```html
<!-- Invisible container used for behavior and interaction -->
<header class="header-wrapper" 
        data-sticky-header="true" 
        data-hide-on-scroll="true" 
        data-hide-at="200" 
        data-show-on-scroll-up="true"
        data-skip-link="true">
        <!-- Your actual header for content and styling -->
        <div class="header">  
                <!-- Header Content -->
        </div>
</header>
```

## Data Attributes Configuration

<table data-full-width="false"><thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td><code>data-sticky-header</code></td><td><code>boolean</code></td><td><code>false</code></td><td>When set to <code>"true"</code>, enables the sticky header logic, including scroll-based class toggling.</td></tr><tr><td><code>data-hide-on-scroll</code></td><td><code>boolean</code></td><td><code>false</code></td><td>When set to <code>"true"</code>, allows the header to be hidden on the user scrolling down the page.</td></tr><tr><td><code>data-show-on-scroll-up</code></td><td><code>boolean</code></td><td><code>false</code></td><td>If <code>"true"</code>, the header will hide when scrolling down and reappear as soon as the user scrolls up. If <code>"false"</code>, the header stays hidden once past the threshold. <mark style="color:$warning;">This requires:</mark> <mark style="color:$warning;">data-hide-on-scroll="true"</mark></td></tr><tr><td><code>data-hide-at</code></td><td><code>number</code></td><td><code>200px</code></td><td>The pixel threshold (scroll distance from top) before the hiding logic is activated. <mark style="color:$warning;">This requires: data-hide-on-scroll="true"</mark></td></tr><tr><td><code>data-overlay-header</code></td><td><code>boolean</code></td><td>false</td><td>If <code>"true"</code>, the header will sit on top of the hero on every page. if <code>data-sticky-header="true"</code> as well it will sit on top of the hero and stick with the users scroll.</td></tr><tr><td><code>data-overlay-header-offset</code></td><td><code>boolean</code></td><td>false</td><td>If <code>"true"</code>, the hero section will make the exact space necessary so that header doesnt overlap the content within the hero. <mark style="color:$warning;">This requires: data-overlay-header="true"</mark></td></tr><tr><td><code>data-skip-link</code></td><td><code>boolean</code></td><td><code>false</code></td><td>When set to <code>"true"</code>, automatically injects an accessible "Skip to main content" link at the start of the header targeting the <code>&#x3C;main></code> element.</td></tr></tbody></table>

***

## Behavioral Details

### CSS Variables

The script automatically calculates the header height and sets a global CSS variable:

* `--header-height`: Updated on load, resize, and content changes using `ResizeObserver`.
* This allows you to use this variable site wide and integrates with ACSS without needing to manually enter these values.

### Sticky Header (hide on scroll) Class States

The component toggles the following classes on the `.header-wrapper` element when the header is using `data-sticky-header` and `data-hide-on-scroll`:

* `.scrolling`: Added when the window is scrolled more than 0px from the top. Use this for adding shadows or reducing padding on scroll.
* `.hide`: Added when the scroll logic determines the header should be hidden.

## Hide Logic Modes

1. **Static Hide (default)**:
   * Requires: `data-hide-on-scroll="true"` and `data-hide-at="[threshold]"`.
   * Behavior: Header hides once you scroll past the threshold and stays hidden.
2. **Responsive Hide (Scroll Up)**:
   * Requires: `data-hide-on-scroll="true"`, `data-hide-at="[threshold]"`, and `data-show-on-scroll-up="true"`.
   * Behavior: Header hides past the threshold when scrolling down. It immediately reappears when the user scrolls back up (with a 5px tolerance).

## Skip Link

If `data-skip-link="true"` is set:

* An `<a>` tag with class `.skip-link` is prepended to the header.
* The first `<main>` element on the page is automatically given `id="main"` if it doesn't have one.


# Menu Wrapper (Component)

The Menu Wrapper component is the primary controller for layout transitions between desktop and mobile views. It manages breakpoint-specific logic, repositions the menu in the DOM for off‑canvas behavior, and keeps its state synchronized with the document body.

## Core Behaviors

### 1. Breakpoint Management

The component uses Mobile Breakpoint prop to manage mobile breakpoint. It automatically applies state classes to the wrapper:

* Adds `.mobile` and removes `.desktop` when below the breakpoint.
* Adds `.desktop` and removes `.mobile` when above the breakpoint.

### 2. Desktop Modes

Depending on the `Destkop Dropdown Animation Mode` component prop, additional classes and logic are applied:

* **Default Dropdown:**&#x20;
  * Treats dropdowns as expected with normal behavior
* **Morph:**
  * Treats dropdowns as one coheseive dropdown container that morphs between each other similarly called the 'stripe menu effect'.&#x20;
  * This will move all dropdown containers into one parent element in the DOM the `.morph-container` and that gets created directly below the navs list.

### 2. Mobile Modes

Depending on the `Mobile Menu Mode` component prop, additional classes and logic are applied:

* **Off Canvas Mode:**&#x20;
  * Creates an Off Canvas effect for the mobile menu
  * Adds ‎`.off-canvas-mode` and moves the menu wrapper in the DOM so it becomes a sibling of the header inner element. This ensures correct overlay positioning.
* **Reveal Mode:**&#x20;
  * Creates a revealing effect for the mobile menu where it reveals itself below the header.
  * Adds ‎`.reveal-mode` without changing the menu wrapper’s position in the DOM.

### 3. DOM Restoration

When returning to the desktop view, the component automatically moves the menu wrapper back to its original position in the DOM, using its location at initialization as the reference.

## Component Properties

The Menu Wrapper exposes a set of component properties that control dropdown behavior and the mobile version of the menu.

| Property                              | Description                                                                                     | Accepted Value                                       | Required |
| ------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------- |
| Menu Class                            | Add a class to the menu wrapper element for styling without altering the component              | Class String                                         | no       |
| Desktop Dropdown Interaction          | Choice of how dropdown interacts on desktop                                                     | <p>Select: <br>Hover, Hover & Click</p>              | Yes      |
| Desktop Dropdown Animation Mode       | Sets the animation mode for all dropdowns within the menu wrapper on desktop                    | Select: Default Dropdown, Morph                      | no       |
| Mobile Breakpoint                     | At what breakpoint should the menu change to mobile                                             | px                                                   | Yes      |
| Mobile Dropdown Mode                  | Choice of how dropdowns should interact on mobile devices                                       | Select: Accordion, Stacked                           | Yes      |
| Mobile Menu Mode                      | Choice of how the entire menu should interact on mobile devices                                 | <p>Select: <br>Off Canvas, <br>Reveal</p>            | Yes      |
| Mobile Menu Transition Duration       | How fast the mobile menu should transition                                                      | CSS timing duration                                  | Yes      |
| Mobile Off Canvas Backdrop            | While in Off Canvas Menu Mode; this adds a backdrop to the off canvas menu.                     | Toggle                                               | No       |
| Mobile Backrdop Color                 | Sets the color of the backdrop if enabled                                                       | CSS background color                                 | No       |
| Mobile Backdrop Opacity               | Sets the opacity of the backdrop if enabled                                                     | Percentage Value                                     | No       |
| Mobile Off Canvas Animation Direction | When in Off Canvas Mode this sets the direction in which the off canvas menu will come in from. | Select: From Right, From Left, From Top, From Bottom | No       |

## Dependencies

* **Header Inner:** The component requires an element with ‎`data-menu-element="header-inner"` as a reference point for DOM movement in mobile mode.

***

## Available CSS Variables

Use the following CSS variables to control core styling of elements inside the menu wrapper without needing to override selector specificity.

| Variable                              | Description                                                                                                       |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| --mobile-menu-padding-inline          | Changes the inline padding used in the mobile menu. Use this for custom elements to keep spacing consistent.      |
| --mobile-menu-padding-block           | changes the block padding used throughoutt the mobile menu. can be used for custom elements to create consistency |
| --mobile-menu-bg-color                | sets the background color for the mobile menu                                                                     |
| --mobile-menu-width                   | sets the max width for the mobile menu when its in Off Canvas Mode                                                |
| --mobile-menu-dropdown-stacked-timing | sets the timing function for the stacked dropdown animation                                                       |

## Helper Classes

The menu wrapper comes with two additional helper classes to help you hide content inside of the Menu Wrapper Component.

| Class                   | Description                                                |
| ----------------------- | ---------------------------------------------------------- |
| .hide-from-desktop-menu | Hides the element from the desktop breakpoint of the menu. |
| .hide-from-mobile-menu  | Hides the element from the Mobile breakpoint of the menu.  |

## Additional Helper Variables

There are a few additional helper variables that are included inside the CSS for the Menu Wrapper Component.

| Variable               | Description                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------------- |
| --link-color           | Sets the color of top level links and links inside of none mega menu links.                  |
| --link-color-hover     | Sets the hover color of top level links and links inside of none mega menu links.            |
| --link-bg-color        | Sets the background color of top level links and links inside of none mega menu links.       |
| --link-bg-color-hover  | Sets the hover background color of top level links and links inside of none mega menu links. |
| --link-font-weight     | Sets the font weight of top level links and links inside of none mega menu links.            |
| --link-transition      | Sets the transition of top level links and links inside of none mega menu links.             |
| --link-color-active    | Sets the color of the link when its the currently active page link.                          |
| --link-bg-color-active | Sets the background of the link when its the currently active page link.                     |


# Menu Dropdown (Component)

The Menu Dropdown component controls how individual dropdowns behave within the menu system. It supports multiple modes that adapt to screen size and menu configuration so your navigation remains usable and consistent across devices.

The component is class driven. As the dropdown changes state or mode, it applies and removes specific classes that you can target in your CSS.

## Core Behavior

### 1. Open State

When a dropdown is opened, the component toggles an ‎`.is-open` class on the dropdown root element. This class is used to:

* Show or hide the dropdown content
* Trigger open / close transitions
* Apply visual states such as active indicators or icon rotations

### 2. Nested Dropdowns

The component supports nested dropdowns, such as multi-level navigation structures.

Each nested dropdown includes collision detection logic that:

* Monitors the size of the browser window
* Checks the position and dimensions of each dropdown
* Repositions dropdowns automatically to prevent them from rendering off-screen

If a nested dropdown would overflow the viewport, it is re-aligned (for example, flipping its position to the opposite side) so content remains fully visible.

### 3. Modes

The dropdown can operate in different modes depending on the breakpoint configuration of the parent Menu Wrapper component. These modes are applied via classes on the dropdown and change both behavior and styling expectations.

Each mode is mutually exclusive at a given breakpoint.

#### Dropdown Mode (Desktop only)

In Dropdown Mode, the component behaves like a standard desktop dropdown.

* Dropdowns open below their triggers
* Interaction is defined by the prop `Activation Method` and can either inherit from the parent Menu Wrapper component or act independently.
* The dropdown root receives a ‎`.dropdown-mode` class for styling and layout

{% hint style="info" %}
This component can be used outside of the menu wrapper or the menu system. When outside of the Menu Wrapper Compontent it will be restricted to the Dropdown Mode.
{% endhint %}

#### Accordion Mode (Mobile only)

In Accordion Mode, the dropdown behaves like an accordion panel.

* Content expands and collapses vertically within the main menu flow
* Only the location of the ‎`.is-open` dropdown changes; the menu structure remains in place
* The dropdown root receives an ‎`.accordion-mode` class for mode specific styling

Use Accordion Mode when you want nested items to reveal inline, without overlaying other content.

#### Stacked Mode (Mobile only)

In Stacked Mode, dropdowns behave like stacked cards on mobile devices.

* Dropdown content is visually separated into stacked layers
* Animations give a card-like transition between levels of navigation
* The dropdown root receives a ‎`.stacked-mode` class for styling
* Dropdown content is moved under the ‎`ul.nav__list` element at breakpoint change so stacking and animation can be handled consistently
* Sets the back button `.menu-dropdown-back-button` to display allowing the user to navigate back levels.

Use Stacked Mode when you want a more app-like, layered navigation experience on smaller screens.

### 4. Accessibility

The Menu Dropdown component automatically manages core accessibility attributes and keyboard behavior, including:

* Applying and syncing ‎`aria-expanded`, ‎`aria-controls`, and other relevant ARIA attributes
* Managing focus when dropdowns open and close so keyboard users remain in context
* Handling the ‎`Esc` key to close open dropdowns and return focus to the trigger

## Component Properties

The Menu Dropdown exposes a set of component properties that control dropdown behavior and the mobile version of the menu.

| Property                | Description                                                                                             | Accepted Value                                                    | Required |
| ----------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------- |
| Dropdown Tag            | Sets the Tag of the dropdown to fit its current usage.                                                  | Select: li, div                                                   | Yes      |
| Dropdown Label          | Changes the label text for the dropdown button                                                          | Text                                                              | Yes      |
| Activation Method       | Changes how the user interacts with the dropdown                                                        | Select: Respect Menu (when in menu wrapper), Hover & Click, Click | Yes      |
| Mark As Mega Menu       | Sets \[data-mega-menu] to true for styling inheritance for mega menus                                   | Toggle                                                            | Yes      |
| Match Width             | Sets the dropdown to inherit the width of a CSS selector within the .header-wrapper                     | CSS ID or CSS Class                                               | No       |
| Extra Classes           | Adds other classes to the dropdown for further targeting if needed.                                     | CSS class without the '.'                                         | No       |
| Hide at Menu Breakpoint | Sets the dropdown to be hidden at any particular menu breakpoint when inside the Menu Wrapper Component | Select: None, Desktop, Mobile                                     | No       |
| Dropdown Content Tag    | Changes the HTML Tag of the element that wraps the dropdownContent slot.                                | Select: ul, ol, div                                               | Yes      |

## Available CSS Variables

Use the following CSS variables to control core styling of elements inside the dropdown without needing to override selector specificity.

| Variable                        | Description                                                                                                                                                         |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --dropdown-padding              | Sets the padding on the dropdown content. This also dictates the offset of each subsquent nested dropdown content when in dropdown mode.                            |
| --dropdown-border-radius        | Sets the border radius of the dropdown content when in dropdown mode.                                                                                               |
| --dropdown-border-width         | Sets the border width on the dropdown content when in dropdown mode.                                                                                                |
| --dropdown-border-color         | Sets the border color on the dropdown content when in dropdown mode.                                                                                                |
| --dropdown-bg                   | Sets the background on the dropdown content when in dropdown mode.                                                                                                  |
| --dropdown-box-shadow           | Sets the box shadow on the dropdown content when in dropdown mode.                                                                                                  |
| --dropdown-transition-property  | Sets a global transition properties for dropdown content.                                                                                                           |
| --dropdown-transition-duration  | Sets the global transition duration for opening and closing dropdowns (not in stacked mode).                                                                        |
| --dropdown-transition-delay     | Sets the global transition delay for opening and closing dropdowns.                                                                                                 |
| --dropdown-transition-timing    | Sets the global transition timing function for opening and closing dropdowns. (excluding stacked mode. stacked mode will use --mobile-menu-dropdown-stacked-timing) |
| --dropdown-closed-top           | Sets the gap of dropdown content from its corresponding trigger when in dropdown mode and its closed.                                                               |
| --dropdown-open-top             | Sets the gap of dropdown content from its corresponding trigger when in dropdown mode and its open.                                                                 |
| --dropdown-trigger-icon-size    | Sets the size of the chevron icon in the dropdown trigger. Defaults to 1em.                                                                                         |
| --dropdown-link-min-radius      | Sets the minimum radius value for links inside standard dropdowns before they abide be a concentric radius                                                          |
| --dropdown-link-padding-inline  | Inline padding for links inside of standard dropdowns                                                                                                               |
| --dropdown-link-padding-block   | Block padding for links inside of standard dropdowns                                                                                                                |
| --dropdown-link-font-weight     | Font weight for links inside of standard dropdowns                                                                                                                  |
| --dropdown-link-color           | Color for links inside of standard dropdowns                                                                                                                        |
| --dropdown-link-color-hover     | Color for links inside of standard dropdowns when they are hovered                                                                                                  |
| --dropdown-link-color-active    | Color for links inside of standard dropdowns when its the current link                                                                                              |
| --dropdown-link-bg-color        | Background color for links inside of standard dropdowns                                                                                                             |
| --dropdown-link-bg-color-hover  | Background color for links inside of standard dropdowns when hovered                                                                                                |
| --dropdown-link-bg-color-active | Background color for links inside of standard dropdownswhen its the current link                                                                                    |


# Menu Link (Component)

A very simple component that is an \<a> wrapped in an `<li>` . This was added to make it easier to add and remove navigation items inside your navigation list or within dropdowns.

## Component Properties

| Property                | Description                                                                                             | Accepted Value                | Required |
| ----------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------- | -------- |
| Custom Class            | Set a custom class on the root element of the component.                                                | CSS class without the '.'     | No       |
| Label                   | Sets the label of the `<a>` inside the element                                                          | Text                          | Yes      |
| Link URL                | Sets the link of the href                                                                               | Link                          | Yes      |
| Hide at Menu Breakpoint | Sets the dropdown to be hidden at any particular menu breakpoint when inside the Menu Wrapper Component | Select: None, Desktop, Mobile | No       |


# Toggle (Component)

The Toggle Component provides a flexible way to manage UI toggles (buttons that enable/disable a target element with ). It supports multiple buttons targeting the same element, focus management, scroll freezing, accessibility features, and a global JavaScript API.

{% hint style="info" %}
This component can be used outside of the menu wrapper or the menu system.
{% endhint %}

## Core Behaviors

### 1. Toggling Class

Toggles the class of any element based on its ID. Gives you the ability to toggle an off canvas or modal.

### 2. Accessibility

The toggle handles accessibility for you by controlling tab focus into the new element that you are toggling and moving focus to the next focus element ID you give it or keeps focus on itself

## Component Properties

Toggles have a set of component properties that allow you to control each instance.

| Property                  | Description                                                                                            | Accepted Value                  | Required |
| ------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------- | -------- |
| Custom ID For This Toggle | Sets the ID for that current toggle element                                                            | CSS ID String without the '#'   | Yes      |
| Aria Label                | Sets the aria label for the toggle element. Recommend language like "Toggle Menu"                      | Text                            | Yes      |
| Target Element ID         | The CSS ID of the element you wish to target with the toggle                                           | CSS ID String without the '#'   | Yes      |
| Toggled Class             | The class that will be toggled on the Target Element                                                   | CSS Class without the '.'       | Yes      |
| Target is a Modal?        | Adds `role="dialog"` and `aria-modal="true"` to the target for accessibility.                          | Toggle                          | No       |
| Next Focus Item ID        | ID of element to focus to once toggled. Leave empty to leave it to self                                | CSS ID String without the '#'   | No       |
| Label Type                | When set to single label it will be one label that displace. Alternate will switch label when toggled. | Select: None, Single, Alternate | Yes      |
| First Label               | Label text when toggle is inactive (for alternate mode) or constant label (for single label mode)      | Text                            | No       |
| Second Label              | Label text when toggle is active (for alternate mode).                                                 | Text                            | No       |
| Icon Type                 | Change the icon type to be used                                                                        | Select                          | Yes      |
| Custom SVG Icon           | If Icon Type is set to Custom SVG this will set the icon                                               | SVG                             | No       |
| Freeze Body Scroll        | If true, locks the body scroll when toggled.                                                           | Toggle                          | No       |

## Available CSS Variables

There are a few CSS variables to control the core styling of the toggle externally without the need to override specificity.

| Variable                     | Default                                         |
| ---------------------------- | ----------------------------------------------- |
| --toggle-transition-duration | --ease-smooth: cubic-bezier(0.25, 0.8, 0.3, 1); |
| --toggle-icon-gap            | 0.25em                                          |
| --toggle-icon-size           | 1.25em                                          |
| --toggle-icon-line-width     | 8px                                             |
| --toggle-icon-color          | currentColor                                    |


# How To's


# Dropdown How To's

## How to change the default dropdown back button label.

1. Right click on any menu dropdown component and click "Edit Component"
2. Find the span label inside the back button

<figure><img src="https://1451250373-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FK1XjLxAlSabqcZ3vCvkD%2Fuploads%2FsmLGlgJ1XsmdzEf8BnNp%2F27f7ca57-5ff8-409c-ae08-a0addffd0b1b-spark-clipboard.png?alt=media&amp;token=2e98051d-a0ea-46d7-9034-d71170f73cdf" alt=""><figcaption></figcaption></figure>

3. Change its content to whatever you wish.&#x20;
4. (IMPORTANT) navigate to the back button element and update the aria label on the back button as well to match


# Add a Banner Above the Header

The announcement bar scrolls up and out of view while the main header follows it up, then stops and pins below the top of the screen. No gap, no jump, no double-height header.

This works whether the header resolves to `position: sticky` or `position: fixed`, so toggling `data-overlay-header` will not break it.

### Before you start

Your header needs this structure, with the banner **inside** the Header Wrapper:

```html
<header class="header-wrapper">   <!-- behavior shell, has the attached script -->
  <div class="header-banner">     <!-- announcement bar -->
  <div class="header">            <!-- logo + nav -->
```

### Step 1: Understand why it doesn't work by default

`.header-wrapper` is the positioned box, so everything inside rides along with it, banner included.

The fix is to offset the wrapper upward by exactly the banner's height. The banner ends up above the viewport while `.header` lands at the top.

How much work that takes depends on which position the wrapper resolves to:

| Attributes       | Computed position | Who handles the timing                           |
| ---------------- | ----------------- | ------------------------------------------------ |
| Sticky only      | `sticky`          | The browser. A static negative offset is enough. |
| Sticky + overlay | `fixed`           | You. Fixed never waits for scroll.               |

`position: sticky` waits for scroll natively. `position: fixed` does not, so a negative offset applied to it takes effect at first paint and the banner would be gone before the visitor scrolls at all.

This guide supports both by publishing two CSS variables and letting each rule use only what it needs.

### Step 2: Add the JavaScript

Paste this into the Header Wrapper's JavaScript panel at the very bottom. It is fully self-contained, so it can go anywhere in the file and nothing existing needs editing.

```javascript
// BANNER HEIGHT + SCROLL OFFSET
// Publishes two custom properties:
//   --banner-height    the banner's measured height
//   --header-scroll-y  scroll position, clamped to that height
// The sticky rule uses only the first. The fixed rule uses both.
(() => {
  const bannerEl = document.querySelector('.header-banner');
  if (!bannerEl) return;

  const root = document.documentElement;
  let bannerHeight = 0;
  let lastY = -1;
  let ticking = false;

  const updateScroll = () => {
    ticking = false;
    // Clamped, so once you're past the banner we stop writing entirely
    const y = Math.min(Math.max(0, window.scrollY), bannerHeight);
    if (y === lastY) return;
    lastY = y;
    root.style.setProperty('--header-scroll-y', `${y}px`);
  };

  const measureBanner = () => {
    bannerHeight = bannerEl.offsetHeight;
    root.style.setProperty('--banner-height', `${bannerHeight}px`);
    lastY = -1; // force a re-write against the new height
    updateScroll();
  };

  const onScroll = () => {
    if (!ticking) {
      ticking = true;
      requestAnimationFrame(updateScroll);
    }
  };

  if (window.ResizeObserver) {
    // border-box so padding/border changes at breakpoints re-measure too
    new ResizeObserver(measureBanner).observe(bannerEl, { box: 'border-box' });
  } else {
    window.addEventListener('resize', measureBanner);
    window.addEventListener('load', measureBanner);
  }

  window.addEventListener('scroll', onScroll, { passive: true });
  measureBanner();
})();
```

Four details worth understanding:

* `if (!bannerEl) return;` means pages without a banner exit immediately and keep stock behavior.
* The `requestAnimationFrame` throttle collapses scroll events down to one write per frame, since scroll fires far more often than the screen repaints.
* The clamp plus the `lastY` guard means writes stop completely once you are past the banner. You pay for roughly the first 76px of scroll and nothing after.
* `{ box: 'border-box' }` matters because the ResizeObserver default is content-box, which ignores padding and border changes.

### Step 3: Replace the Overlay & Sticky Header CSS

Open the Header Wrapper's CSS panel and find the `/* ## Overlay & Sticky Header */` section. Use the comment navigation menu at the top left of the CSS editor to jump straight to it.

Replace everything from that comment down to (but not including) `/* ### hide on scroll */` with the block below.

```css
  /* ## Overlay & Sticky Header */
  &[data-overlay-header='true'],
  &[data-sticky-header='true'] {
    inset-block-start: var(--wp-admin--admin-bar--height, 0px);
  }

  &[data-overlay-header='true'] {
    position: absolute;
  }

  &[data-sticky-header='true'] {
    position: sticky;
  }

  /* Sticky without overlay.
     Position comes from the rule above; this only shifts the offset up by
     the banner's height. Sticky handles the timing natively, so
     --header-scroll-y is deliberately not used here. */
  &[data-sticky-header='true']:not([data-overlay-header='true']) {
    inset-block-start: calc(
      var(--wp-admin--admin-bar--height, 0px) - var(--banner-height, 0px)
    );
    /* Optional: reclaim the flow space so content starts at the top of the
       page, the way overlay does. Delete this line for normal flow. */
    margin-block-end: calc(-1 * var(--header-height, 0px));
  }

  /* Sticky + overlay resolves to fixed. A fixed box cannot wait for scroll
     the way sticky does, so min() supplies that timing from
     --header-scroll-y and caps the movement at the banner's height. */
  &[data-sticky-header='true'][data-overlay-header='true'] {
    position: fixed;
    inset-block-start: calc(
      var(--wp-admin--admin-bar--height, 0px) -
      min(var(--header-scroll-y, 0px), var(--banner-height, 0px))
    );
  }

  &[data-overlay-header='true'][data-overlay-header-offset='true'] + main > :first-child > :first-child {
    margin-block-start: var(--header-height);
  }
```


# Toggle API

### Global API (`window.Toggles`)

The component exposes a global registry to interact with toggle instances programmatically.

#### `window.Toggles.getAll()`

Returns an array of all initialized `Toggle` class instances on the page.

#### `window.Toggles.findById(id)`

Returns the `Toggle` instance with the matching button ID.

#### `window.Toggles.findByTarget(targetId)`

Returns an array of `Toggle` instances that target the specified element ID.

***

### Instance API (`Toggle`)

Each toggle instance provides properties and methods for state control.

#### Properties

* `id`: The unique ID of the toggle (generated if not present on the button).
* `button`: The HTML button element.
* `target`: The HTML target element (resolved at runtime).
* `isEnabled`: Boolean indicating if the toggle is currently active (supports getter and setter).
* `config`: The parsed configuration object (read-only via `getConfig()`).

#### Methods

* `enable()`: Activates the toggle.
* `disable()`: Deactivates the toggle.
* `toggle()`: Flips the current state.
* `setEnabled(boolean)`: Explicitly sets the state.
* `updateLabelWidth()`: Force recalculation of label widths (if in `alternate` mode).
* `on(eventName, handler)`: Adds an event listener to the **target** element.
* `off(eventName, handler)`: Removes an event listener from the **target** element.
* `getConfig()`: Returns a copy of the parsed configuration from data attributes.

***

### Events

Events are dispatched from the **target element**. They bubble up and are not cancelable.

| Event Name       | Detail                       | Description                           |
| ---------------- | ---------------------------- | ------------------------------------- |
| `toggle:enable`  | `{ id, isEnabled: true }`    | Fired when the toggle is activated.   |
| `toggle:disable` | `{ id, isEnabled: false }`   | Fired when the toggle is deactivated. |
| `toggle:change`  | `{ id, isEnabled: boolean }` | Fired on any state change.            |

***

### Examples

#### Programmatic Toggle Control

```javascript
// Find a toggle by its button ID
const menuToggle = window.Toggles.findById("menu-toggle");

// Open the toggle target
menuToggle.enable();

// Close the toggle target
menuToggle.disable();

// Toggle the current state
menuToggle.toggle();

// Set state explicitly
menuToggle.setEnabled(true);  // Open
menuToggle.setEnabled(false); // Close

// Check current state
if (menuToggle.isEnabled) {
  console.log("Menu is open");
}

// Get all toggle instances
const allToggles = window.Toggles.getAll();
console.log(`Found ${allToggles.length} toggles on the page`);
```

#### Finding Toggles by Target

```javascript
// Find all toggle buttons that control "mobile-menu"
const menuToggles = window.Toggles.findByTarget("mobile-menu");

console.log(`${menuToggles.length} buttons control the mobile menu`);

// Disable all of them (closes the menu)
menuToggles.forEach(toggle => toggle.disable());

// Enable via any of them (opens the menu, all stay synced)
menuToggles[0].enable();
```

#### Event Listeners for State Changes

```javascript
const menuTarget = document.getElementById("mobile-menu");

// Listen for enable (open)
menuTarget.addEventListener("toggle:enable", (e) => {
  console.log(`Toggle ${e.detail.id} opened the menu`);
  
  // Track analytics
  analytics.track("menu_opened");
});

// Listen for disable (close)
menuTarget.addEventListener("toggle:disable", (e) => {
  console.log(`Toggle ${e.detail.id} closed the menu`);
});

// Listen for any state change
menuTarget.addEventListener("toggle:change", (e) => {
  const { id, isEnabled } = e.detail;
  console.log(`Menu is now ${isEnabled ? "open" : "closed"}`);
  
  // Update UI based on state
  document.body.classList.toggle("menu-active", isEnabled);
});
```

#### Using Instance Event Methods

```javascript
const toggle = window.Toggles.findById("menu-toggle");

// Add event listener via instance method (attaches to target element)
function handleChange(e) {
  console.log(`State changed: ${e.detail.isEnabled}`);
}

toggle.on("toggle:change", handleChange);

// Later, remove the listener
toggle.off("toggle:change", handleChange);
```

#### Reading Toggle Configuration

```javascript
const toggle = window.Toggles.findById("menu-toggle");

// Get a copy of the parsed configuration
const config = toggle.getConfig();

console.log(config);
// Output:
// {
//   targetId: "mobile-menu",
//   targetToggledClass: "is-open",
//   freezeScroll: true,
//   labelMode: "alternate",
//   nextFocusId: null,
//   targetIsModal: false
// }

// Access individual config values
if (config.freezeScroll) {
  console.log("This toggle freezes scroll when active");
}
```

#### Close All Open Toggles

```javascript
// Close all active toggles on the page
function closeAllToggles() {
  window.Toggles.getAll()
    .filter(toggle => toggle.isEnabled)
    .forEach(toggle => toggle.disable());
}

// Example: Close all toggles on route change (SPA)
window.addEventListener("popstate", closeAllToggles);

// Example: Close all toggles with Escape (handled automatically, but custom logic)
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape") {
    closeAllToggles();
  }
});
```


# Menu Wrapper API

## Dispatched Events

The component dispatches these custom events from its own element when the breakpoint state changes:

| Event Name            | Detail | Description                       |
| --------------------- | ------ | --------------------------------- |
| `menuWrapper:mobile`  | None   | Fired when entering mobile view.  |
| `menuWrapper:desktop` | None   | Fired when entering desktop view. |

***

### Examples

#### Detecting Mobile/Desktop Transitions

```javascript
const menuWrapper = document.querySelector('[data-menu-element="menu-wrapper"]');

// Listen for mobile view entry
menuWrapper.addEventListener("menuWrapper:mobile", () => {
  console.log("Switched to mobile view");
  
  // Example: Initialize mobile-specific features
  initMobileGestures();
});

// Listen for desktop view entry
menuWrapper.addEventListener("menuWrapper:desktop", () => {
  console.log("Switched to desktop view");
  
  // Example: Clean up mobile features
  destroyMobileGestures();
});
```

#### Checking Current Viewport State

```javascript
const menuWrapper = document.querySelector('[data-menu-element="menu-wrapper"]');

// Check if currently in mobile view
function isMobileView() {
  return menuWrapper.classList.contains("mobile");
}

// Check if currently in desktop view
function isDesktopView() {
  return menuWrapper.classList.contains("desktop");
}

// Check mobile menu mode
function getMobileMode() {
  if (menuWrapper.classList.contains("off-canvas-mode")) {
    return "off-canvas";
  } else if (menuWrapper.classList.contains("reveal-mode")) {
    return "reveal";
  }
  return null; // Desktop mode
}

// Example usage
if (isMobileView()) {
  console.log(`Mobile mode: ${getMobileMode()}`);
}
```

#### Integration with Toggle Component

```javascript
const menuWrapper = document.querySelector('[data-menu-element="menu-wrapper"]');

// Listen for menu open/close via Toggle component
menuWrapper.addEventListener("toggle:change", (e) => {
  const { id, isEnabled } = e.detail;
  
  if (isEnabled) {
    console.log("Menu opened");
    // Track analytics, pause videos, etc.
  } else {
    console.log("Menu closed");
    // Resume content, clean up state
  }
});

// Programmatically control the menu via Toggle API
const menuToggle = window.Toggles.findByTarget("main-nav")[0];

// Open the mobile menu
menuToggle.enable();

// Close the mobile menu
menuToggle.disable();

// Check if menu is open
if (menuToggle.isEnabled) {
  console.log("Menu is currently open");
}
```

#### Responsive Behavior Coordination

```javascript
const menuWrapper = document.querySelector('[data-menu-element="menu-wrapper"]');

// Close mobile menu when switching to desktop
menuWrapper.addEventListener("menuWrapper:desktop", () => {
  // Find all toggles targeting this menu and disable them
  const toggles = window.Toggles.findByTarget(menuWrapper.id);
  toggles.forEach(toggle => {
    if (toggle.isEnabled) {
      toggle.disable();
    }
  });
  
  // Remove body scroll lock if present
  document.body.classList.remove("menu-open", "scroll-locked");
});

// Coordinate with dropdown mode changes
document.addEventListener("menuDropdown:modeChange", (e) => {
  const { newMode } = e.detail;
  
  // When dropdowns switch to stacked mode, we're in mobile
  if (newMode === "stacked") {
    console.log("Dropdowns now in stacked mobile mode");
  }
});
```


# Dropdown API

### JavaScript API

#### Global Helper: `window.Dropdowns`

* `window.Dropdowns.getAll()`: Returns an array of all initialized Dropdown instances.
* `window.Dropdowns.findById(idOrDomId)`: Finds a specific instance by its numeric ID or DOM ID (e.g., `dropdown-1`).

#### Instance Methods

If you have a reference to a dropdown instance, you can call these methods:

* `instance.open({ cascade: true })`: Opens the dropdown.
* `instance.close({ cascade: true })`: Closes the dropdown.
* `instance.toggle({ cascade: true })`: Toggles the open state.

### Custom Events

Events are dispatched from the main `[data-menu-element="dropdown"]` element and bubble up.

| Event                       | Description                                                                            |
| --------------------------- | -------------------------------------------------------------------------------------- |
| `menuDropdown:open`         | Fired when the dropdown opens.                                                         |
| `menuDropdown:close`        | Fired when the dropdown closes.                                                        |
| `menuDropdown:modeChange`   | Fired when the component switches between `dropdown`, `accordion`, or `stacked` modes. |
| `menuDropdown:open:[mode]`  | Specific open event (e.g., `menuDropdown:open:dropdown`).                              |
| `menuDropdown:close:[mode]` | Specific close event (e.g., `menuDropdown:close:stacked`).                             |

**Event Detail (`event.detail`):**

* `instanceId`: The unique numeric ID of the dropdown.
* `mode`: The current mode (`dropdown`, `accordion`, or `stacked`).
* `level`: The nesting level (0 for top-level).

***

### Examples

#### Programmatic Control

```javascript
// Find a dropdown by its DOM ID (e.g., "dropdown-1")
const dropdown = window.Dropdowns.findById("dropdown-1");

// Open a dropdown
dropdown.open();

// Close a dropdown (cascade: true closes children too)
dropdown.close({ cascade: true });

// Toggle open/closed state
dropdown.toggle();

// Close all open dropdowns on the page
window.Dropdowns.getAll()
  .filter(d => d.isOpen)
  .forEach(d => d.close({ cascade: true }));

// Find a dropdown by numeric ID
const firstDropdown = window.Dropdowns.findById(1);
```

#### Listening for Events

```javascript
// Listen for any dropdown opening
document.addEventListener("menuDropdown:open", (e) => {
  console.log(`Dropdown ${e.detail.instanceId} opened`);
  console.log(`Current mode: ${e.detail.mode}`);
  console.log(`Nesting level: ${e.detail.level}`);
});

// Listen for any dropdown closing
document.addEventListener("menuDropdown:close", (e) => {
  console.log(`Dropdown ${e.detail.instanceId} closed`);
});

// Listen for mode-specific events
document.addEventListener("menuDropdown:open:stacked", (e) => {
  console.log("A dropdown opened in stacked (mobile) mode");
});

document.addEventListener("menuDropdown:close:accordion", (e) => {
  console.log("A dropdown closed in accordion mode");
});
```

#### Detecting Mode Changes

```javascript
// Listen for responsive mode switches
document.addEventListener("menuDropdown:modeChange", (e) => {
  const { instanceId, mode, oldMode, newMode } = e.detail;
  
  console.log(`Dropdown ${instanceId} switched from ${oldMode} to ${newMode}`);
  
  // Perform actions based on new mode
  if (newMode === "stacked") {
    // Mobile stacked mode activated
    document.body.classList.add("mobile-nav-active");
  } else if (newMode === "dropdown") {
    // Desktop dropdown mode activated
    document.body.classList.remove("mobile-nav-active");
  }
});
```

#### Working with Nested Dropdowns

```javascript
// Get all dropdown instances
const allDropdowns = window.Dropdowns.getAll();

// Find top-level dropdowns only (level 0)
const topLevelDropdowns = allDropdowns.filter(d => d.level === 0);

// Find nested dropdowns (level > 0)
const nestedDropdowns = allDropdowns.filter(d => d.level > 0);

// Check if a dropdown has open children
const dropdown = window.Dropdowns.findById(1);
if (dropdown.hasOpenDescendant()) {
  console.log("This dropdown has open nested menus");
}

// Access parent/child relationships
const childDropdown = window.Dropdowns.findById(2);
if (childDropdown.parentInstance) {
  console.log("Parent dropdown ID:", childDropdown.parentInstance.id);
}
```


# Bricks Mega Menu Template

## --V1.5.6.3 (September 29th, 2025)

Very small update

<details>

<summary>Update Log</summary>

1. on the logo the url has been changed from "/" to use the {site\_url} dynamic data. This fixed an issue with multilingual websites sending the user back to the homepage that wasnt with their selected language
2. added the JS script to be right below the header upon import so you no longer have to import it separately.

</details>

### Upgrading from 1.5.6.2 -> 1.5.6.3

1. update the logos (desktop and mobile) to use {site\_url} instead of "/" for the url

## --V1.5.6.2 (July 23rd, 2025)

There was only one small update needed.&#x20;

<details>

<summary>Update Log</summary>

1. on mobile devices; when closing the menu, the menu items would flash quickly. This was due to some styling occurring on the bricks layer defaults.

</details>

### Upgrading from 1.5.6.1 -> 1.5.6.2

1. take everything from below&#x20;

```
/* -----------------------LOGIC *IGNORE*-------------------- */
```

from the new scss file and replace it in your file.&#x20;

## --V1.5.6.1 (Jul 17th, 2025)

In this update there were a few fixes that were needed due to the new 2.0 update.&#x20;

<details>

<summary>Update log</summary>

1. fixed an issue where mobile dropdown content wasn't filling the entire width of the mobile screen
2. fixed an issue where the css was targeting .header and it was messing up the bricks UI because the bricks UI also uses .header
3. updated some of the css to use @layer bricks.reset to solve some compatibility issues with the new bricks 2.0 cascade layer css

</details>

### Upgrading from 1.5.6 -> 1.5.6.1

1. update the .header selector to also target header .header to look like below

```scss
// header
header .header {
    padding-block: var(--space-m);
    padding-inline: var(--gutter);
    .header__container {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        align-items: center;
    }
    .header__logo {
        width: 4rem;
        height: auto;
    }
}
```

2. Simply take everything thats below

<pre><code><strong>/* -----------------------LOGIC *IGNORE*-------------------- */
</strong></code></pre>

from the new SCSS file and replace what exists on and below that line in your current SCSS.

## --V1.5.6 (April 11th, 2025)

Changed the way that sub menus open on mobile. before they use to open in position: absolute and this caused an issue where if you had a long navigation list and then clicked on a dropdown on mobile you would have to scroll back up to see the newly opened dropdown. This is now resolved.

### Upgrading from 1.5.5.1 -> 1.5.6

1. Simply take everything thats below

```
/* -----------------------LOGIC *IGNORE*-------------------- */
```

from the new SCSS file and replace what exists on and below that line in your current SCSS.\
\
2\. copy the new menu-v1.1.js file and replace your current header js with it

<details>

<summary>Update Log</summary>

1. reworked how sub menu dropdowns open up on mobile. If you have a long list you no longer have to scroll back up to see the newly opened dropdown.
2. Switched the icon and text positioning for the mobile back button so the icon is on the left and text is on the right for RTL languages.
3. the $nav-items-gap variable wasnt working so that is now fixed.
4. reworked the JS so that on mobile device when the menu is open, any dropdowns that are open are offset by the mobile header height

</details>

## --V1.5.5.1 (Mar 16th, 2025)

Reworked how the link styling happens on desktop. The default link styling and the hover styling are now separate mixins. This is due to how the elements needed to be styled depending on if they were a link or dropdown

### Upgrading from 1.5.5 -> 1.5.5.1

Upgrading can happen between these two but would be time consuming and would recommend comparing the your current file with the new one using [vs code compare ](https://grantwinney.com/how-to-compare-files-using-vs-code/)and leaving your own adjustments.

<details>

<summary>Update Log</summary>

1. changed out @mixin link-style to now have a @mixin link-hover-style for managing the hover state. this now applies to the open state of a dropdown as well
2. changed out the @mixin sticky-link-style to now also have a @mixin sticky-link-hover-style for the hover state

</details>

## --V1.5.5 (Mar 13th, 2025)

Pretty much a complete overhaul of the menu system. &#x20;

There is no upgrading to this new version. You will have to completely redo the header or use it only on new projects

### Upgrading from 1.5.4 -> 1.5.5

There is no upgrading on this one. you would have to start fresh or use on new projects.

<details>

<summary>Update Log</summary>

### Note worthy changes

#### 1. (New) Back Button is now an actual button that can be seen by language switcher plugins and can be styled using the below selector in the sheet.

```scss
// BACK BUTTON STYLING https://nickarce.com/back-button-styling/ ******
.mm-close-dropdown-btn {
    background-color: unset;
    gap: 0.5em;
    padding-inline-start: unset;
}
```

#### 2. (New) The template now has some JS included to maintain accessibility and to also close the the menu when its no longer at the breakpoint. This now means the breakpoint must be managed in the css and in the JS script that imports with the header.

#### 3. You can now easily change out the hamburger color if you are using a custom icon instead of one from the included libraries by setting the below setting to fill or stroke (depending on what your icon requires)

```scss
// IF USING A CUSTOM HAMBURGER ICON DO YOU WANT TO SET THE FILL OR STROKE COLOR ******
$custom-hamburger-icon-fill-stroke: none; //SELECTION (none/fill/stroke)
```

#### 4. you can now set the hamburger icon color if you have a sticky header

```scss
// SET STICKY HAMBURGER COLOR MUST HAVE $sticky-header-styles set to true for it to work ******
$hamburger-sticky-color-choice: red;
```

#### 5. You can now easily change out the mobile close color if you are using a custom icon instead of one from the included libraries by setting the below setting to fill or stroke (depending on what your icon requires)

```scss
// IF USING A CUSTOM CLOSE ICON DO YOU WANT TO SET THE FILL OR STROKE COLOR ******
$custom-close-icon-fill-stroke: none; // SELECTION(none/fill/stroke)
```

### Other changes

1. menu now supports RTL languages
2. menu now has some much nicer animations
3. breakpoint now needs adjustment on css sheet and js sheet

</details>

## --V1.5.4 (July 5th, 2024)

### Upgrading from 1.5.2 or 1.5.3 -> 1.5.4

{% embed url="<https://youtu.be/8APcobgkpGk>" %}

Updating for ACSS 3.0 changes

* Header Template V1.3.2 (no change)
* SCSS V1.5.4
* Dropdown Template Marble V1.1
* Dropdown Template Ruby v1.2
* Dropdown Template Slate v1.2
* Dropdown Template Sandstone v1.2

<details>

<summary>Update Log</summary>

NEW

```scss
// ARE YOU USING ACSS AUTO CONTENT GAP?
$fix-acss-content-gap: false; // SELECTION (true/false)
```

1. Added the above variable as a true or false variable to fix some issues with the ACSS auto content gapping. Turning this to "true" will remove the gapping in the header template that is being added. [IMAGE LINK](broken://pages/0waLvldTVyPbbVZLh1Yy)
2. When adding a max-width to all text elements in ACSS the menu would break appearance so a fix was implemented to ignore those changes. [IMAGE LINK ](https://share.zight.com/5zuKKLdm)
3. Reworked the CSS so you no longer have to duplicate dropdowns in order for them to inherit styling or use their classes, you can now drop in a 'dropdown' from the bricks elements into the 'nav items' item and it will auto inherit styling.

#### Other

* ACSS CSS

  * When using the new SCSS area within the ACSS dashboard there were some specificity issues due to the loading order of the sheet. Those specificity issues have been resolved with this new update.
  * Missed some var(--shade) variables in some of the dropdown templates. Those have been replaced with var(--neutral)
  * Color var(--action) replaced with var(--primary)

</details>

## --V1.5.3 (April 5th, 2024)

New dropdown and other fixes.

This update comes with:

* Header Template V1.3.2
* SCSS V1.5.3
* New Dropdown Template Marble
* Dropdown Template Ruby v1.1
* Dropdown Template Slate v1.1
* Dropdown Template Sandstone v1.1

<details>

<summary>Update Log</summary>

NEW

* Added new dropdown "Marble"

#### Bug fixes

* Removed duplicate CSS from SCSS sheet
* Header template and dropdown templates had missing classes.

#### Other

* ACSS CSS
  * replaced box shadows with ACSS variables
  * color black replaced with var(--neutral)
  * color white replaced with var(--white)
* Dropdown Templates
  * var(--shade) replaced with var(--neutral)
  * added :focus-within styling where applicable

</details>

### Upgrading from 1.5.2 -> 1.5.3

{% hint style="success" %}
No need to update. This is purely for new project usage
{% endhint %}

## --V1.5.2 (Dec 08, 2023)

Some accessibility fixes and verbiage updates.

This update comes with:

* Header Template V1.3.1
* SCSS V1.5.2

<details>

<summary>Update Log</summary>

#### Bug fixes

* fixed accessibility where the close button and menu weren't receiving focus when opening the mobile menu with a keyboard
* specificity conflict with bricks with targeting mobile menu wrapper with some snippet managers

#### Other

* Swapped Position of the Mobile Logo and Close Icon within the header template for better accessibility
* updated verbiage to use "Menu Dropdown" and "Mega Dropdown" for better continuity between documentation

</details>

### Upgrading from 1.5/1.5.1 -> 1.5.2

{% embed url="<https://share.zight.com/4guRdXEB>" %}

## --V1.5.1 Hotfix (Dec 05, 2023)

Mobile logos were having some difficulties and needed some tweaking.

This update comes with:

* SCSS V1.5.1

<details>

<summary>Update Log</summary>

* fixed mobile logo not taking height from $mobile-logo-height when using png or jpeg

</details>

## --V1.5 (Dec 04, 2023)

V1.5 comes with two new features that allow you to add a logo on mobile and the ability to have multi-level dropdowns.

This update comes with:

* Header Template V1.3
* SCSS V1.5

<details>

<summary>Update Log</summary>

#### New Features

* [Mobile Logo Capability](https://docs.nickarce.com/mega-menu-template/getting-started/add-your-logos#add-your-mobile-logo-if-desired)
  * variable $mobile-logo to turn feature on and off
  * variable $mobile-logo-height to change the height of the logo
* [Multi-Level Dropdowns Capability](https://docs.nickarce.com/mega-menu-template/getting-started/dropdowns/menu-dropdowns#adding-multi-level-dropdowns)
  * variable $multi-level-dropdown to turn on and off

#### Other

* Refactored CSS for less specificity and overlap
* Reworked the menu dropdown to have no padding. Links inside of the menu dropdown now have padding to give a better clickable area and better hover styling

</details>


# Getting Started

## Before you get started

1. All styling for this template exists within the SCSS sheet. There are variables and mixins available to you to get your styling the way you would like it.  The only exception to this is the dropdown templates are styled inside of bricks for easy importing. \
   \
   Think:
   1. Entire header template = SCSS Sheet
   2. Mega Dropdown Content = Bricks Builder
2. The SCSS sheet is commented throughout to allow you the ability to know what every mixin and variable are affecting. Along side those comments you will find links to images/gifs to visually see what is being styled.


# Importing Template

## Step 1: Choose an SCSS Compiler

***

There are many compilers out there and my favorite without a doubt is[ wpcodebox](https://wpcodebox.com/) but there are many others to choose from. Here are some options.&#x20;

### Paid Options

* [WPCodeBox](https://wpcodebox.com/)
* [Advanced Scripts](https://www.cleanplugins.com/products/advanced-scripts/)&#x20;
* [Scripts Organizer](https://dplugins.com/downloads/scripts-organizer/)

### Free Options

* [SCSS WP Editor](https://wordpress.org/plugins/scss-wp-editor/)

## Step 2: Import SCSS

***

1. Navigate to your downloaded files and find the folder labeled "SCSS"
2. There are two versions of the SCSS to choose from:
   1. "Mega-menu-Vxx.scss" for the users that use Automatic CSS
   2. "Mega-menu-no-acss-Vxx.scss" for the users that do not use Automatic CSS
3. Open your prefered file in either a text editor or code editor like VS Code.
4. Copy all of the file contents
5. Navigate to your preferred script plugin and paste the SCSS into a new SCSS file or SCSS partial **IF you know how to use partials.**

{% hint style="info" %}
if you are using a script plugin that allows you to make live changes to the website its recommended you turn on that function while making changes to reduce the number of page refreshes necessary.
{% endhint %}

## Step 3: Create a Header Template

***

If you already have a header template skip to step 4

1. &#x20;Navigate to your wordpress dashboard and go to bricks > templates
2. Click "add new" at the top left
3. Give it a title of something like "Header"
4. On the right hand side choose the template type "header"\
   &#x20;![](https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2Ftb182p0D73iaWLYhCbRZ%2Fimage.png?alt=media\&token=29cf625e-b7c2-4bb5-a0d8-44ddd5d2f537)
5. Click publish
6. Click edit with bricks
7. Once bricks editor loads click on the gear icon and then click "template settings"\
   &#x20;![](https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2Fy5j8LQF0nrFf2SiEfWZ0%2FScreenshot%202023-12-06%20at%204.32.54%E2%80%AFPM.png?alt=media\&token=4c948083-a883-4699-8e41-ea8c838b323e)
8. Click conditions
9. Add condition
10. Select "Entire Website"

## Step 4: Importing Header JSON

***

1. Navigate to downloaded files
2. Find the folder named "Header Template"
3. Open the .json file in a text editor or code editor
4. Copy all of its contents
5. Navigate back to the bricks editor
6. Click inside of the page canvas
7. CMD+V or CTRL+V to paste the header directly into the builder
8. Save the bricks template on the top right

{% hint style="info" %}
The browser may ask you for permission to paste and some browsers may not let you paste at all. If that fails try pasting from the chrome browser
{% endhint %}

## Step 5: Add JS

1. navigate to your downloaded files
2. Open the folder named "js"
3. open the .js file in a text editor or code editor
4. copy its contents
5. add a code element below your new header element

   <figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FXn4EGAlVORrrJWLvt1do%2Fimage.png?alt=media&amp;token=5e26da74-15f5-44af-932a-97be93268c0c" alt=""><figcaption></figcaption></figure>
6. paste code into the javascript area

   <figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FuYBuz1B7BeDuzZDpfpYl%2Fimage.png?alt=media&amp;token=3a542cc9-b8ff-4101-8021-e4e6e54ba828" alt=""><figcaption></figcaption></figure>
7. Turn on "execute code" and "Render without wrapper"

   <figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FkqBxnFK0jx2tHvSdjYU6%2Fimage.png?alt=media&amp;token=10146bd0-a3fc-4d76-afb8-dcc888a6fd18" alt=""><figcaption></figcaption></figure>
8. make sure to save your template!

{% hint style="warning" %}
if you decide to use a snippet manager or load the js externally, please ensure to load the JS after the header elements as it wont work if the JS loads before
{% endhint %}


# Add Your Logos

## Add Your Desktop Logo

***

<figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2F7PGtVS1qJBG8c5CzrdJm%2FAdding%20logo.gif?alt=media&amp;token=dc30cdb5-7459-4084-b095-4ab796c9373f" alt=""><figcaption></figcaption></figure>

1. find layer "logo" and select your image
2. update any of the information found on the content tab of the image. It is setup to get you started with a preferred set of settings.

## Add Your Mobile Logo (if desired)

***

Feature added with V1.5

<figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FvsJQkkcLD9dYXZYRqdhe%2Fmobile-logo-add.gif?alt=media&amp;token=07f4c66a-9b20-4837-a7b1-961ee304d66f" alt=""><figcaption></figcaption></figure>

1. Find layer "mobile logo" and select your image
2. Navigate to your SCSS sheet and look for $mobile-logo and change it's value to "true"

```scss
$mobile-logo: false; // SELECTION (true/false)
```

{% hint style="info" %}
The mobile logo will not show on all browsers. It will show for browsers that support :has() see this link for browser compatibility <https://caniuse.com/?search=:has()>
{% endhint %}

3. In your SCSS sheet look for $mobile-logo-height and change its value to your preferred mobile logo height.

```scss
// WHAT HEIGHT DO YOU WANT THE LOGO? ******
$mobile-logo-height: 3rem;
```

### Q: What if you dont want a mobile logo?

A: You can delete the item "Mobile Logo" from the structure panel. The relevant CSS for it will not load unless you have $mobile-logo set to true.


# Navigation Items

## Adding More Nav Links Or Dropdowns

***

Because the CSS is targeting specific classes we want to make sure we duplicate what is already in the header template if we need more of an item.&#x20;

{% hint style="warning" %}
**Do not** add a nav link or dropdown from the bricks "add element" panel as this wont bring over classes. Duplicate navigation items that already exist if you need more.
{% endhint %}

## Link Styles (Desktop)

***

To style the top level link styles we can edit the @mixin link-style and the @mixin-link-hover-style to our desired settings. Included in mixin is an area for the active page styling.

```scss
// WHAT STYLES DO YOU WANT YOUR NAV LINKS? https://nickarce.com/link-styles/ ******
@mixin link-style {
    color: var(--neutral);

    //active page style
    &[aria-current="page"], &.aria-current {

    }
}

//WHAT STYLES DO YOU WANT YOUR NAV LINKS ON HOVER AND YOUR DROPDOWNS WHEN THEY ARE OPEN? *****
@mixin link-hover-style {
    color: var(--neutral-light);

     //active page style
    &[aria-current="page"], &.aria-current {

    }
}
```

## Sticky Header Link Styles (Desktop)

***

### Turning Sticky Styles On/Off

If you are using a sticky header you can turn on alternate styling for links your mobile hamburger menu icon when you are scrolling the page by turning the $sticky-header-styles to "true".

```scss
// DO YOU WANT DIFFERENT STYLES FOR LINKS AND HAMBURGER ICON FOR A STICKY HEADER? https://nickarce.com/sticky-link-style/ ******
$sticky-header-styles: false; // SELECTION (true/false)
```

### Styling Sticky Links

When you have $sticky-header-links set to "true" you can now update the styling of the links within your header on desktop. These are set by default to crazy colors to allow you to see the change.

```scss
// WHAT STYLES DO YOU WANT THE LINKS ON A STICKY HEADER? ******
@mixin sticky-link-style {
    color: red;
    
    //hover styling
    &:hover {
        color: blue;
    }

    //active page style
    &[aria-current="page"], &.aria-current {
        background-color: magenta;
    }
}
```

### Styling Sticky Mobile Hamburger Icon

You can update the below variables in the SCSS sheet to change the color of your hamburger icon while the header is in a sticky position

```scss
// SET STICKY HAMBURGER COLOR MUST HAVE $sticky-header-styles set to true for it to work ******
$hamburger-sticky-color-choice: red;
```

## Last Link Item As Button (Desktop)

***

### On/Off

By default the last item in your navigation will appear as a button. This can be turned off by changing the $last-nav-button-desktop to "false"

```scss
$last-nav-button-desktop: true; // SELECTION (true/false) *********
```

### Styling

You can alter the styling of the last nav item by changing the @mixin last-link-btn-style to your desired settings. For ACSS users this will include the .btn--primary styles by default.

```scss
// STYLE LAST NAV BUTTON HERE ********
@mixin last-link-btn-style {
    // style button here
    padding: 0.8em 1.2em;
    background-color: black;
    color: white;

    //hover styling
    &:hover {

    }
}
```


# Importing Mega Dropdown Template

These dropdown templates do require Automatic CSS as variables from that framework are used to systemize it. The ACSS variables can be switched out to your liking.

{% embed url="<https://share.zight.com/wbu52y4k>" %}


# Dropdowns

This document covers the styling that will be applied to both the menu dropdowns and the mega dropdowns.

## Dropdown Background Color

***

Edit $dropdown-background to your desired color

```scss
// WHAT BACKGROUND COLOR DO YOU WANT FOR DROPDOWN WRAPPER ******
$dropdown-background: white;
```

## Dropdown Reveal Animation Position

***

### When its not visible where do you want it to be positioned?

Change the below to your desired value. The higher the number the lower it sits in the browser.

```scss
$fade-up-closed: 40px; // STARTING POSITION (not active)
```

### When the dropdown is visible where do you want it positioned?

Change the below to your desired value. The higher the number the lower it sits in the browser.

```scss
$fade-up-open: 20px; // ENDING POSITION (active)
```

## Dropdown Chevron Animation

***

There are three animation choices for the dropdown chevron icon when the dropdown is open and active.&#x20;

1. none: no animation
2. flip: flips the icon 180deg
3. rotate: rotates the icon 180deg

```scss
$active-dropdown-icon-anim: flip; // SELECTION (none/flip/rotate)
```

## Dropdown Accessibility

***

The template does as much as it can to be accessible but it will require you to make a few changes that will be unique to your website.&#x20;

For every dropdown you will need to add in an aria-label. This tells keyboard users what opening the dropdown will do. Examples:

* If the dropdown text is "Men's Clothing" the aria-label should be something like "Toggle Men's Clothing Dropdown"
* If the Dropdown Text is "Resources" the aria-label should be something like "Toggle Resources Menu"

You can call it a menu, dropdown, list, etc. That part is up to your website preference.

<figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FfpVhOSNBk7REt95t9kX7%2FScreenshot%202023-12-07%20at%202.58.58%E2%80%AFPM.png?alt=media&amp;token=8b3e4c76-8338-4463-b593-75c09e93ba4b" alt="" width="230"><figcaption></figcaption></figure>


# Menu Dropdowns

## Menu Dropdown Container Styling

***

<figure><img src="https://nickarce.com/wp-content/uploads/menu-dropdown-style.jpg.webp" alt=""><figcaption></figcaption></figure>

For changing the styling of the menu dropdown container, update the @mixin standard-menu-dropdown-content.

```scss
@mixin standard-menu-dropdown-content {
    //style here
    min-width: fit-content;
    box-shadow: 119px 360px 152px rgba(0, 0, 0, 0.01), 67px 203px 128px rgba(0, 0, 0, 0.05), 30px 90px 95px rgba(0, 0, 0, 0.09), 7px 23px 52px rgba(0, 0, 0, 0.1), 0px 0px 0px rgba(0, 0, 0, 0.1);

    // link text length. this will help size the dropdown
    > li a, > li .brx-submenu-toggle {
        white-space: nowrap;
    }
}
```

the below line of code is to target the links inside the menu dropdown to make sure they do not wrap onto new lines. You can adjust as needed

```scss
// link text length. this will help size the dropdown
    > li a, > li .brx-submenu-toggle {
        white-space: nowrap;
    }
```

{% hint style="warning" %}

1. If you want to change the background of the dropdown container see the docs [here](https://docs.nickarce.com/mega-menu-template/getting-started/dropdowns#dropdown-background-color)
2. If you want to change the padding it will be from the internal link padding seen in the below step
   {% endhint %}

## Menu Dropdown Link Styling

***

<figure><img src="https://nickarce.com/wp-content/uploads/dropdown-links-style.jpg" alt=""><figcaption></figcaption></figure>

To style the links within the menu dropdown we will find the @mixin dropdown-link-style. The padding on the links will alter the "padding" of the container as the container does not have padding.

```scss
@mixin dropdown-link-style {
    color: black;
    padding: 2rem 1.4rem;
    
    //hover styling
    &:hover {
        color: white;
        background-color: black;
    }
}
```

## Adding Multi-Level Dropdowns (deprecated at V1.5.5)

***

Added with V1.5

Multi-level dropdowns will only work with a menu dropdown and not with a mega menu dropdown.

1. Find a menu dropdown and duplicate it within bricks&#x20;
2. Drag the duplicate dropdown inside of another menu dropdown. The structure should look like this Menu Dropdown > Content > Menu Dropdown + Nav Link(s) \
   ![](https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2F69n0GJuikufx08Wd7G1j%2Fimage.png?alt=media\&token=985ef0f2-835d-408c-82ae-fc24220fb78f)
3. <mark style="color:red;">WARNING</mark>: Do not turn on multi-level from within the dropdown itself as shown below. It will not work as expected.

<figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FOX1d7h1TR1fNGCwBCBIK%2F4d8550d6-c74c-42d1-a335-4768e93b0e78-spark-clipboard.jpg?alt=media&amp;token=0da7562d-7b75-4020-8c7d-2733566f970d" alt=""><figcaption></figcaption></figure>

4. Change the below value in the SCSS sheet to true to enable multi-level support. (deprecated as of 1.5.5)

```scss
//MULTILEVEL DROPDOWNS (will turn off some animations to function) ******
$multi-level-dropdown: false; // SELECTION (true/false)
```

## Creating Dropdowns from the Wordpress Menu System

If you want to use the default menu system that worpress gives you, you can now create individual dropdowns using the menu builder inside of wordpress. Here is a tutorial on how to do it: <br>

{% embed url="<https://share.zight.com/6quAjzj2>" %}


# Mega Dropdowns

## Mega Dropdown Container Styling (Desktop)

***

<figure><img src="https://nickarce.com/wp-content/uploads/mega-menu-dropdown-style-1920x1100.jpg.webp" alt=""><figcaption></figcaption></figure>

The styling for the mega dropdown container on desktop is handled within the @mixin mega-dropdown-content and can be adjusted to your liking. By default it is set to display its children in a row.

```scss
@mixin mega-dropdown-content {
    //style here
    padding: 2rem;
    gap: 2rem;
    box-shadow: 119px 360px 152px rgba(0, 0, 0, 0.01), 67px 203px 128px rgba(0, 0, 0, 0.05), 30px 90px 95px rgba(0, 0, 0, 0.09), 7px 23px 52px rgba(0, 0, 0, 0.1), 0px 0px 0px rgba(0, 0, 0, 0.1);
    flex-direction: row;
}
```

## Mega Dropdown Width (Desktop)

***

The width of the container is dictated from within bricks styling by choosing an element in your structure panel to inherit the width of. for example if you want it to be full width we will target the .header for the width. If we want it to be our website container width we can target .header\_\_container. See Video below:

{% embed url="<https://share.zight.com/04udpG4Z>" %}

## Mega Dropdown Container Styling (Mobile)

***

<figure><img src="https://nickarce.com/wp-content/uploads/mobile-mega-content.jpg.webp" alt="" width="563"><figcaption></figcaption></figure>

To style the container on mobile you will update the styling within @mixin dropdown-content-mobile. By default it is set to show its children in a column.

```scss
@mixin dropdown-content-mobile {
    //style here
    flex-direction: column;
}
```


# Mobile

## Mobile Breakpoint

***

To change the breakpoint at which the menu changes to a hamburger icon follow the below steps

### Step 1: change out the SCSS variable seen below that is inside of your scss sheet to your desired mobile breakpoint

In your scss sheet change our the breakpoint of the $navigation-breakpoint variable.

```scss
// WHEN WILL YOUR NAVIGATION CHANGE TO MOBILE? ******
$navigation-breakpoint: 1100px;
```

### Step 2: Change out JS Variable

inside of the JS script that is imported into bricks along with your header template, change out the MENU\_BREAKPOINT variable to the same you used in the SCSS sheet

```javascript
// Mobile Breakpoint
    const MENU_BREAKPOINT = 1100;
```

## Mobile Hamburger Icon

***

### Size

```scss
// MOBILE HAMBURGER ICON SIZE ******
$hamburger-icon-size: 2.6rem; // hamburger icon
```

### Color

```scss
// MOBILE HAMBURGER ICON COLOR ******
$hamburger-color: black;
```

### Icon

Find the icon within the bricks panel and update it to the icon or SVG that you wish to use.

![](https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FbxGKKlhp2CEs4MqGzgno%2Fimage.png?alt=media\&token=c0dd673c-5527-4a2e-94f5-d39aaedeb7d1)

## Mobile Close Icon

***

### Size

```scss
// MOBILE CLOSE ICON SIZE ******
$close-icon-size: 2.6rem; // close icon
```

### Color

```scss
// MOBILE CLOSE ICON COLOR ******
$close-icon-color: black;
```

### Icon

Find the icon within the bricks panel and update it to the icon or SVG that you wish to use.

![](https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FlFIf6rlRTUSW5yklgPuM%2FScreenshot%202023-12-07%20at%2012.39.17%E2%80%AFPM.png?alt=media\&token=10cd9462-117f-4b0a-8847-8cc7d5da7f06)

## Mobile Menu Open/Close Animation

***

### Slide Position

```scss
// WHERE DO YOU WANT THE MOBILE MENU TO SLIDE OPEN FROM? ******
$mobile-menu-slide-start-point: right; // SELECTION (left/right/top/bottom)
```

### Transition

There are two variables to update the transition of the mobile menu sliding into view. Due to some bricks styling it is recommended to not change the transition time. If you do the slide in effect timing will work but the slide out when closing the menu may feel off. This is due to the way bricks handles the closing of the menu.&#x20;

```scss
// HOW LONG SHOULD THE SLIDE TAKE? (note: for best close animation smoothness choose 0.2s)******
$mobile-menu-slide-transition-time: 0.2s;

// WHAT TRANSITION EFFECT DO YOU WANT? ******
$mobile-menu-slide-transition-effect: cubic-bezier(0.79,0.14,0.15,0.86);
```

## Mobile Background Color

***

The mobile background color is handled by the below variable

```scss
// MOBILE MENU BACKGROUND COLOR ******
$mobile-background-color: white;
```

## Mobile Padding (dynamic)

***

The padding on the mobile menu is all dynamic and items within the menu's will inherit and calculate automatically based on two values:

### Vertical Padding $mm-pad-v:&#x20;

```scss
$mm-pad-v: 2rem; // vertical spacing to be used throughout design
```

### Horizontal Padding $mm-pad-h:&#x20;

```scss
$mm-pad-h: 2rem; // horizontal spacing to be used throughout design
```

## Mobile Link Styling

***

### General styling

The links on mobile and desktop are handled seperatly. To change the styling of the links within the menu and submenus you can alter the styling by nesting your properties within the @mixin mobile-link-style.

```scss
// MOBILE MENU NAV LINK STYLE ******
@mixin mobile-link-style {
    color: black;

    //hover style
    &:hover {
        background-color: black;
        color: white;
    }

    //active page style
    &[aria-current="page"], &.aria-current {
        
    }
}
```

### Link Dividers

<figure><img src="https://nickarce.com/wp-content/uploads/mobile-menu-border.jpg.webp" alt=""><figcaption></figcaption></figure>

By default all of the navigation items will have dividing borders to help separate them from each other. You can update the variable $mm-border to your desired value or simple put in "none" if you do not want a border.

```scss
// SET MOBILE LINK BOTTOM BORDER CSS. USE 'none' IF YOU DONT WANT BORDERS https://nickarce.com/mobile-menu-border/ ******
$mm-border: 1px solid var(--shade-light); // border value or none

```

## Last Nav Item As a Button

***

### On/Off

You can turn on and off the last item in your navigation to look like a button on mobile. This will inherit the styling from the @mixin last-link-btn-style

```scss
// DO YOU WANT THE LAST NAV ITEM TO LOOK LIKE A BUTTON ON MOBILE? https://nickarce.com/mobile-button/ **********
$last-nav-button-mobile: true; // SELECTION (true/false) *********
```

### Push Last Link To Bottom Of Screen

By Default, if you have your last item set to a button it will push it to the bottom of the screen for easier usability for mobile users. If you wish to turn this off you can change the value of $last-nav-button-mobile-bottom to "false"

```scss
// IF USING BUTTON ON MOBILE: DO YOU WANT IT AT THE BOTTOM OF THE SCREEN https://nickarce.com/mobile-button-bottom/ **********
$last-nav-button-mobile-bottom: true; // SELECTION (true/false)
```


# How To's


# Change Mega Dropdown Container Width

### How to set the container automatically

{% embed url="<https://share.zight.com/04udpG4Z>" %}

### How to choose a manual size and center the dropdown to the dropdown selector

{% embed url="<https://share.zight.com/DOu1gmoQ>" %}


# How to Change Back Button Text

Easy Peasy. 💪

### 1.5.5 and Above

You will find the button in the mobile navigation to change its text content

<figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FrfwUUa7itsTwy4EnDqYI%2FScreenshot%202025-05-22%20at%2011.49.14%E2%80%AFPM.png?alt=media&amp;token=c71a4c48-df1c-4bc6-b9ff-3f03dc725f64" alt=""><figcaption></figcaption></figure>

### Pre 1.5.5

Do a search in the SCSS sheet for the below and change to your desired value

```scss
content: "Back";
```


# Add Scrollbar On Mobile Menu

Update the variable $hide-scrollbar-sub-nav-scrollbar to "false"

```scss
$hide-scrollbar-sub-nav-scrollbar: true; // SELECTION (true/false)
```


# Adding Custom CSS

Within the SCSS sheet there is an area designed to give you the boilerplate for your custom CSS. Here there are preset areas to help you target items in your DOM depending on the state of the menu.

1. Targeting the menu globally
2. Targeting the menu when its specifically not mobile
3. Targeting the menu when its at the breakpoint
4. Targeting the menu when its open on mobile

```scss
/* ----------------------YOUR CUSTOM CSS--------------------- */




// Global Mega Menu Styling
.mm {

    //nest items here

}

// Specifically Not Mobile Menu
.mm:not(.brx-open) {

    //nest items here

}

// Mega Menu Styling Only At Breakpoint
@media (max-width: $navigation-breakpoint) {

    .mm {

        //nest items here

    }

}

// Mega Menu Styling On Mobile Menu
.mm.brx-open {

    //nest items here

}




/* -----------------------END OF YOUR CUSTOM CSS-------------------- */
```


# Change Dropdown Activation Toggle

This quick guide will show you how to change a dropdown to be activiated on click, hover, or click or hover.

When you have a dropdown selected, you can change the "toggle on" setting under the "content panel" to change how the dropdown is activated. It gives you a few options to choose from.&#x20;

{% hint style="warning" %}
This will require you to make the change on every dropdown as this does not change globally on the class level but only updates on the element level.
{% endhint %}

<figure><img src="https://368645589-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZAyzlif8jp8KbrptKaA5%2Fuploads%2FbrZxYzm5x1foZmS9l4UK%2FScreenshot%202024-01-15%20at%2012.26.59%E2%80%AFPM.png?alt=media&amp;token=c42b2940-326e-4572-bed9-9aadaaf92d3e" alt=""><figcaption></figcaption></figure>


# How to Query Loop Over Menu Dropdown

You will learn how to change the structure of the menu dropdown to add your own LI elements. Once you have changed the structure you can now add a query to the newly created LI element.

{% embed url="<https://share.zight.com/qGunr0OB>" %}


# Remove Android Chrome Blue Link Highlight

On android devices specifically while using chrome, there is a blue tap highlight that is added to links inside the mobile menu. This can cause the visuallized tap target to appear like its not on the actual link you are tapping.&#x20;

In order to remove you can add this small snippet of CSS

```css
.mm li {
    -webkit-tap-highlight-color: transparent;
}
```


# Setup

A gesture-driven drawer component for Etch. Supports swipe-to-close, nested stacking, non-modal notifications, and four directions. Built with vanilla JS — no framework required.

***

## What it does

* Slides in from any edge: top, bottom, left, or right
* Swipe-to-close with velocity detection and over-drag dampening
* Nested stacking — open a drawer from inside another drawer
* Passive (non-modal) mode for notification-style UI
* Auto-open on page load with configurable delay and frequency control
* Fully accessible — keyboard nav, focus trap, screen reader announcements

## How to Import <a href="#id-1.-import-template" id="id-1.-import-template"></a>

### 1. Import Drawer

First, import the drawer component to get the component loaded in your etch site.

1. Open your `drawer.json` file in a text editor or IDE and copy its entire contents.
2. In Etch navigate to the page you want to work on
3. Paste the json you copied&#x20;
4. Move the drawer below your drawer trigger in the DOM so the drawer opening JS works correctly.

### 2. Import Drawer Patterns

The drawer comes with a few premade patterns to get you started

1. navigate to the "Drawer Patterns" folder in your download file
2. Open the json file of the pattern you would like to use (examples [here](https://etch.nickarce.com/drawer-component/).)
3. Copy the contents of that json file
4. Paste the contents into Etch
5. Move the drawer below your drawer trigger in the DOM so the drawer opening JS works correctly.

## Customizing

Drawers initialize automatically on page load. For most use cases, set props [Component Props](/etch-drawer/customization/component-props) in the Etch builder and you're done.

To change its CSS styling see [Custom CSS](/etch-drawer/customization/custom-css) for the options available.&#x20;

To control a drawer from custom code, grab its instance from the DOM:

```javascript
const drawer = document.querySelector('#my-drawer')._drawer;

drawer.open();
drawer.close();
```

See the [Javascript API](/etch-drawer/customization/javascript-api) for the full reference.


# Changelog

## v1.1.3 (May 28th, 2026)

### Changes

1. Fixed an issue where the drawer would appear on the front end on load then vanish immediately when the property "Show in builder" was set to true.&#x20;
2. Added accessibility keyboard navigation to drawers with snap points so they can be navigated. The drawer handle operates like a navigation pad for the up and down arrows.
3. Added the ability for buttons to be used for navigating to the next or previous snap points. see [here](https://app.gitbook.com/o/Stk0t1tQGHZDzvdmssfa/s/v6VZuQYokv6QNEpCQiEs/~/edit/~/changes/22/customization/behavior#snap-navigation-buttons) for more information.

### How to update:&#x20;

1. navigate to Etch settings in the Etch Builder
2. turn on "Overwrite on paste" option
3. copy the new JSON for the drawer
4. paste the JSON anywhere in the builder. This will add a drawer and update existing underlying drawer config.
5. press save
6. delete the pasted in drawer.&#x20;
7. turn off "overwrite on paste" option in etch settings
8. press save

***

## v1.1.2 (May 26th, 2026)

### Changes

1. Fixed an issue where the body scroll lock wasn't taking effect on Chrome mobile across iOS and Android devices.
2. Fix an issue where there was a black bar at the very bottom of the screen that would show when using snap points.

### How to update:&#x20;

1. navigate to Etch settings in the Etch Builder
2. turn on "Overwrite on paste" option
3. copy the new JSON for the drawer
4. paste the JSON anywhere in the builder. This will add a drawer and update existing underlying drawer config.
5. press save
6. delete the pasted in drawer.&#x20;
7. turn off "overwrite on paste" option in etch settings
8. press save

***

## v1.1.1 Hotfix (May 22nd, 2026)

### Changes

1. user spotted that snap points and scrolling content collided. Changed the logic so that a scrollable drawer only becomes scrollable once it reaches its max height or it passes the last snap point. Makes for much smoother handling

### How to update:&#x20;

1. navigate to Etch settings in the Etch Builder
2. turn on "Overwrite on paste" option
3. copy the new JSON for the drawer
4. paste the JSON anywhere in the builder. This will add a drawer and update existing underlying drawer config.
5. press save
6. delete the pasted in drawer.&#x20;
7. turn off "overwrite on paste" option in etch settings
8. press save

***

## v1.1 (May 22nd, 2026)

### Changes:&#x20;

1. NEW Snap Points Feature: The drawer can now lock at preset positions along its path, letting users reveal content in stages. Drag further to advance to the next snap point. see docs [here](https://app.gitbook.com/o/Stk0t1tQGHZDzvdmssfa/s/v6VZuQYokv6QNEpCQiEs/~/edit/~/changes/16/customization/behavior#snap-points).
2. Updated the javascript API to include event handlers instead of defaulting to callbacks

### How to update:&#x20;

1. navigate to Etch settings in the Etch Builder
2. turn on "Overwrite on paste" option
3. copy the new JSON for the drawer
4. paste the JSON anywhere in the builder. This will add a drawer and update existing underlying drawer config.
5. press save
6. delete the pasted in drawer.&#x20;
7. turn off "overwrite on paste" option in etch settings
8. press save

***

## v1.0.2 (May 18th, 2026)

### Changes:&#x20;

1. updated the minimum velocity threshold to the swipe acceleration curve, preventing the drawer from appearing to flash off-screen during fast swipes.

### How to update:&#x20;

#### Option 1 (overwrite component config):&#x20;

1. navigate to Etch settings in the Etch Builder
2. turn on "Overwrite on paste" option
3. copy the new JSON for the drawer
4. paste the JSON anywhere in the builder. This will add a drawer and update existing underlying drawer config.
5. press save
6. delete the pasted in drawer.&#x20;
7. turn off "overwrite on paste" option in etch settings
8. press save

#### Option 2 (find and replace):&#x20;

1. right click on the component and click "edit component"&#x20;
2. navigate to the 'wrapper" layer
3. Find the below line in the JS

```
static MIN_CLOSE_DURATION = 0.1;
```

and replace with&#x20;

```
static MIN_CLOSE_DURATION = 0.2;
```

***

## v1.0.1 (May 18th, 2026)

### Changes:&#x20;

1. fixed a hidden drawer announcement appearing visually in the dom causing page bounce

### How to update (overwrite the component config):&#x20;

1. navigate to Etch settings in the Etch Builder
2. turn on "Overwrite on paste" option
3. copy the new JSON for the drawer
4. paste the JSON anywhere in the builder. This will add a drawer and update existing underlying drawer config.
5. press save
6. delete the pasted in drawer.&#x20;
7. turn off "overwrite on paste" option in etch settings
8. Hit save

***

## Release v1 (May 18th, 2026)


# Behavior

## Positioning and direction

The drawer can open from any edge. Set the **Open Direction** prop to control the entry point. Each direction uses its own CSS transforms and border-radius rules.

| Direction            | Behavior                                                    |
| -------------------- | ----------------------------------------------------------- |
| **Bottom** (default) | Slides up, rounded top corners, max-height capped           |
| **Top**              | Slides down, rounded bottom corners, accounts for admin bar |
| **Left**             | Slides in from the left, full-height with rounded corners   |
| **Right**            | Slides in from the right, full-height with rounded corners  |

## Drag-to-close

On open drawers, a pointer drag in the closing direction moves the drawer and dims the overlay proportionally. Releasing past the close threshold (25% of the drawer dimension) closes it — otherwise it snaps back.

* **Velocity detection** — fast swipes close immediately regardless of distance
* **Over-drag dampening** — dragging past the open position applies logarithmic resistance
* **Scroll awareness** — dragging is suppressed for 100ms after scrolling inside the drawer body, preventing scroll-to-close conflicts
* **Handle-only mode** — restrict drag to the `.c-drawer__handle` element with the `handleOnly` option
* **No-drag mode** — disable swipe-to-close entirely with the **Disable Drag to Close** prop
* `cursor: grabbing` is applied globally during drag via `.c-drawer--dragging`

## Snap points

Vertical drawers (top or bottom) can rest at multiple heights instead of just open or closed. Add `data-snap-point` to any element inside the drawer body and the drawer will snap to that element's edge as the user drags.

```html
<div data-snap-point="bottom">Quick preview</div>
<div data-snap-point="bottom">Details</div>
<div data-snap-point="top">Full content</div>
```

Use `"top"` to snap to the element's top edge, `"bottom"` to snap to its bottom edge. The drawer opens to the smallest snap point first, and dragging below it closes the drawer. Snap points are ignored for left/right drawers.

### Snap navigation buttons

Add `data-snap-to` to any button inside the drawer to step between snap points on click — useful for discoverability and for visitors who won't drag.

```html
<button data-snap-to="next">Expand</button>
<button data-snap-to="previous">Collapse</button>
```

* `"next"` expands to the next-larger snap point. `"previous"` collapses to the next-smaller one.
* At the extremes the button does nothing — `"next"` no-ops at full height, `"previous"` no-ops at the smallest snap point. It never closes the drawer.
* Use a real `<button>` element. The component does not synthesize keyboard or focus behavior, so a `<div>` would not be operable by keyboard or screen-reader users.
* The button's availability is reflected with `aria-disabled` at the extremes. Style the inactive state with `[data-snap-to][aria-disabled="true"]` if so you desire.
* On non-snap drawers (no `data-snap-point` elements, or left/right direction) the button is ignored — it's inert markup.

## Nested drawers

You can open a drawer from inside another drawer. When this happens, the parent scales back slightly while the child opens on top, then restores when the child closes.

{% stepper %}
{% step %}

### Add a Drawer component to the page

This is your parent drawer.
{% endstep %}

{% step %}

### Inside the **body** of that drawer, insert a second Drawer

This is your child drawer.
{% endstep %}

{% step %}

### Give the child drawer its own **Unique ID**

{% endstep %}

{% step %}

### Add a button inside the parent drawer's body

Set the child's **Open Trigger Selector** to match that button.
{% endstep %}

{% step %}

### The component handles everything else

The child drawer is moved to the correct position, the parent scales back on open, and focus stays within the active drawer.
{% endstep %}
{% endstepper %}

A few things to keep in mind:

* Passive drawers cannot be nested — they don't participate in the stack.
* The child drawer gets its own close gesture and close button. Closing it returns the parent to full size.
* You can nest multiple levels deep, but one or two is the practical limit for usability.

## Passive mode

Passive mode renders the drawer as a non-modal notification bar. It has no overlay, cannot trap focus, and does not lock body scroll.

* Overlay is hidden (CSS managed), body scroll is not locked
* Click outside does not close — only drag or a close button dismisses it
* Trigger elements do not receive `aria-expanded` updates
* `pointer-events: none` on the wrapper lets users interact with content beneath
* Ideal for bottom-sheet notifications that don't demand immediate attention

Enable with the **Passive Mode** prop or the `passive` constructor option.

## Auto-open and persistence

Drawers can open automatically on page load with configurable delay and storage-backed frequency control.

| Frequency option  | Behavior                                                   |
| ----------------- | ---------------------------------------------------------- |
| `every-visit`     | Always show (no persistence)                               |
| `until-dismissed` | Show until the user closes it, then never again            |
| `once`            | Show one time only (recorded on open)                      |
| `after-delay`     | Re-show after a configurable duration since last dismissal |

Duration strings accept `30s`, `2h`, `7d`, or bare numbers (treated as hours). All persistence uses `localStorage` with keys namespaced under `etch-drawer-`.

Configure with the **Auto Open**, **Auto Open Delay**, **Auto Open Reshow**, and **Auto Open Reshow Delay** props.

## Etch builder integration

Inside the Etch visual builder iframe, the Drawer disables all gesture handling and event binding. The builder controls open/close via the **Show In Builder** prop, which toggles visibility on the wrapper element through a `MutationObserver`.

* No drag, no pointer events, no keyboard bindings
* CSS rules handle visibility from the prop value
* The open/close class toggle still synchronizes `data-state` for preview rendering


# Component Props

Configure the Drawer through the builder's prop panel.

## All props

| Prop                   | Description                                               | Accepted value                                          | Required |
| ---------------------- | --------------------------------------------------------- | ------------------------------------------------------- | -------- |
| Unique ID For Drawer   | Sets a unique identifier on the drawer wrapper            | string                                                  | Yes      |
| Drawer Class           | Appends additional classes to the drawer wrapper          | CSS class string                                        | No       |
| Open Trigger Selector  | CSS selector for elements that open the drawer            | CSS selector string                                     | No       |
| Close Trigger Selector | CSS selector for elements that close the drawer           | CSS selector string                                     | No       |
| Open Direction         | Slide direction the drawer enters from                    | `top`, `bottom`, `left`, `right`                        | No       |
| Passive Mode           | Enable non-modal mode — no overlay, no focus trap         | boolean                                                 | No       |
| Disable Drag to Close  | Prevent swipe-to-close gesture                            | boolean                                                 | No       |
| Handle Only            | Restrict drag-to-close to the `.c-drawer__handle` element | boolean                                                 | No       |
| Auto Open              | Open automatically on page load                           | boolean                                                 | No       |
| Auto Open Delay        | Seconds to wait before auto-opening                       | number (seconds)                                        | No       |
| Auto Open Reshow       | Frequency control for auto-open                           | `every-visit`, `until-dismissed`, `once`, `after-delay` | No       |
| Auto Open Reshow Delay | Duration before re-showing after dismissal                | duration string (`30s`, `2h`, `7d`)                     | No       |
| Close Anchor Link      | Close drawer when a same-page anchor link is clicked      | boolean                                                 | No       |
| Show In Builder        | Keep the drawer visible inside the Etch builder           | boolean                                                 | No       |


# Custom CSS

Customize the visual appearance by setting these custom properties on `.c-drawer` or `:root`.

## Custom properties

| Variable                  | Description                          | Default                       |
| ------------------------- | ------------------------------------ | ----------------------------- |
| `--drawer-bg`             | Drawer background color              | `#fff`                        |
| `--drawer-overlay-color`  | Overlay background color             | `rgba(0,0,0,0.4)`             |
| `--drawer-duration`       | Transition duration                  | `0.5s`                        |
| `--drawer-easing`         | Transition easing curve              | `cubic-bezier(0.32,0.72,0,1)` |
| `--drawer-radius`         | Drawer border radius                 | `var(--radius)`               |
| `--drawer-inset`          | Margin from viewport edges           | `8px`                         |
| `--drawer-max-height`     | Maximum height for vertical drawers  | `92dvh`                       |
| `--drawer-max-width`      | Maximum width for horizontal drawers | `24rem`                       |
| `--drawer-body-padding`   | Padding inside `.c-drawer__body`     | `var(--space-m)`              |
| `--drawer-handle-width`   | Drag handle width                    | `48px`                        |
| `--drawer-handle-height`  | Drag handle height                   | `4px`                         |
| `--drawer-handle-color`   | Drag handle color                    | `#b4b4b8`                     |
| `--drawer-handle-padding` | Drag handle wrapper padding          | `1.25rem 0 1rem`              |


# Javascript API

Drawers are initialized automatically on page load. You only need to call `new Drawer(...)` if you are adding a drawer to the page dynamically after load.

For most use cases, the builder's prop panel is all you need. The JavaScript API is for situations where you need to control the Drawer from custom code — for example, opening it after a form submission, closing it from another component, or reacting when it opens or closes.

## Getting an instance

Each initialized drawer stores its instance on the wrapper element:

```javascript
const wrapper = document.querySelector('#my-drawer');
const drawer = wrapper._drawer;

if (drawer) drawer.open();
```

## Instance methods

| Method                   | What it does                                                                                           |
| ------------------------ | ------------------------------------------------------------------------------------------------------ |
| `drawer.open()`          | Opens the drawer                                                                                       |
| `drawer.close()`         | Closes the drawer with its normal animation                                                            |
| `drawer.close(duration)` | Closes the drawer in a specific number of seconds (e.g. `drawer.close(0.2)`)                           |
| `drawer.destroy()`       | Removes all event listeners and cleans up. Call this before removing the drawer element from the page. |

## Instance properties

| Property            | Type    | Description                                        |
| ------------------- | ------- | -------------------------------------------------- |
| `drawer.isOpen`     | boolean | `true` if the drawer is currently open             |
| `drawer.isDragging` | boolean | `true` if the user is actively dragging the drawer |

## Constructor options

Pass these when creating a drawer instance manually with `new Drawer(...)`.

Several options mirror builder props (`direction`, `passive`, `noDrag`, `handleOnly`). When both are set, the constructor option wins — the builder prop acts as the default.

| Option              | Description                                                                                                    | Type                                     | Default    | Builder prop          |
| ------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ---------- | --------------------- |
| `dismissible`       | Allow the user to close the drawer by clicking the overlay or pressing ESC                                     | boolean                                  | `true`     | —                     |
| `direction`         | Edge the drawer slides in from                                                                                 | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Open Direction        |
| `passive`           | Non-modal mode — no overlay, no focus trap, no body scroll lock                                                | boolean                                  | `false`    | Passive Mode          |
| `noDrag`            | Disable swipe-to-close entirely                                                                                | boolean                                  | `false`    | Disable Drag to Close |
| `handleOnly`        | Restrict drag-to-close to the handle bar only                                                                  | boolean                                  | `false`    | Handle Only           |
| `nested`            | Mark this as a nested drawer. Detected automatically in most cases — you rarely need to set this manually.     | boolean                                  | auto       | —                     |
| `scrollLockTimeout` | Milliseconds to wait after a scroll before allowing drag-to-close. Prevents accidental closes after scrolling. | number                                   | `100`      | —                     |
| `closeThreshold`    | Fraction of the drawer's size the user must drag before it closes on release. `0.25` = 25%.                    | number                                   | `0.25`     | —                     |

## Events

Drawers dispatch `CustomEvent`s on the wrapper element. They bubble, so you can listen on the wrapper or any ancestor.

| Event            | When it fires                          | `event.detail`                                                               |
| ---------------- | -------------------------------------- | ---------------------------------------------------------------------------- |
| `drawer:open`    | After the drawer opens                 | none                                                                         |
| `drawer:close`   | After the drawer closes                | none                                                                         |
| `drawer:drag`    | Every frame while the user is dragging | `{ event, percentage }` — `percentage` is `0` to `1`                         |
| `drawer:release` | When the user releases after a drag    | `{ event, snappedBack }` — `snappedBack` is `true` if the drawer stayed open |

`event.detail.event` is the underlying pointer event.

### Callbacks (deprecated)

The previous callback options (`onOpen`, `onClose`, `onDrag`, `onRelease`) still work for backwards compatibility, but log a deprecation warning. New code should use the events above.

| Callback    | Equivalent event | Arguments              |
| ----------- | ---------------- | ---------------------- |
| `onOpen`    | `drawer:open`    | none                   |
| `onClose`   | `drawer:close`   | none                   |
| `onDrag`    | `drawer:drag`    | `(event, percentage)`  |
| `onRelease` | `drawer:release` | `(event, snappedBack)` |

## Static methods

| Method          | Description                                                                                                                                                                                                               |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Drawer.init()` | Scans the page for all `.c-drawer__wrapper` elements and initializes any that haven't been set up yet. Runs automatically on page load — call it manually only if you've added new drawer elements to the DOM after load. |

## Examples

**Open a drawer from a button click:**

```javascript
const drawer = document.querySelector('#my-drawer')._drawer;

document.querySelector('#open-btn').addEventListener('click', () => {
  drawer.open();
});
```

**Open a drawer after a form submits:**

```javascript
const form = document.querySelector('#signup-form');
const drawer = document.querySelector('#confirmation-drawer')._drawer;

form.addEventListener('submit', (e) => {
  e.preventDefault();
  // ... handle the submission ...
  drawer.open();
});
```

**Run code when the drawer opens or closes:**

```javascript
const wrapper = document.querySelector('#my-drawer');

wrapper.addEventListener('drawer:open', () => {
  console.log('Drawer opened');
});

wrapper.addEventListener('drawer:close', () => {
  console.log('Drawer closed');
});
```

**Show a progress indicator while dragging:**

```javascript
const wrapper = document.querySelector('#my-drawer');
const progressBar = document.querySelector('#drag-progress');

wrapper.addEventListener('drawer:drag', (e) => {
  progressBar.style.width = `${e.detail.percentage * 100}%`;
});

wrapper.addEventListener('drawer:release', () => {
  progressBar.style.width = '0%';
});
```

**Close a drawer quickly after a success action:**

```javascript
const drawer = document.querySelector('#my-drawer')._drawer;

// Close in 0.2 seconds instead of the default 0.5
drawer.close(0.2);
```

**Toggle open/closed:**

```javascript
const drawer = document.querySelector('#my-drawer')._drawer;

if (drawer.isOpen) {
  drawer.close();
} else {
  drawer.open();
}
```

**Clean up before removing a drawer from the page:**

```javascript
const drawer = document.querySelector('#my-drawer')._drawer;

drawer.destroy();
document.querySelector('#my-drawer').remove();
```


# Accessibility

## What's built in

* Drawers receive `role="dialog"` (modal) or `role="region"` (passive) with `aria-modal="true"`
* The first heading inside the drawer is linked via `aria-labelledby`
* A live region announces "Dialog opened" / "Dialog closed" (or "Notification" for passive drawers) to screen readers
* Focus moves to the first focusable element inside the drawer on open — passive drawers skip focus management
* Tab and Shift+Tab are trapped within the drawer to prevent focus from escaping behind the overlay
* Focus returns to the trigger element when the drawer is dismissed
* All drawer content is hidden from screen readers when `data-state` is `closed`
* Overlays are marked `aria-hidden="true"` — they are decorative
* Drag handles receive a descriptive `aria-label`
* Snap navigation buttons (`data-snap-to`) get `aria-disabled="true"` when there's no further snap point in that direction, so the dead end is announced rather than silently doing nothing — see [Core Behavior — Snap navigation buttons](broken://pages/51fd42123935a066b3efafd4ce76da2d7a72e6a0#snap-navigation-buttons)

## Passive mode differences

Passive drawers use `role="region"` instead of `role="dialog"` and do not trap focus or lock body scroll, since they are non-modal notifications. See [Core Behavior — Passive mode](broken://pages/51fd42123935a066b3efafd4ce76da2d7a72e6a0#passive-mode) for more detail.


