> 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/sticky-columns.md).

# x-sticky-columns

Directives that coordinate sticky behaviour between sibling columns: the root measures, the columns report state to CSS.

Directives that coordinate sticky behaviour between sibling columns: the root measures, the columns report state to CSS.

| Directive / output         | What it does                                                                                      |
| -------------------------- | ------------------------------------------------------------------------------------------------- |
| `x-sticky-columns`         | The root. Measures registered columns and decides which ones stick                                |
| `x-sticky-columns:column`  | Registers a column with the nearest root                                                          |
| `data-enable-sticky`       | On each column: `"true"` when at least one column in the group overflows the viewport             |
| `data-enable-smart-offset` | `"true"` on overflowing columns shorter than the tallest one. These columns get the scroll offset |
| `data-fits-in-viewport`    | `"true"` when the column is shorter than the viewport                                             |
| `--sticky-columns-offset`  | The scroll-driven offset (px) a smart column adds to its sticky `top`                             |

The plugin writes state, not `position: sticky`. The theme's `sticky-columns.css` applies the positioning: a column with the `sticky-column` class pins at `md` and up when `data-enable-sticky` is true, at `top: calc(header offset + padding + --sticky-columns-offset)`. A registered column without the class only counts in the group measurement.

The smart offset is for two overflowing columns of different heights: the shorter one scrolls with the page until its bottom edge reaches the viewport bottom, pins there, and walks back up when the page scrolls up. Measurements run inside `ResizeObserver`, so layout changes re-evaluate the whole group.

## Examples

### A sticky product layout

Use `x-sticky-columns` on the two-column wrapper and `x-sticky-columns:column` with the `sticky-column` class on each column:

```html
<div x-sticky-columns class="md:grid md:grid-cols-2">
  <div x-sticky-columns:column class="sticky-column sticky-column--flush-top">
    <!-- media gallery -->
  </div>
  <div x-sticky-columns:column class="sticky-column [--sticky-padding:--desktop-pt]">
    <!-- product details -->
  </div>
</div>
```

This is the product page (`product-section.liquid`). The shorter media or details column pins while the taller one scrolls. Two overflowing columns scroll independently through the smart offset.

### A sticky Pair column

Use the "Short column sticky" setting on a Pair section in Column height mode:

```html
<div x-sticky-columns class="flex flex-row">
  <pair-column x-sticky-columns:column class="sticky-column sticky-column--flush-top">…</pair-column>
  <pair-column x-sticky-columns:column>…</pair-column>
</div>
```

The section is the root and each column registers; the setting adds the `sticky-column` classes. "Short column placement" set to the bottom swaps in `sticky-column--end`, which pins to the viewport bottom instead. See [Pair sections](/building-pages/pair.md) for the merchant-facing behaviour.

### Measuring without pinning

Use `x-sticky-columns:column` without the `sticky-column` class when a column should count in the measurement but never pin:

```html
<div x-sticky-columns class="flex">
  <aside x-sticky-columns:column class="sticky-column">…</aside>
  <div x-sticky-columns:column>…</div>
</div>
```

The collection page works this way: the filter sidebar pins, and the product grid registers only so the group knows a column overflows.

## Customizing

There are no modifiers, options, or magics. Configure behavior through the CSS contract in `sticky-columns.css`:

| Class / variable                               | What it does                                                                                                                                                                                                    |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sticky-column`                                | Opts the column into `position: sticky` at `md` and up                                                                                                                                                          |
| `sticky-column--flush-top`                     | Offsets against `--header-sticky-offset` instead of `--header-bottom`, so the column sits flush with the viewport top when the header is away. The plugin sets both variables on the column from the page store |
| `sticky-column--end`                           | Pins to the viewport bottom instead of the top                                                                                                                                                                  |
| `--sticky-padding` / `--sticky-column-padding` | Extra space between the sticky edge and the column                                                                                                                                                              |
