Inkbox

Component

Slider

A 6px lilac-gray track filled purple to the value, with the switch's white knob.

npx -y github:anwarlemu/inkbox-brand-cli#release add slider
Preview
250 emails
Range
9:00 to 17:00
States

Usage

Use to pick an approximate value in a range where the exact number matters less than its position (a daily send limit, a retry delay, a storage quota). Show the current value next to its label. Pass two values for a range.

Avoid

  • A slider for exact numbers (use Input)
  • A thick or colored knob
  • Ticks on every step

Exports: Slider. This component is a client component.

Dependencies: radix-ui.

Example

examples/slider.tsx
"use client";​import * as React from "react";​import { Label } from "@/components/ui/label";import { Slider } from "@/components/ui/slider";​export function Preview() {  const [limit, setLimit] = React.useState([250]);  return (    <div className="grid w-full max-w-100 gap-3">      <div className="flex items-center justify-between">        <Label id="limit-label">Daily send limit</Label>        <span className="text-body text-fg-muted">{limit[0]} emails</span>      </div>      <Slider aria-labelledby="limit-label" value={limit} onValueChange={setLimit} max={1000} step={50} />    </div>  );}​export function Range() {  const [hours, setHours] = React.useState([9, 17]);  return (    <div className="grid w-full max-w-100 gap-3">      <div className="flex items-center justify-between">        <Label id="hours-label">Calling hours</Label>        <span className="text-body text-fg-muted">          {hours[0]}:00 to {hours[1]}:00        </span>      </div>      <Slider aria-labelledby="hours-label" value={hours} onValueChange={setHours} min={0} max={24} step={1} minStepsBetweenThumbs={1} />    </div>  );}​export function States() {  return (    <div className="grid w-full max-w-100 gap-6">      <Slider aria-label="Disabled" defaultValue={[40]} disabled />      <div className="flex h-40 gap-8">        <Slider aria-label="Vertical" defaultValue={[60]} orientation="vertical" />        <Slider aria-label="Vertical range" defaultValue={[20, 70]} orientation="vertical" />      </div>    </div>  );}

Source

components/ui/slider.tsx
"use client";​import * as React from "react";import { Slider as SliderPrimitive } from "radix-ui";​import { cn } from "@/lib/utils";​// Extrapolated from the console's switch (Figma "Toggle"): a 6px pill track in the field's lilac-gray, filled with the// action purple up to the value, and a 20px white knob with the switch knob's soft drop. Under the pointer or keyboard// focus the knob takes a soft purple halo; it never animates its position (it follows the pointer). Pass two values for// a range.function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }: React.ComponentProps<typeof SliderPrimitive.Root>) {  const values = React.useMemo(() => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]), [value, defaultValue, min, max]);  return (    <SliderPrimitive.Root      data-slot="slider"      defaultValue={defaultValue}      value={value}      min={min}      max={max}      className={cn(        "relative flex w-full touch-none items-center select-none data-disabled:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",        className,      )}      {...props}    >      <SliderPrimitive.Track        data-slot="slider-track"        className="relative grow overflow-hidden rounded-pill bg-line-field data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"      >        <SliderPrimitive.Range data-slot="slider-range" className="absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full" />      </SliderPrimitive.Track>      {Array.from({ length: values.length }, (_, index) => (        <SliderPrimitive.Thumb          data-slot="slider-thumb"          key={index}          aria-label={props["aria-label"] ? undefined : values.length > 1 ? (index === 0 ? "Minimum" : "Maximum") : undefined}          className="block size-5 shrink-0 cursor-grab rounded-pill bg-surface shadow-knob outline-none transition-[box-shadow] duration-150 ease-out hover:shadow-[var(--ds-shadow-knob),0_0_0_4px_var(--ds-color-ring-tint)] focus-visible:shadow-[var(--ds-shadow-knob),0_0_0_4px_var(--ds-color-ring-tint)] active:cursor-grabbing disabled:pointer-events-none motion-reduce:transition-none"        />      ))}    </SliderPrimitive.Root>  );}​export { Slider };

Reference: Extrapolated from the Inkbox console (Figma 427:2715): the switch's knob and purple, the field's lilac-gray.