Cash advance
Pick the amount you want advanced against next month's revenue.
0 €
10.000 €
Overview
A slider is the right control when the user is picking a point on a continuous scale and the position itself carries meaning — a cash advance against revenue, a daily auto-debit cap, a risk tier. It makes sense the moment a precise number is overkill (the user wants “around 4 grand”, not “exactly 4.137 €”) but the magnitude still matters. For exact values, reach for a number input.
Two thumbs only when the answer is genuinely a span
A range slider with two thumbs picks bothends of a window — a price filter, a date range, a transaction-size band. Don't use two thumbs for a single value with a default; that's a single slider with a starting position.
Anatomy
Five parts. The label area sits above the track; the range labels sit below. Both are optional in pairs — but the track itself, the fill, and the thumb are always present.
Cash advance
Pick the amount you want advanced.
0 €
10.000 €
Label area
Inter Tight Medium 16/20 label + optional regular description (16/20 in text.tertiary). Names the question the slider is answering.
Track (unfilled)
4 px-tall pill at 14% foreground. Spans the full row; the thumb glides along the centre line.
Track (filled)
Same height, foreground colour. Stretches from 0 to the thumb on a single slider; from thumb-1 to thumb-2 on a range.
Thumb
16 px circle in foreground. Centred on the value position, sits on top of the track. Hover halo at 8% foreground; focus ring with 2 px offset.
Range labels (optional)
Min on the left, max on the right. Inter Tight Regular 16/20 in text.tertiary, tabular numerals so units stack cleanly.
Types
Two type variants. The single-thumb slider picks one value on a scale; the two-thumb range picks both ends of a window. Same track, same dimensions — only the thumb count changes.
Single value
One thumb. The user picks a point on a continuous scale.
Daily cap
The most we'll auto-debit toward repayment in any one day.
0 €
2.500 €
Range · two thumbs
Two thumbs. The user picks a span — a min and a max on the same scale.
Transaction size
Filter to transfers that fall inside this range.
0 €
50.000 €
Header and labels
Three pieces of supporting copy can sit around the track: a label (always recommended unless the question is obvious), an inline value display (when the user needs to read the live number as they drag), and the min/max range labels (when the absolute scale matters).
Label + description + range
Cash advance
Pick the amount you want advanced.
0 €
10.000 €
Label only
Cash advance
Inline value display
Cash advance
4.200 €
Adjust the live amount.
0 €
10.000 €
The current value sits to the right of the label, in foreground weight. Use when the user needs to read the value as they drag.
No header
Bare track. Use only inside dense composed surfaces (a filter row, a settings table) where the question is already obvious from surrounding structure.
Continuous and discrete
A continuous slider lets the thumb glide; the value can be any number in the scale. A discrete slider snaps to evenly spaced notches — use it when only specific values are meaningful.
Continuous
Volume
0%
100%
The default. Any value on the scale is valid; the thumb glides.
Discrete · 5 steps
Risk tolerance
Low
High
The thumb snaps to evenly spaced notches. Use when only specific values are meaningful — risk tiers, payment plans, batch sizes.
States
Five visible states. Held still here for review — hover, focus, and active come for free on the real component via CSS pseudo-classes.
Default
Hover
Focus
Disabled
Error
Hover and focus are halo-only
Pointer hover renders a soft halo at 8% foreground around the thumb; keyboard focus renders a 2 px ring with 2 px offset. The track and thumb fill colours don't change. Don't add a scale transform on the thumb — the halo is enough.
Error
Apply the error tone when the picked value violates a constraint the user can resolve — exceeding a cap, dropping below a minimum, or conflicting with another field. The track and thumb both flip; a single-line helper below the range names the constraint.
Error
Cash advance
Pick the amount you want advanced.
0 €
10.000 €
Maximum advance is 8.000 € against your current revenue.
Apply the error tone when the picked value violates a constraint the user can resolve. The track + thumb both flip; a single-line helper names the limit.
Behavior
- Click anywhere on the track to jump to that value. The thumb animates to the click position in 150 ms. The whole track is the click target — not just the 16 px thumb.
- Drag with the cursor or finger. The thumb follows the pointer; the value updates as it moves (controlled state) so any inline value display reflects the live number.
- Keyboard moves in steps. ← / → move by 1 step; Home and End jump to min and max. Page Up / Page Dn move by 10× step.
- Range thumbs can't cross. On a two-thumb slider, the lower thumb stops at the upper thumb's position — and vice versa. The two never pass each other; the constraint is enforced before the value updates.
- Round to a sensible step. Even on a continuous slider, the user almost never wants fractional values. Round to the nearest 1, 5, 10, or 100 depending on the scale; the input feels smooth without committing to noise.
Slider vs. input vs. select
The slider, the number input, and the select all answer “pick a value” questions, but they earn their place in different contexts.
Range slider
The position on the scale matters; the user's aiming for an approximate value (“around half”) and seeing the magnitude is part of the answer.
Number input
The exact value matters and the user knows it — invoice amount, product weight, retry count. Pair with stepper buttons if the user often nudges the value.
Select
The values are discrete and named — country, currency, plan tier with its own marketing label. Don't use a discrete slider when the steps deserve names.
Money field
Currency-aware input with the system's formatting ( European separators, prefix/suffix per locale). Use this for exact money values; pair with a slider only when both are useful.
Accessibility
- Native input where possible.
<input type="range">inherits keyboard, focus, value reporting, andaria-valuenowannouncements for free. Wrap it for visual styling; don't replace it. - Label association. Every slider needs an accessible name — either a wrapping
<label>element oraria-labelledbypointing at the visible label area. - aria-valuetext when units matter. The default
aria-valuenowannounces a bare number. When the value is currency, percent, or anything formatted, setaria-valuetextto the formatted string — “4.200 €”, not just “4200”. - Range:use two inputs, each with its own accessible name (“Minimum”, “Maximum”). A single
role="group"wrapper with a shared legend describes the field as a whole. - Focus order:on a range, the lower thumb tabs first, then the upper. Don't reverse the order or skip the lower thumb when it's at the start.
- Touch target:the visible thumb is 16 px; the actual hit area extends to the full track height (44 px+) so touch users don't have to thread the needle.
- Reduced motion: the click-to-jump animation is a 150 ms transform. Honour
prefers-reduced-motion— snap the thumb instead of animating.
Code
One RangeSlider component, one props shape. Single-thumb is the default; pass value as a tuple to render the two-thumb range.
tsx
import { RangeSlider } from "@flatpay-dk/ui";
// Single value
const [advance, setAdvance] = useState(4200);
<RangeSlider
label="Cash advance"
description="Pick the amount you want advanced."
value={advance}
onValueChange={setAdvance}
min={0}
max={10000}
step={100}
format={(v) => new Intl.NumberFormat("da-DK", {
style: "currency",
currency: "EUR",
maximumFractionDigits: 0,
}).format(v)}
/>
// Discrete steps
<RangeSlider
label="Risk tolerance"
value={tier}
onValueChange={setTier}
min={0}
max={4}
step={1}
showRangeLabels
rangeLabels={["Low", "High"]}
/>
// Two-thumb range
const [bounds, setBounds] = useState([10, 70]);
<RangeSlider
label="Transaction size"
description="Filter to transfers inside this range."
value={bounds}
onValueChange={setBounds}
min={0}
max={50000}
step={500}
format={(v) => `${v.toLocaleString("da-DK")} €`}
/>
// Inline value display + error
<RangeSlider
label="Cash advance"
value={advance}
onValueChange={setAdvance}
min={0}
max={10000}
showValue
error={advance > maxAllowed
? "Maximum advance is 8.000 € against your current revenue."
: undefined}
/>Best practices
Sliders are unfamiliar in dense product UI. The defaults below keep them legible without surprising the user.
Cash advance
0 €
10.000 €
Do
Show the live value somewhere — inline next to the label, or as a min/max range below the track. The slider is for approximation; the number anchors it.
Don't
Don't ship a bare track. Without a label, range labels, or live value, the user has no idea what they're moving.
Cash advance
0 €
10.000 €
Step: 100 € · current: 4.200 €
Do
Round to a sensible step. The user picks 4.200 €, not 4.137,82 €. Steps of 100 / 500 / 1.000 fit currency; 1 / 5 / 10 fit counts.
Cash advance
0 €
10.000 €
Step: 0,01 € · current: 4.137,82 €
Don't
Don't snap to fractional pennies. A continuous slider that reports 4.137,82 € reads as broken; the user wanted approximate.
Transaction size
0 €
50.000 €
Do
Use two thumbs for genuine spans — a min and a max on the same scale, like a price filter.
Invoice amount
0 €
10.000 €
Pick exactly 1.247,50 € from a slider? Hard.
Don't
Don't use a slider for an exact value the user already knows. Invoice amounts, retry counts, fixed payment values — those want a number input.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | [number, number] | — | Current value (single thumb) or [min, max] tuple (range). Make it controlled; the slider is never internally uncommitted. |
| onValueChange | (value: number | [number, number]) => void | — | Fires on every move (drag, click-to-jump, keyboard step). Receives the value(s), not the event. |
| label | string | — | Question the slider is answering. Inter Tight Medium 16/20. Required unless aria-labelledby is set externally. |
| description | string | — | Optional supporting copy below the label. Inter Tight Regular 16/20 in text.tertiary. |
| min | number | 0 | Lowest value. Surfaces as the left range label when showRangeLabels is on. |
| max | number | 100 | Highest value. Surfaces as the right range label. |
| step | number | 1 | Snap increment. The thumb rounds to the nearest step; keyboard ←/→ moves by step, Page Up/Dn by 10× step. |
| showRangeLabels | boolean | true | Renders min and max labels below the track in tabular numerals. Disable when surrounding context already implies the scale. |
| rangeLabels | [string, string] | — | Override the default min/max labels with named tier strings — e.g. ['Low', 'High'] on a discrete risk slider. |
| showValue | boolean | false | Renders the current value to the right of the label, in foreground weight. Use when the user reads the live value as they drag. |
| format | (value: number) => string | — | Formatter used for the inline value display, range labels (when overridden), and aria-valuetext announcements. Pair with Intl for currency / percent. |
| error | string | — | Error message rendered below the range labels. Track + thumb flip to the danger tone. |
| disabled | boolean | false | Removes from the tab order and dims the track + thumb to 40% foreground. |
| name | string | — | Form field name. Required when the slider submits as part of a form. |