> For the complete documentation index, see [llms.txt](https://docs.nickarce.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nickarce.com/etch-drawer/customization/behavior.md).

# 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
