> For the complete documentation index, see [llms.txt](https://analog.groupthought.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://analog.groupthought.com/reference/alpine/scroll-lock.md).

# x-scroll-lock

A directive that locks page scrolling while an expression is true, with counted locks and an iOS-specific path.

A directive that locks page scrolling while an expression is true.

| Directive / attribute        | What it does                                                                                          |
| ---------------------------- | ----------------------------------------------------------------------------------------------------- |
| `x-scroll-lock="expression"` | Locks page scroll while the expression is true, unlocks when it turns false or the element is removed |
| `data-scroll-locked`         | Set to `"true"` on `<html>` while any lock is active. Use it as a styling hook                        |

Locks are counted across elements. Closing a dialog over an already locked drawer doesn't unlock the page. The page unlocks when every `x-scroll-lock` expression is false again.

Standard browsers get `overflow: hidden` on `<html>` plus scrollbar-width compensation (`scrollbar-gutter: stable`, or `padding-right` where unsupported) so the layout does not shift. iOS Safari ignores `overflow: hidden`, so there the plugin blocks `touchmove` on the document instead, while it still allows scrollable descendants, pinch zoom, range sliders, and text-selection handles.

## Examples

### Locking behind a dialog

Use the open state of the overlay as the expression:

```html
<dialog x-dialog:dialog x-scroll-lock="$dialog.isOpen">…</dialog>
```

Every modal surface in Analog does this, including `dialog.liquid`, the video lightbox, the filter sidebar on mobile, the predictive search flyout (`x-scroll-lock="$popover.isOpen"`).

### Locking from a store

Use a store value when the overlay's state lives outside the element:

```html
<div x-data x-scroll-lock="$store.drawer.isOpen">…</div>
```

`theme.liquid` locks this way for the cart and menu drawers, since the drawer store is global and the lock should not depend on which drawer section rendered.

## Options

There are no modifiers or options. The expression is the whole API. The directive skips evaluation while Alpine clones a component, so cloned trees do not add duplicate locks.
