# Range

Lets users select a numeric value, or a range between two values, by dragging a handle along a track. Supports horizontal and vertical orientations, step snapping, optional value tooltips, and binds through `x-model`. The slider wraps a native input, so the value participates in forms and validation.

Part of the Harmonia Alpine.js component library. Every directive uses the `x-h-` prefix.

## Usage

Use range sliders for selecting numeric values within a defined range, such as volume, price, or time intervals. Avoid using sliders for exact numeric input as precision can be difficult with dragging alone. For precise values, pair the slider with a visible readout or an Input.

The element must contain a native `<input>` as a direct child. The slider hides it and keeps its value in sync (`low,high` in dual mode), while the input carries everything form-related: the submitted `name`, the `disabled` state, constraints, custom validity, and `aria-invalid`. A form reset restores the slider to its initial values.

## Directive

- `x-h-range`

## API

### Attributes

| Attribute      | Values            | Required | Description                                                                                                  |
| -------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
| data-min       | number            | false    | Lowest selectable value (default: `0`).                                                                      |
| data-max       | number            | false    | Highest selectable value (default: `100`).                                                                   |
| data-step      | number            | false    | Step the values snap to (default: `1`).                                                                      |
| data-value     | number            | false    | Initial value when no `x-model` is bound. In dual mode, two comma-separated values (e.g. `"20,80"`).         |
| data-tooltips  | `true`<br/>`auto` | false    | Shows the current value in a tooltip at each handle. With `auto`, only while a handle is dragged or focused. |
| data-unit      | string            | false    | Suffix appended to tooltip text and `aria-valuetext` (e.g. `"%"`).                                           |
| data-label     | string            | false    | Accessible name for the handle in single mode (Defaults to `"Value"`). Ignored in dual mode.                 |
| data-min-label | string            | false    | Accessible name for the lower handle in dual mode (default: `"Minimum"`).                                    |
| data-max-label | string            | false    | Accessible name for the upper handle in dual mode (default: `"Maximum"`).                                    |

### Modifiers

| Modifier | Description                                                   |
| -------- | ------------------------------------------------------------- |
| vertical | Renders a vertical slider. Give the element a height.         |
| dual     | Renders two handles for selecting a range between two values. |

### Model

Bind a number with `x-model`, or an array of two numbers (`[low, high]`) in dual mode. The model updates live while a handle is dragged. Values coming from the model are clamped to the `data-min` / `data-max` bounds and snapped to `data-step` for display.

### Events

| Event  | Description                                                                                       |
| ------ | ------------------------------------------------------------------------------------------------- |
| input  | Fired continuously while a handle moves. The current value is in `event.detail`.                  |
| change | Fired when a handle is released or after a keyboard change. The final value is in `event.detail`. |

### Validation timing

By default this control shows native-constraint errors only after the user attempts to submit the form, since the inner input is never edited directly. To validate on load instead, set `data-validate="immediate"` on a wrapping `x-h-fieldset`, `x-h-field`, or any ancestor element. Setting `aria-invalid="true"` on the inner input always shows the error immediately. See Fieldset for details.

## Keyboard Handling

Each handle is focusable and supports:

- `Right` / `Up` - Increase the value by one step (`Right` and `Left` swap in right-to-left layouts).
- `Left` / `Down` - Decrease the value by one step.
- `Page Up` / `Page Down` - Increase or decrease the value by ten steps.
- `Home` - Set the minimum value.
- `End` - Set the maximum value.

In dual mode the two handles cannot cross, so `Home` on the upper handle stops at the lower handle's value and `End` on the lower handle stops at the upper handle's value.

## Accessibility

Every handle exposes `role="slider"` with `aria-valuemin` / `aria-valuemax` / `aria-valuenow` and `aria-orientation`. When `data-unit` is set, a matching `aria-valuetext` (e.g. "42%") is announced as well. In dual mode the element itself becomes a `role="group"` named "Range" by default, which you can override with your own `aria-label` or `aria-labelledby`, and the handles receive the `data-min-label` / `data-max-label` names. While the inner input is invalid (or carries `aria-invalid`), the handles are marked `aria-invalid` so the state is announced. Pointer dragging, track clicks, and full keyboard operation are all supported, and pressing the track moves the nearest handle.

## Binding

Binds through Alpine `x-model`. See the Examples for the expected value shape.

## Examples

### Default

```html
<div x-data="{ volume: 40 }" class="flex items-center gap-4">
  <div x-h-range x-model="volume" data-label="Volume">
    <input type="text" name="volume" />
  </div>
  <span class="w-12 text-right" x-text="volume"></span>
</div>
```

### Dual handles

```html
<div x-data="{ price: [20, 80] }" class="flex items-center gap-4">
  <div x-h-range.dual x-model="price" aria-label="Price range">
    <input type="text" name="price" />
  </div>
  <span class="w-12 text-right" x-text="price[0] + '-' + price[1]"></span>
</div>
```

### Vertical

```html
<div x-h-range.vertical data-value="30" data-label="Balance" style="height: 10rem">
  <input type="text" name="balance" />
</div>
<div x-h-range.vertical.dual data-value="20,60" aria-label="Working hours" style="height: 10rem">
  <input type="text" name="hours" />
</div>
```

### Tooltips

Set `data-tooltips="true"` for always-visible value tooltips, or `data-tooltips="auto"` to show them only while a handle is dragged or focused.

```html
<div x-h-range data-value="40" data-tooltips="true" data-unit="%" data-label="Humidity">
  <input type="text" name="humidity" />
</div>
<div x-h-range.dual data-value="20,80" data-tooltips="auto" data-unit="%" aria-label="Brightness range">
  <input type="text" name="brightness" />
</div>
```

### Steps and bounds

```html
<div x-data="{ rating: 6 }" class="flex items-center gap-4">
  <div x-h-range x-model="rating" data-min="1" data-max="8" data-step="1" data-label="Rating">
    <input type="text" name="rating" />
  </div>
  <span class="w-12 text-right" x-text="rating"></span>
</div>
<div x-data="{ opacity: 0.5 }" class="flex items-center gap-4">
  <div x-h-range x-model="opacity" data-min="0" data-max="1" data-step="0.1" data-label="Opacity">
    <input type="text" name="opacity" />
  </div>
  <span class="w-12 text-right" x-text="opacity"></span>
</div>
```

### Invalid

Reacts to the native invalid state or to the `aria-invalid` attribute set on the inner input. Native errors appear after a submit attempt: drag the second slider above 50 and press Submit, then drag it back to clear the error.

```html
<div x-h-range data-value="40" data-label="Volume">
  <input type="text" name="volume" aria-invalid="true" />
</div>
<form x-data @submit.prevent class="flex flex-col items-start gap-4">
  <div x-h-range data-value="40" data-label="Budget" @input="$refs.budget.setCustomValidity($event.detail > 50 ? 'Keep the budget at 50 or below' : '')">
    <input type="text" name="budget" x-ref="budget" />
  </div>
  <button x-h-button type="submit">Submit</button>
</form>
```

### Disabled

Set the native `disabled` attribute on the inner input to disable the slider.

```html
<div x-h-range data-value="40" data-label="Volume">
  <input type="text" name="volume" disabled />
</div>
```

Full docs: https://www.codbex.com/harmonia/components/range.html

## Notes

- Directive values are Alpine expressions, so quote string literals: `x-h-...="'Label'"`.
- Components render only after Alpine has registered Harmonia. See SKILL.md for setup.
