> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Slider - Web

> Slider selects a single number or a range of numbers by dragging a thumb along a track.

> [!NOTE]
>
> For development, import `Slider` from
> `@servicetitan/anvil2/beta`. For design, find it in the
> [Slider file](https://www.figma.com/design/mDQWExIArlqrnHQMvOW2O6/Slider).

> [!NOTE]
>
> **Beta Feature**
>
> This feature is currently in beta, and needs to be imported from `@servicetitan/anvil2/beta`.
>
> While we hope to minimize breaking changes, they may occur due to feedback we receive or other improvements. These will always be documented in the changelog and communicated in Slack.
>
> Please reach out in the [#ask-designsystem](https://servicetitan.enterprise.slack.com/archives/CBSRGHTRS) channel with any questions or feedback!

[slider-playground](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-playground?embed=1\&hideNavigation=1\&hideExplorer=1\&terminalHeight=0\&file=src/App.tsx)

## Anatomy

Slider consists of seven elements that work together to select a value along a scale.

![Anatomy of a
Slider](https://mintcdn.com/servicetitan/VXApqfCX-7JXiEPx/images/docs/web/components/slider/design/anatomy-of-a-slider.png?fit=max\&auto=format\&n=VXApqfCX-7JXiEPx\&q=85\&s=3441966cac9eec17b0a455c5e0663d42)

1. **Label** - Names the value being set, with an optional more info icon
2. **Track** - The scale from the lowest to the highest value; the fill shows the selected amount or range
3. **Thumb** - The handle users drag or move with the keyboard; a range slider has two
4. **Value readout** (Optional) - Shows the current value, formatted, opposite the label
5. **Helper text** (Optional) - Description, hint, warning, or error text below the slider
6. **Marks** (Optional) - Dots on the track at selectable values, with optional labels underneath
7. **Min and max labels** (Optional) - The lowest and highest values, under the ends of the track

## Options

Slider supports the following configurations to fit single values, ranges, and fixed sets of choices.

### Marks

Marks show users which values are available. Labeled marks name important points on the scale.

slider-marks

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-marks?file=src/App.tsx)

```tsx lines theme={null}
import { Slider } from "@servicetitan/anvil2/beta";

function App() {
  return (
    <Slider
      label="Project progress"
      defaultValue={75}
      step={25}
      formatValue={(value) => `${value}%`}
      marks={[0, 25, 50, 75, 100].map((value) => ({
        value,
        label: `${value}%`,
      }))}
      style={{ width: 320 }}
    />
  );
}

export default App;
```

### Increment sets

Set the increment to match how precise the value needs to be. Use a dot at every step only with coarse increments, such as every 10 or every 25 on a 0 to 100 scale, so the dots stay readable. With finer increments, leave out the dots or mark only the values users need for orientation.

slider-increment-sets

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-increment-sets?file=src/App.tsx)

```tsx lines theme={null}
import { Flex } from "@servicetitan/anvil2";
import { Slider } from "@servicetitan/anvil2/beta";

function App() {
  return (
    <Flex direction="column" gap="6" style={{ width: 320 }}>
      {[25, 10].map((step) => (
        <Slider
          key={step}
          label="Progress"
          defaultValue={50}
          step={step}
          formatValue={(value) => `${value}%`}
          marks
          description={`Increments every ${step}%`}
        />
      ))}
    </Flex>
  );
}

export default App;
```

### Restricted to marks

Restrict selection to the marks when only specific, unevenly spaced values are valid. The thumb moves from mark to mark.

slider-restrict-to-marks

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-restrict-to-marks?file=src/App.tsx)

```tsx lines theme={null}
import { Slider } from "@servicetitan/anvil2/beta";

function App() {
  return (
    <Slider
      label="Service radius"
      defaultValue={25}
      formatValue={(value) => `${value} mi`}
      marks={[0, 10, 25, 50, 100].map((value) => ({
        value,
        label: String(value),
      }))}
      restrictToMarks
      style={{ width: 320 }}
    />
  );
}

export default App;
```

### Range

A range slider has two thumbs that select a start and end value. The fill covers the selected range.

slider-range

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-range?file=src/App.tsx)

```tsx lines theme={null}
import { Slider } from "@servicetitan/anvil2/beta";

function App() {
  return (
    <Slider
      label="Pay range"
      range
      defaultValue={[18, 28]}
      min={15}
      max={60}
      formatValue={(value) => `$${value}`}
      showMinMaxLabels
      style={{ width: 320 }}
    />
  );
}

export default App;
```

Range sliders can keep a minimum distance between the thumbs. Without one, the thumbs can meet at the same value but never cross.

slider-range-minimum-gap

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-range-minimum-gap?file=src/App.tsx)

```tsx lines theme={null}
import { Slider } from "@servicetitan/anvil2/beta";

const formatHour = (hour: number) =>
  `${hour % 12 || 12} ${hour < 12 ? "AM" : "PM"}`;

function App() {
  return (
    <Slider
      label="Arrival window"
      range
      defaultValue={[8, 12]}
      min={7}
      max={19}
      minStepsBetweenThumbs={2}
      formatValue={formatHour}
      description="Windows must be at least 2 hours."
      style={{ width: 320 }}
    />
  );
}

export default App;
```

### Value tooltip

The value tooltip shows the current value above the thumb. Use it when the value readout is hidden or far from the thumb, or when precise values matter while dragging.

slider-tooltip-visible

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-tooltip-visible?file=src/App.tsx)

```tsx lines theme={null}
import { Slider } from "@servicetitan/anvil2/beta";

function App() {
  return (
    <Slider
      label="Markup"
      defaultValue={50}
      formatValue={(value) => `${value}%`}
      showTooltip
      data-interactive="hover"
      style={{ width: 320 }}
    />
  );
}

export default App;
```

### Named values

Values can be shown as words rather than numbers. The same text appears in the readout, the tooltip, and screen reader announcements. When every value has a labeled mark, hide the readout so the selected value does not appear twice.

slider-format-value

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-format-value?file=src/App.tsx)

```tsx lines theme={null}
import { Slider } from "@servicetitan/anvil2/beta";

const tiers = ["Good", "Better", "Best"];

function App() {
  return (
    <Slider
      label="Equipment tier"
      defaultValue={1}
      min={0}
      max={2}
      formatValue={(value) => tiers[value]}
      marks={tiers.map((label, value) => ({ value, label }))}
      showValue={false}
      style={{ width: 320 }}
    />
  );
}

export default App;
```

### Without fill

Remove the fill when the value is a position rather than an amount, such as a point in time.

slider-no-fill

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-no-fill?file=src/App.tsx)

```tsx lines theme={null}
import { Slider } from "@servicetitan/anvil2/beta";

const quarters = ["Q1", "Q2", "Q3", "Q4"];

function App() {
  return (
    <Slider
      label="Forecast quarter"
      defaultValue={1}
      min={0}
      max={3}
      formatValue={(value) => quarters[value]}
      marks={quarters.map((label, value) => ({ value, label }))}
      showFill={false}
      showValue={false}
      style={{ width: 320 }}
    />
  );
}

export default App;
```

### States

Slider supports disabled, read-only, and error states.

slider-states

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-states?file=src/App.tsx)

```tsx lines theme={null}
import { Flex } from "@servicetitan/anvil2";
import { Slider } from "@servicetitan/anvil2/beta";

function App() {
  return (
    <Flex direction="column" gap="6" style={{ width: 320 }}>
      <Slider
        label="Technician commission"
        defaultValue={10}
        formatValue={(value) => `${value}%`}
        disabled
      />
      <Slider
        label="Max discount"
        defaultValue={15}
        formatValue={(value) => `${value}%`}
        readOnly
        description="Set by your manager."
      />
      <Slider
        label="Discount"
        defaultValue={30}
        formatValue={(value) => `${value}%`}
        error="Over 25% needs manager approval."
      />
    </Flex>
  );
}

export default App;
```

* **Disabled** - The fill, thumb, and value fade and the fill turns gray. The label and helper text stay readable.
* **Read-only** - Shows the value as the end of the fill, with no thumb and no dots. Users can still focus the slider to hear its value.
* **Error** - The fill, thumb border, and focus ring turn red, and the error message appears below.

### With a number field

Place a [Number Field](https://anvil.servicetitan.com/docs/components/number-field/web.md) next to the Slider when users need to drag or type an exact value. Hide the slider's value readout, since the field shows the value.

slider-with-number-field

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-with-number-field?file=src/App.tsx)

```tsx lines theme={null}
import { Flex, NumberField } from "@servicetitan/anvil2";
import { Slider } from "@servicetitan/anvil2/beta";
import { useState } from "react";

function App() {
  const [value, setValue] = useState(15);

  return (
    <Flex alignItems="flex-end" gap="4" style={{ width: 320 }}>
      <Slider
        label="Discount"
        showValue={false}
        value={value}
        onChange={setValue}
        formatValue={(next) => `${next}%`}
        style={{ flex: 1 }}
      />
      <NumberField
        hideControls
        label="Discount"
        hideLabel
        suffix="%"
        maxValue={100}
        value={value}
        onChange={(next) => setValue(Math.min(Math.max(next ?? 0, 0), 100))}
        style={{ width: 88 }}
      />
    </Flex>
  );
}

export default App;
```

For a range, place two Number Fields below the Slider, joined by an en dash.

slider-range-with-number-fields

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-range-with-number-fields?file=src/App.tsx)

```tsx lines theme={null}
import { Flex, NumberField, Text } from "@servicetitan/anvil2";
import { Slider, type SliderRangeValue } from "@servicetitan/anvil2/beta";
import { useState } from "react";

function App() {
  const [value, setValue] = useState<SliderRangeValue>([18, 28]);
  const [start, end] = value;

  return (
    <Flex direction="column" gap="2" style={{ width: 320 }}>
      <Slider
        label="Pay range"
        range
        showValue={false}
        min={15}
        max={60}
        value={value}
        onChange={setValue}
        formatValue={(next) => `$${next}`}
      />
      <Flex alignItems="center" gap="2">
        <NumberField
          hideControls
          label="Minimum pay"
          hideLabel
          prefix="$"
          minValue={15}
          maxValue={60}
          value={start}
          onChange={(next) =>
            setValue([Math.min(Math.max(next ?? 15, 15), end), end])
          }
          style={{ flex: 1, minWidth: 0 }}
        />
        <Text aria-hidden subdued>
          –
        </Text>
        <NumberField
          hideControls
          label="Maximum pay"
          hideLabel
          prefix="$"
          minValue={15}
          maxValue={60}
          value={end}
          onChange={(next) =>
            setValue([start, Math.min(Math.max(next ?? 60, start), 60)])
          }
          style={{ flex: 1, minWidth: 0 }}
        />
      </Flex>
    </Flex>
  );
}

export default App;
```

## Behavior

Slider responds to dragging, clicking the track, and keyboard input.

### Dragging and clicking

Dragging a thumb moves it in increments along the track. Clicking anywhere on the track moves the nearest thumb to that point, which gives users an alternative to dragging. The whole 40px row around the track responds to touch and clicks, so the target stays large while the track stays thin.

### Feedback

The thumb lifts with a shadow on hover and focus, and lifts further while pressed. A focus ring appears only during keyboard use; it stays hidden after dragging or clicking until a key is pressed.

### Value tooltip

When enabled, the tooltip appears on thumb hover, while dragging, and on keyboard focus. Users can move the pointer onto the tooltip without it closing, and Escape hides it. On touch screens it appears only while dragging, so it does not stay open after a tap.

### Right-to-left

In right-to-left layouts the track reverses: the lowest value sits on the right, and the left and right arrow keys swap direction.

## Usage Guidelines

Use the Slider when users choose a value or range from a bounded scale and an approximate value is acceptable, such as a discount percentage, a search radius, or a price range.

| Standard | Requirement |
| - | - |
| DoApproximate values | Use Slider when the relative position matters more than the exact number, such as a discount percentage or a search radius. |
| DoRanges | Use Slider to select a start and an end, such as a price or pay range. |
| DoOrdered sets | Use Slider to choose from a small, ordered set of values, such as low, medium, and high. |
| DoFiltering | Use Slider to filter results by a numeric range. |
| Don'tExact values | Avoid using Slider for values users need to enter exactly. |
| Don'tLarge or unbounded ranges | Avoid using Slider when the range is very large or has no upper bound. |
| Don'tUnordered options | Avoid using Slider when the choices have no natural order. |

### Alternatives

#### Slider vs Number Field

Use [Number Field](https://anvil.servicetitan.com/docs/components/number-field/web.md) when users know the exact value they want to enter or when the range is very large. Use Slider when users explore a range or set an approximate value. When users need both, see [With a number field](https://anvil.servicetitan.com/docs/components/slider/web.md#with-a-number-field).

#### Slider vs Radio

Use [Radio](https://anvil.servicetitan.com/docs/components/radio/web.md) when users pick one of a few distinct options that are not points on a scale, or when each option needs its own description. Use Slider when the options are ordered steps along one scale.

## Content

Content within the Slider should name the value clearly and show it in units users recognize.

* Write the label as a noun that names the value, such as "Discount" or "Service radius"
* Include units in the displayed value, such as "15%", "\$28", or "25 mi"
* Keep mark labels short and label only the marks users need for orientation
* Include units in mark labels and min and max labels, such as "25%" or "50 mi". If the labels crowd, remove the units from the scale and keep them in the readout. Keep the units on the scale when the readout is hidden
* Min and max labels use the same format as the readout. To show the ends without units, label the first and last marks instead
* On dense scales, such as every 10 on a 0 to 100 scale, label every other mark or only the first and last marks so the labels do not crowd
* Use the description to explain limits or effects, such as "Applied to the invoice total before tax"
* Write error messages that state what to change, such as "Over 25% needs manager approval"

### Text below the track

Consider using one type of text below the track: min and max labels, mark labels, or helper text. Combining them can crowd the slider and make it harder to tell which text describes the scale.

* **Min and max labels** - Work well when only the ends of the scale need context
* **Mark labels** - Work well when points along the scale need names; consider labeling the first and last marks instead of adding min and max labels
* **Helper text** - Works well when users need an explanation more than scale context

Error messages appear below the slider when needed, even alongside scale labels.

## Keyboard Interaction

Users can navigate the Slider using standard keyboard controls.

| Key | Description |
| - | - |
| Tab | Moves focus to the thumb; in a range slider, moves to the next thumb |
| Right Arrow or Up Arrow | Increases the value by one increment, or to the next mark |
| Left Arrow or Down Arrow | Decreases the value by one increment, or to the previous mark |
| Page Up | Increases the value by a large increment |
| Page Down | Decreases the value by a large increment |
| Home | Sets the value to the minimum |
| End | Sets the value to the maximum |
| Escape | Hides the value tooltip |

The large increment defaults to a tenth of the range. In right-to-left layouts, the Left and Right Arrow keys swap direction.

## Code

> [tab] Implementation
>
> slider-playground
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-playground?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> function App() {
>   return (
>     <Slider
>       label="Discount"
>       defaultValue={15}
>       formatValue={(value) => `${value}%`}
>       description="Applied to the invoice total before tax."
>       showTooltip
>       style={{ width: 320 }}
>     />
>   );
> }
>
> export default App;
> ```
>
> ## Common Examples
>
> ```tsx theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> function ExampleComponent() {
>   return <Slider label="Discount" defaultValue={15} />;
> }
> ```
>
> ### Controlled usage
>
> The `Slider` can be controlled. `onChange` fires on every step of a drag, and `onChangeEnd` fires once when the drag ends or a key changes the value. Use `onChangeEnd` for work that should not run on every step, such as saving.
>
> slider-controlled
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-controlled?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Button, Flex } from "@servicetitan/anvil2";
> import { Slider } from "@servicetitan/anvil2/beta";
> import { useState } from "react";
>
> function App() {
>   const [value, setValue] = useState(20);
>
>   return (
>     <Flex direction="column" gap="4" style={{ width: 320 }}>
>       <Slider
>         label="Volume"
>         value={value}
>         onChange={setValue}
>         onChangeEnd={(next) => console.log("Saved", next)}
>       />
>       <Flex gap="2">
>         <Button onClick={() => setValue(0)}>Mute</Button>
>         <Button onClick={() => setValue(100)}>Max</Button>
>       </Flex>
>     </Flex>
>   );
> }
>
> export default App;
> ```
>
> ### Formatting values
>
> `formatValue` controls the text in the value readout, the tooltip, the min and max labels, and `aria-valuetext`. Use it to add units or to name values.
>
> slider-format-value-readout
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-format-value-readout?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> const tiers = ["Good", "Better", "Best"];
>
> function App() {
>   return (
>     <Slider
>       label="Equipment tier"
>       defaultValue={1}
>       min={0}
>       max={2}
>       formatValue={(value) => tiers[value]}
>       marks
>       showMinMaxLabels
>       style={{ width: 320 }}
>     />
>   );
> }
>
> export default App;
> ```
>
> ### Marks
>
> Pass an array of `SliderMark` objects to `marks` to draw dots at chosen values, with optional labels.
>
> > [!NOTE]
> >
> > Consider using one type of text below the track: `showMinMaxLabels`, mark labels, or helper text (`description` or `hint`). See [Text below the track](https://anvil.servicetitan.com/docs/components/slider/web.md#text-below-the-track).
>
> slider-marks
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-marks?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> function App() {
>   return (
>     <Slider
>       label="Project progress"
>       defaultValue={75}
>       step={25}
>       formatValue={(value) => `${value}%`}
>       marks={[0, 25, 50, 75, 100].map((value) => ({
>         value,
>         label: `${value}%`,
>       }))}
>       style={{ width: 320 }}
>     />
>   );
> }
>
> export default App;
> ```
>
> Pass `marks={true}` to draw a dot at every step. Use it only with coarse steps.
>
> slider-increment-sets
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-increment-sets?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Flex } from "@servicetitan/anvil2";
> import { Slider } from "@servicetitan/anvil2/beta";
>
> function App() {
>   return (
>     <Flex direction="column" gap="6" style={{ width: 320 }}>
>       {[25, 10].map((step) => (
>         <Slider
>           key={step}
>           label="Progress"
>           defaultValue={50}
>           step={step}
>           formatValue={(value) => `${value}%`}
>           marks
>           description={`Increments every ${step}%`}
>         />
>       ))}
>     </Flex>
>   );
> }
>
> export default App;
> ```
>
> ### Restrict to marks
>
> With `restrictToMarks`, only the `marks` values can be selected, by drag or keyboard, and `step` is ignored. The arrow keys move one mark at a time. `restrictToMarks` requires a `marks` array.
>
> slider-restrict-to-marks
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-restrict-to-marks?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> function App() {
>   return (
>     <Slider
>       label="Service radius"
>       defaultValue={25}
>       formatValue={(value) => `${value} mi`}
>       marks={[0, 10, 25, 50, 100].map((value) => ({
>         value,
>         label: String(value),
>       }))}
>       restrictToMarks
>       style={{ width: 320 }}
>     />
>   );
> }
>
> export default App;
> ```
>
> ### Range
>
> Set `range` to render two thumbs. `value`, `defaultValue`, `onChange`, and `onChangeEnd` then use a `SliderRangeValue`, a `[start, end]` tuple in ascending order.
>
> slider-range
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-range?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> function App() {
>   return (
>     <Slider
>       label="Pay range"
>       range
>       defaultValue={[18, 28]}
>       min={15}
>       max={60}
>       formatValue={(value) => `$${value}`}
>       showMinMaxLabels
>       style={{ width: 320 }}
>     />
>   );
> }
>
> export default App;
> ```
>
> `minStepsBetweenThumbs` keeps the thumbs a minimum number of steps apart. With `restrictToMarks`, it counts marks instead of steps.
>
> slider-range-minimum-gap
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-range-minimum-gap?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> const formatHour = (hour: number) =>
>   `${hour % 12 || 12} ${hour < 12 ? "AM" : "PM"}`;
>
> function App() {
>   return (
>     <Slider
>       label="Arrival window"
>       range
>       defaultValue={[8, 12]}
>       min={7}
>       max={19}
>       minStepsBetweenThumbs={2}
>       formatValue={formatHour}
>       description="Windows must be at least 2 hours."
>       style={{ width: 320 }}
>     />
>   );
> }
>
> export default App;
> ```
>
> ### Value tooltip
>
> `showTooltip` shows the value above the thumb on hover, while dragging, and on keyboard focus.
>
> slider-tooltip
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-tooltip?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> function App() {
>   return (
>     <Slider
>       label="Markup"
>       defaultValue={50}
>       formatValue={(value) => `${value}%`}
>       showTooltip
>       style={{ width: 320 }}
>     />
>   );
> }
>
> export default App;
> ```
>
> ### Without fill
>
> Set `showFill={false}` when the value is a position rather than an amount. `showFill` is available only on single-value sliders; passing it with `range` is a type error.
>
> slider-no-fill
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-no-fill?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Slider } from "@servicetitan/anvil2/beta";
>
> const quarters = ["Q1", "Q2", "Q3", "Q4"];
>
> function App() {
>   return (
>     <Slider
>       label="Forecast quarter"
>       defaultValue={1}
>       min={0}
>       max={3}
>       formatValue={(value) => quarters[value]}
>       marks={quarters.map((label, value) => ({ value, label }))}
>       showFill={false}
>       showValue={false}
>       style={{ width: 320 }}
>     />
>   );
> }
>
> export default App;
> ```
>
> ### Disabled, read-only, and error
>
> `readOnly` hides the thumb and dots but keeps the thumb focusable so the value can be read, and the value still submits with a form. With `showFill={false}`, the thumb stays visible. `error` accepts a boolean for styling only, or a string or string array to also render messages.
>
> slider-states
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-states?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Flex } from "@servicetitan/anvil2";
> import { Slider } from "@servicetitan/anvil2/beta";
>
> function App() {
>   return (
>     <Flex direction="column" gap="6" style={{ width: 320 }}>
>       <Slider
>         label="Technician commission"
>         defaultValue={10}
>         formatValue={(value) => `${value}%`}
>         disabled
>       />
>       <Slider
>         label="Max discount"
>         defaultValue={15}
>         formatValue={(value) => `${value}%`}
>         readOnly
>         description="Set by your manager."
>       />
>       <Slider
>         label="Discount"
>         defaultValue={30}
>         formatValue={(value) => `${value}%`}
>         error="Over 25% needs manager approval."
>       />
>     </Flex>
>   );
> }
>
> export default App;
> ```
>
> ### With a number field
>
> Pair `Slider` with a `NumberField` when users need to enter exact values. Set `showValue={false}` on the slider and `hideControls` on the field so the field lines up with the slider row.
>
> slider-with-number-field
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-with-number-field?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Flex, NumberField } from "@servicetitan/anvil2";
> import { Slider } from "@servicetitan/anvil2/beta";
> import { useState } from "react";
>
> function App() {
>   const [value, setValue] = useState(15);
>
>   return (
>     <Flex alignItems="flex-end" gap="4" style={{ width: 320 }}>
>       <Slider
>         label="Discount"
>         showValue={false}
>         value={value}
>         onChange={setValue}
>         formatValue={(next) => `${next}%`}
>         style={{ flex: 1 }}
>       />
>       <NumberField
>         hideControls
>         label="Discount"
>         hideLabel
>         suffix="%"
>         maxValue={100}
>         value={value}
>         onChange={(next) => setValue(Math.min(Math.max(next ?? 0, 0), 100))}
>         style={{ width: 88 }}
>       />
>     </Flex>
>   );
> }
>
> export default App;
> ```
>
> For a range, place two fields below the slider.
>
> slider-range-with-number-fields
>
> [StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/slider-range-with-number-fields?file=src/App.tsx)
>
> ```tsx lines expandable theme={null}
> import { Flex, NumberField, Text } from "@servicetitan/anvil2";
> import { Slider, type SliderRangeValue } from "@servicetitan/anvil2/beta";
> import { useState } from "react";
>
> function App() {
>   const [value, setValue] = useState<SliderRangeValue>([18, 28]);
>   const [start, end] = value;
>
>   return (
>     <Flex direction="column" gap="2" style={{ width: 320 }}>
>       <Slider
>         label="Pay range"
>         range
>         showValue={false}
>         min={15}
>         max={60}
>         value={value}
>         onChange={setValue}
>         formatValue={(next) => `$${next}`}
>       />
>       <Flex alignItems="center" gap="2">
>         <NumberField
>           hideControls
>           label="Minimum pay"
>           hideLabel
>           prefix="$"
>           minValue={15}
>           maxValue={60}
>           value={start}
>           onChange={(next) =>
>             setValue([Math.min(Math.max(next ?? 15, 15), end), end])
>           }
>           style={{ flex: 1, minWidth: 0 }}
>         />
>         <Text aria-hidden subdued>
>           –
>         </Text>
>         <NumberField
>           hideControls
>           label="Maximum pay"
>           hideLabel
>           prefix="$"
>           minValue={15}
>           maxValue={60}
>           value={end}
>           onChange={(next) =>
>             setValue([start, Math.min(Math.max(next ?? 60, start), 60)])
>           }
>           style={{ flex: 1, minWidth: 0 }}
>         />
>       </Flex>
>     </Flex>
>   );
> }
>
> export default App;
> ```
>
> ### Forms
>
> Pass `name` to submit the value with a form through a hidden input. A range slider submits two inputs with the same name. Disabled sliders do not submit.
>
> ## React Accessibility
>
> * Each thumb is an element with `role="slider"`, `aria-valuenow`, `aria-valuemin`, `aria-valuemax`, and `aria-valuetext` from `formatValue`. Range thumbs report the other thumb's value as their bound.
> * The thumb is named by the label. With `hideLabel`, the label becomes the thumb's `aria-label`, with inline markdown stripped. Range thumbs are named "\[label] minimum" and "\[label] maximum".
> * Helper text and `moreInfo` are linked to each thumb with `aria-describedby`. `error` sets `aria-invalid`, `disabled` sets `aria-disabled`, and `readOnly` sets `aria-readonly`.
> * The arrow keys, Page Up, Page Down, Home, and End change the value. In right-to-left layouts the Left and Right Arrow keys swap direction. Escape hides the tooltip.
> * The track row is 40px tall and the whole row responds to pointer input, meeting the WCAG 2.5.8 target size. Clicking the track moves the nearest thumb, providing a single-pointer alternative to dragging (WCAG 2.5.7).
> * The tooltip can be hovered and dismissed with Escape (WCAG 1.4.13). It is hidden from screen readers because `aria-valuetext` already announces the value.
> * The value readout, marks, and mark labels are hidden from screen readers. Use `formatValue` so the announced value matches what users see.

> [tab] Slider Props
>
> ```tsx theme={null}
> <Slider
>   label="Discount"
>   min={0}
>   max={100}
>   step={10}
>   largeStep={20}
>   defaultValue={20}
>   formatValue={(value) => `${value}%`}
>   onChange={(value) => console.log(value)}
>   onChangeEnd={(value) => console.log(value)}
>   marks
>   showMinMaxLabels
>   showTooltip
>   description="Applied to the invoice total before tax."
>   name="discount"
> />
> ```
>
> ## `Slider` Props
>
> `Slider` uses a discriminated union on `range`: single-value sliders take a `number`, and range sliders take a `SliderRangeValue`. A second union requires a `marks` array when `restrictToMarks` is `true`.
>
> **label** `string` *required*
>
> Accessible name for the slider. Supports inline markdown.
>
> **defaultValue** `number | SliderRangeValue`
>
> Initial value when uncontrolled. A `number` for single-value sliders, defaulting to `min`. A `SliderRangeValue` when `range` is `true`, defaulting to `[min, max]`.
>
> **description** `ReactElement | string`
>
> Secondary text rendered below the slider.
>
> **disabled** `boolean` (default: false)
>
> Disables the slider. The thumb leaves the tab order and the value does not submit with a form.
>
> **error** `boolean | string | string[]`
>
> Applies error styling: red fill, thumb border, and focus ring. A string or string array also renders below the slider.
>
> **formatValue** `(value: number) =\> string` (default: String)
>
> Formats values for the readout, tooltip, min and max labels, and `aria-valuetext`.
>
> **hideLabel** `boolean` (default: false)
>
> Hides the visible label and moves it to `aria-label` on each thumb.
>
> **hint** `ReactElement | string`
>
> Always-visible hint rendered below the slider.
>
> **labelProps** `object`
>
> Additional props passed to the label, except `children`, `moreInfo`, `required`, `id`, and `htmlFor`.
>
> **largeStep** `number`
>
> Page Up and Page Down increment. Defaults to a tenth of the range, rounded to whole steps.
>
> **marks** `boolean | SliderMark[]` (default: false)
>
> `true` draws a dot at every step; use it only with coarse steps. An array draws dots at chosen values, with optional labels underneath. Values outside `min` and `max` are ignored. Required when `restrictToMarks` is `true`.
>
> **max** `number` (default: 100)
>
> Highest selectable value.
>
> **min** `number` (default: 0)
>
> Lowest selectable value.
>
> **minStepsBetweenThumbs** `number` (default: 0)
>
> Minimum distance between the thumbs, in steps, or in marks with `restrictToMarks`. `0` lets the thumbs meet. Available only when `range` is `true`.
>
> **moreInfo** `ReactNode`
>
> Content for the info icon tooltip next to the label.
>
> **name** `string`
>
> Submits the value with a form through a hidden input. Range sliders submit two inputs with the same name.
>
> **onChange** `(value: number | SliderRangeValue) =\> void`
>
> Called on every value change, including each step of a drag. Receives a `number`, or a `SliderRangeValue` when `range` is `true`.
>
> **onChangeEnd** `(value: number | SliderRangeValue) =\> void`
>
> Called once a drag ends or a key changes the value. Receives a `number`, or a `SliderRangeValue` when `range` is `true`.
>
> **range** `boolean` (default: false)
>
> Renders two thumbs that select a start and end value.
>
> **readOnly** `boolean` (default: false)
>
> Shows the value without step dots, and without a thumb unless `showFill` is `false`. The thumb stays focusable so the value can be read, and the value still submits with a form.
>
> **restrictToMarks** `boolean` (default: false)
>
> Only the `marks` values can be selected, by drag or keyboard; `step` is ignored. Requires a `marks` array.
>
> **showFill** `boolean` (default: true)
>
> Draws the fill from `min` to the thumb. Turn it off when the value is a position rather than an amount. Not available when `range` is `true`.
>
> **showMinMaxLabels** `boolean` (default: false)
>
> Shows the formatted `min` and `max` under the ends of the track.
>
> **showTooltip** `boolean` (default: false)
>
> Shows the value above the thumb on hover, while dragging, and on keyboard focus. Escape hides it until the thumb is hovered, focused, or moved again. Not shown when `readOnly` is `true`.
>
> **showValue** `boolean` (default: true)
>
> Shows the formatted value opposite the label.
>
> **step** `number` (default: 1)
>
> Granularity of values, counted from `min`.
>
> **value** `number | SliderRangeValue`
>
> Current value in controlled mode. A `number` for single-value sliders, or a `SliderRangeValue` when `range` is `true`.
>
> **warning** `string | string[]`
>
> Warning text rendered below the slider.
>
> > [!NOTE]
> >
> > `Slider` also accepts layout utility props and standard `div` attributes, except `onChange`, `defaultValue`, and `children`. They apply to the root element.

> [tab] SliderMark
>
> ```tsx theme={null}
> import type { SliderMark } from "@servicetitan/anvil2/beta";
>
> const marks: SliderMark[] = [
>   { value: 0, label: "0" },
>   { value: 50 },
>   { value: 100, label: "100" },
> ];
> ```
>
> ## `SliderMark` Properties
>
> **value** `number` *required*
>
> Position on the scale. Values outside `min` and `max` are ignored.
>
> **label** `string`
>
> Text shown under the mark.
>
> ## `SliderRangeValue`
>
> `SliderRangeValue` is `[number, number]`: the start and end values of a range slider, in ascending order.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.