# drag

> `drag:` binds a number to a drag gesture: grabbing the element moves the value, arrow keys move the same value, and Escape puts it back where it started. You write no drag code — no begin handler, no cancel handler, no keyboard handler — which is what makes those three agree.

<!-- id: ui-drag · area: ui · stability: stable · html: https://osysharp.com/reference/ui/drag/ -->

## Summary        {#summary}
A resize handle, a splitter, a slider — anything where a gesture moves a **number** — is one prop:

```osy syntax
component Splitter() {
  int width = 240;
  render {
    Row {
      Box(w: width) { Text("sidebar"); }
      Box(drag: width, dragAxis: DragAxis.Horizontal, dragStep: 8, dragMin: 160, dragMax: 480) { Text("⋮"); }
      Box { Text("content"); }
    }
  }
}
```

That is the whole feature. Dragging the handle writes `width`; <kbd>←</kbd>/<kbd>→</kbd> write the same `width` in
steps of 8; <kbd>Home</kbd>/<kbd>End</kbd> go to 160 and 480; <kbd>Esc</kbd> mid-drag puts it back where the drag
started. None of that is code you write.

## Signature      {#signature}
```osy syntax
drag:     <a numeric state member>     // the value the gesture moves — two-way
dragAxis: DragAxis.Horizontal | DragAxis.Vertical        // REQUIRED
dragStep: <number>                     // how far one arrow key moves it (default 1)
dragMin:  <number>                     // clamp, and where Home goes
dragMax:  <number>                     // clamp, and where End goes
```

## Description    {#description}

### It binds a VALUE, and that is why the hard parts are already right   {#binding}
The obvious shape for a drag is an event — "call me with the delta". This is a **binding** instead, and the
difference is not stylistic. Two things follow from the platform owning the value rather than the app:

**Escape restores.** The pre-drag value is snapshotted when you grab the handle, and Escape writes it back. With an
event you would hold that start value yourself, and whether it is still right depends on when your begin handler ran,
whether a re-render reset it, and what happens when two drags interleave. Cancel is the part of a drag that is always
skipped and always missed; here there is nothing to skip.

**The keyboard works.** Arrow keys write the *same bound value* through the *same code path*. There is no second
handler to write and therefore no second handler to forget, get subtly wrong, or leave behind when the drag logic
changes.

You also get, without asking: the drag surviving the pointer leaving the element (so a fast drag does not drop), a
movement threshold (so a click on the handle does not nudge the value), and the right touch behaviour on a phone.

### `dragAxis` is required, and that is deliberate   {#axis}
It is the one required option in the UI vocabulary. The axis decides three separate things — which direction of
movement is read, which arrow keys respond, and **the element's touch behaviour**.

That last one is why there is no default. On a touchscreen a drag and a scroll begin identically, so the element has
to declare which it is *before* the finger moves. A horizontal drag leaves vertical panning to the page, so the page
still scrolls over your control. Get it wrong and everything looks perfect on a desktop and the page will not scroll
on a phone — so the platform asks rather than guesses.

### The handle is focusable   {#keyboard}
Declaring `drag:` makes the element focusable, because a keyboard path you cannot reach is not a keyboard path. Click
it or <kbd>Tab</kbd> to it, then use the arrows.

### What it does not do   {#not-drag-and-drop}
`drag:` moves a **number**. Dragging an *object* — a card onto a lane, a row to a new position — is drag-and-drop,
which needs to know what you picked up and what you dropped it on; that is a different vocabulary and it is not this
prop with a different type.

There is also no `Both` axis. A `drag:` binds one number, and a two-axis gesture has no single value to write into
it.

## Examples       {#examples}

```osy title="a draggable splitter, keyboard included" test app=ui-drag
component Splitter() {
  int width = 240;
  render {
    Row {
      Box(drag: width, dragAxis: DragAxis.Horizontal, dragStep: 8, dragMin: 160, dragMax: 480) {
        Text("drag me");
      }
      Text(width);
    }
  }
}
```

```osy title="a vertical drawer height" test app=ui-drag-vertical
component Drawer() {
  int height = 120;
  render {
    Box(drag: height, dragAxis: DragAxis.Vertical, dragStep: 16, dragMin: 40) {
      Text(height);
    }
  }
}
```

## See also       {#see-also}
- [keys](https://osysharp.com/reference/ui/keys/) — declaring a key surface, and reading whether a key is held right now
- [onEscape](https://osysharp.com/reference/ui/on-escape/) — Escape as dismissal elsewhere; a drag consumes its own Escape while one is in flight
- [component](https://osysharp.com/reference/ui/component/) — state, actions, and the render block these examples are written in
- [layout primitives](https://osysharp.com/reference/ui/layout/) — `Row`/`Stack`/`Box`, the elements a `drag:` goes on
