# Range Field

Two native thumbs on one track, kept in order. (Custom element.)

Reference: https://timeless.build/docs/components/range-field/

> **Choosing between components.** Use Range Field when the value is a span. For a single value, [Range](/docs/components/range/) is CSS over one native input and needs no JavaScript at all.

> **Markup you author.** Both thumbs are native range inputs with their own `name`, so the pair submits as two entries and resets natively with scripting off. Give each one its own accessible name saying which end it is; nothing else can tell them apart in a screen reader.

## Markup

```html
<div class="ui-field">
  <span class="ui-label" id="budget-label">Monthly budget</span>
  <ui-range-field id="budget" aria-labelledby="budget-label">
    <span data-ui-part="track">
      <input data-ui-part="from" type="range" name="budget-from" value="120" min="0" max="500" step="10" aria-label="Monthly budget, lower bound" aria-describedby="budget-description">
      <input data-ui-part="to" type="range" name="budget-to" value="380" min="0" max="500" step="10" aria-label="Monthly budget, upper bound" aria-describedby="budget-description">
    </span>
    <output data-ui-part="output">120 – 380</output>
  </ui-range-field>
  <p class="ui-description" id="budget-description">A thumb stops at its neighbour rather than swapping with it.</p>
</div>
```

## Install

```js
import '@timelessui/components/css/tokens.css'
import '@timelessui/components/css/core/forms.css'
import '@timelessui/components/css/core/range-field.css'
import '@timelessui/components/css/themes/atmosphere/tokens.css'
import '@timelessui/components/css/themes/atmosphere/forms.css'
import '@timelessui/components/css/themes/atmosphere/range-field.css'
import '@timelessui/components/register/ui-range-field'
```

A `register/` import is a side effect: it defines the element as the module evaluates, so it
has to run in code the browser loads. To register explicitly instead — to control the timing,
or to define into another window — call the function from the matching `define/` entry point,
which registers nothing on its own:

```js
import { defineRangeFieldElement } from '@timelessui/components/define/ui-range-field'
defineRangeFieldElement()
```

## Anatomy

Parts are authored in your own markup and identified by the selector below. Required parts
must be present for the component to work. Private `data-ui-internal-*` hooks are written by
the runtime and must never be authored.

**<ui-range-field>**

| Part | Required | Selector | Purpose |
| --- | --- | --- | --- |
| `track` | Yes | `[data-ui-part~='track']` | Wrapper around the two thumbs. It is the shared track: both inputs stack inside it, and the fill between them is drawn on it from measured bounds. |
| `from` | Yes | `input[type='range']` | The lower thumb, a native range input. Give it its own `name`, `min`, `max`, `step`, and accessible name; it submits and resets on its own, with no JavaScript. |
| `to` | Yes | `input[type='range']` | The upper thumb. Same contract as `from`, with its own `name` so the pair submits as two entries. |
| `output` | No | `[data-ui-part~='output']` | Live readout of the pair. Timeless writes the current values into it as text, so omit the part when you want to format them yourself. |

**ui-field**

| Part | Required | Selector | Purpose |
| --- | --- | --- | --- |
| `control` | No | `[data-ui-part~='control']` | Wrapper around the native control when one is needed. |

## Attributes

Every value below is implemented by the stylesheets this component ships. Boolean attributes
are presence-based: author the attribute with no value, or omit it.

**ui-field**

| Attribute | Values | Default | Description |
| --- | --- | --- | --- |
| `data-ui-layout` | `stacked` · `inline` | `stacked` | Whether the label sits above the control or beside it. |
| `data-ui-density` | `compact` · `normal` · `spacious` | `normal` | Gap between label, control, description, and error. |

## Element API

Attributes above are the authoring surface. These are the DOM properties and events the
registered element adds once it upgrades.

### `<ui-range-field>`

This element adds no public properties. Configure it with its attributes.

| Event | Detail | Description |
| --- | --- | --- |
| `ui-change` | `CustomEvent<RangeFieldChangeDetail>` | Dispatched after either thumb moves, carrying the clamped pair and which thumb moved. Bubbles and is composed. |

## State

Native attributes, ARIA, and platform pseudo-classes are authoritative. Style state through
the selectors below rather than adding your own state classes.

| State | Source | How to express it |
| --- | --- | --- |
| `disabled` | native | Native `disabled` on either thumb. |
| `invalid` | aria | Set `aria-invalid="true"` on the control and point `aria-describedby` at the error part. |

## Styling

Root identity: `ui-range-field`, `ui-field`, `ui-label`, `ui-description`.
Required stylesheets: `tokens.css`, `core/forms.css`, `core/range-field.css`, `themes/atmosphere/tokens.css`, `themes/atmosphere/forms.css`, `themes/atmosphere/range-field.css`.

Set these custom properties to restyle the component:

| Custom property | Controls |
| --- | --- |
| `--ui-range-track` | Track thickness. |
| `--ui-range-thumb` | Thumb diameter. |
| `--ui-range-fill` | Colour of the filled span between the two thumbs. |

Design tokens this component reads (27), global and set at the theme level: `--ui-accent`, `--ui-bg-control`, `--ui-bg-control-muted`, `--ui-bg-surface`, `--ui-bg-surface-subtle`, `--ui-danger`, `--ui-danger-soft`, `--ui-disabled-opacity`, `--ui-duration-fast`, `--ui-ease-standard`, `--ui-fg`, `--ui-fg-muted`, `--ui-fg-subtle`, `--ui-focus`, `--ui-font-mono`, `--ui-line`, `--ui-line-strong`, `--ui-radius-md`, `--ui-radius-pill`, `--ui-radius-sm`, `--ui-shadow-control`, `--ui-shadow-inset`, `--ui-space-1`, `--ui-space-2`, `--ui-space-3`, `--ui-space-4`, `--ui-space-5`.

## Accessibility

Keep the native elements, roles, and relationships shown in the markup. Timeless adds only the
state and keyboard coordination the platform does not already provide, and it never supplies
your accessible names — those depend on your content.

Follows the [Slider (Multi-Thumb) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/) from the ARIA Authoring Practices Guide.

| Key | Action |
| --- | --- |
| `Arrow keys, Home / End, Page Up / Page Down` | Move the focused thumb. Handled by the native range input, not by Timeless. |

Each thumb is a native `input[type=range]` and therefore its own tab stop, with its own accessible name and its own value announcement. Timeless only keeps the pair ordered: a thumb stops at its neighbour rather than swapping with it, so the key you are holding never starts moving the other thumb.

## Before JavaScript runs

The markup above is complete and usable on its own: native controls submit, labels associate, and authored ARIA is already correct. Registration adds state synchronisation, focus management, and keyboard coordination on top of it.
