forty-cdk
llms.txt

Primitives

Slider

A draggable thumb that picks a numeric value along a track.

forty-cdk/slider WAI-ARIA APG

Drag a thumb, or focus it and press the arrow keys. Home and End jump to the bounds, and each thumb carries its own data-index.

40

A single primitive supports single, range, and multi-thumb sliders: the shape comes from value's array length and how many [forSliderThumb] you render. Implements FormValueControl<readonly number[]> from @angular/forms/signals.

Anatomy

<div forSlider [(value)]="value" [min]="0" [max]="100" [step]="1">
  <span forSliderTrack>
    <span forSliderRange></span>
    <span forSliderThumb [index]="0" ariaLabel="Volume"></span>
  </span>
</div>

Examples

Stepped

step sets the granularity values snap to and the amount each arrow-key press moves. Here step is 10, so values snap to 0, 10, 20…; PageUp / PageDown move by 10× this step.

40

Range (two thumbs)

The value model is a readonly number[]; two forSliderThumb pieces, one per index, make a range. Each thumb's aria-valuemin / aria-valuemax squeeze to its neighbor so the thumbs can't cross, and minStepsBetweenThumbs keeps a minimum gap in step units.

200–800

Vertical orientation

orientation='vertical' reflects data-orientation on every piece and sets aria-orientation on the thumb. The exposed fractions are unchanged, and the consumer paints them along the Y axis. ArrowUp increases the value and ArrowDown decreases it.

40

Inverted

inverted flips the value-to-position mapping: in horizontal LTR, max sits on the left. The flip is baked into the exposed fractions, so the same CSS paints both ways. Keyboard semantics are unchanged: ArrowRight / ArrowUp still move toward max.

40

Signal Forms

[forSlider] implements FormValueControl<readonly number[]>. Pair with [formField] for auto-wiring with @angular/forms/signals:

<div forSlider [formField]="form.opacity">…</div>

For native <form> submit, set [name] and the directive mirrors value() into N <input type="hidden"> siblings (one per thumb). data-touched / data-dirty / data-pending / data-invalid are reflected on the host as boolean data-* attributes (present when true, absent otherwise).

API

ForSlider

PropertyTypeDescription
value
model
Two-way bindable value array. Required by FormValueControl<readonly number[]>. Output. Emits on drag, keyboard, and track click.
Default: —
min
input
Numeric minimum.
Default: 0
max
input
Numeric maximum.
Default: 100
step
input
Discrete value increment for arrows + drag.
Default: 1
stepMultiplier
input
Multiplier over step for PageUp / PageDown (configurable via provideForSliderDefaults).
Default: 10
orientation
input
'horizontal' or 'vertical'.
Default: 'horizontal'
dir
input
'ltr' or 'rtl'. RTL flips horizontal pointer mapping and ArrowLeft/ArrowRight semantics.
Default: 'ltr'
inverted
input
Visually flips the value-to-position mapping (e.g. max on the left in horizontal LTR). Keyboard Up/Right (LTR) still moves toward max regardless.
Default: false
minStepsBetweenThumbs
input
Multi-thumb only: minimum gap between adjacent thumbs in step units.
Default: 0
disabled
input
Disables all interaction.
Default: false
readonly
input
Allows focus + announcement, blocks updates.
Default: false
name
input
If non-empty, mirrors value() into N <input type="hidden"> siblings for native form submit.
Default: ''
valueCommit
—
Output. Fires once with the final value array at the trailing edge of a value-changing interaction: on pointerup / pointercancel after a drag, or on keyup after one or more keyboard adjustments.
Default: —
touchedChange
—
Output. Fires once, the first time the slider is touched (drag end, or focus leaving the slider region).
Default: —
touch
—
Output. Signal Forms touch notification. Fires on every touch-producing interaction (drag end, focus leaving the region), not only the first.
Default: —

(valueChange) (from model<readonly number[]>) fires only on internal updates (drag, keyboard, track click). It stays silent on consumer writes via [(value)].

(valueCommit) stays silent when the interaction did not actually change the value (e.g. press + release without movement, or arrow at the extreme).

Data attributeValues
data-orientationhorizontal | vertical
data-disabledpresent | absent
data-readonlypresent | absent
data-touchedpresent | absent
data-dirtypresent | absent
data-pendingpresent | absent
data-invalidpresent | absent

ForSliderTrack

The clickable track surface. PointerDown anywhere on the track that isn't a thumb finds the nearest thumb, jumps it to that position, focuses it, and starts a drag.

Data attributeValues
data-orientationhorizontal | vertical
data-disabledpresent | absent

ForSliderRange

Optional decorative band between the lowest and highest thumb (single-thumb: 0 → thumb; multi-thumb: between the outermost thumbs). Exposes --for-slider-range-start, --for-slider-range-end, and --for-slider-range-size for sizing.

Data attributeValues
data-orientationhorizontal | vertical
data-disabledpresent | absent

ForSliderThumb

PropertyTypeDescription
index
input.required
0-based position in the parent slider's value() array. Pass the loop index when rendering N thumbs.
Default: —
ariaLabel
input
Accessible name for the thumb, emitted as aria-label only when non-empty. A consumer-set static aria-label wins.
Default: null
valueText
input
Human-readable value override (e.g. $1,200 instead of 1200), mirrored as aria-valuetext only when non-empty.
Default: ''
Data attributeValues
data-orientationhorizontal | vertical
data-disabledpresent | absent
data-readonlypresent | absent
data-index0-based thumb index

Keyboard

Focus a thumb, then:

KeyAction
ArrowRight (LTR) / ArrowLeft (RTL) / ArrowUpIncrease by step.
ArrowLeft (LTR) / ArrowRight (RTL) / ArrowDownDecrease by step.
PageUpIncrease by step × stepMultiplier.
PageDownDecrease by step × stepMultiplier.
HomeSet to min.
EndSet to max.

inverted swaps "increase" / "decrease" on every key. Disabled and readonly thumbs are no-ops.

Values live on the min ± k·step grid. A thumb already on the grid travels the full amount (step, or step × stepMultiplier for the page keys); a thumb off the grid lands on the adjacent grid point in the direction of travel and the page multiplier is discarded, matching the platform's HTMLInputElement.stepUp() / stepDown(). So ArrowRight from 23 with [step]="10" gives 30 and ArrowLeft gives 20. The first jump is never oversized. Pointer drags are unaffected: they snap to the nearest grid point, since a drag has no direction of travel.

Accessibility

Implements the WAI-ARIA Slider pattern (single thumb) and the WAI-ARIA Slider (Multi-Thumb) pattern (range / N thumbs).

  • role="slider" on each thumb with aria-valuemin, aria-valuemax, aria-valuenow, optional aria-valuetext, and aria-orientation.
  • Multi-thumb non-passing: each thumb's aria-valuemin / aria-valuemax automatically squeeze to its neighbors' values (offset by the minStepsBetweenThumbs gap), so the announced range is exactly the range the thumb can reach, matching the APG multi-thumb guidance.
  • The root has role="group" and dir="rtl" mirrored when dir()==='rtl', so screen readers and CSS layout agree.
  • disabled thumbs receive tabindex="-1" and aria-disabled="true".
  • aria-readonly belongs on the thumb, not the root. WAI-ARIA supports it on role="slider" but not on role="group", so each thumb carries aria-readonly="true" while the root reflects the data-readonly styling hook only.
  • Provide [ariaLabel] on every thumb. Even single-thumb sliders benefit from explicit naming. The directive does not synthesize a label. When the name is already visible in the DOM, write a native aria-labelledby on the thumb instead; the directive leaves that attribute alone.
  • A degenerate configuration (min greater than max, or a non-positive step) leaves the slider inoperable and is dev-guarded by a console.warn in development builds.

Styling

forty-cdk ships no styles: put your own class on each piece and key your CSS off the data-* attributes listed under API, not off the for* selectors (Styling forty-cdk explains why).

CSS custom properties

PropertyMeaning
--for-slider-thumb-positionOn [forSliderThumb]. Fraction [0, 1], already accounting for inverted. The thumb's position along the track.
--for-slider-range-startOn [forSliderRange]. Lowest fraction [0, 1] (single: pinned to the closer edge; multi: smallest thumb).
--for-slider-range-endOn [forSliderRange]. Highest fraction [0, 1].
--for-slider-range-sizeOn [forSliderRange]. end - start. Useful for width / height.

Pair with data-orientation on every piece to pick the right axis from CSS.

.sl-thumb {
  inset-inline-start: calc(var(--for-slider-thumb-position) * 100%);
}

.sl-range {
  inset-inline-start: calc(var(--for-slider-range-start) * 100%);
  inline-size: calc(var(--for-slider-range-size) * 100%);
}

.sl-thumb[data-disabled] {
  opacity: 0.5;
}

Wrapping in a design system

Wrapping form primitives documents both supported wrapper patterns: hostDirectives with the exported FOR_SLIDER_HOST_DIRECTIVE_INPUTS / FOR_SLIDER_HOST_DIRECTIVE_OUTPUTS name tuples, and subclassing.