GlassKit UI
Components

Slider

A continuous level control (volume, brightness — the quick controls). A native range tinted with accent-color; arrow keys / Neural-Band pinch-twist adjust it. Controlled via value + onChange.

Volume · 60

Arrow keys adjust while focused

600 × 600 · live

Installation

npx @glasskit-ui/cli add slider

Install the SDK (it provides GlassViewport, useDpad and the stylesheet), then copy these files into your project:

npm install @glasskit-ui/react
// components/lib/utils.tsexport type ClassValue = string | number | null | undefined | false;/** * Join truthy class names. Dependency-free on purpose: the lens components * style via bespoke semantic classes (no conflicting Tailwind utilities to * de-dupe), so this needs no clsx/tailwind-merge and resolves from anywhere * the registry is vendored. */export function cn(...inputs: ClassValue[]): string {  return inputs.filter(Boolean).join(" ");}/** * Accessible name from a free-form `label` prop: the label itself when it's a * plain string, otherwise undefined (a ReactNode can't become an aria-label). */export function stringLabel(label: unknown): string | undefined {  return typeof label === "string" ? label : undefined;}
// components/glasskit/slider.tsximport type { ReactNode } from "react";import { cn, stringLabel } from "../lib/utils";/** * <Slider> — a continuous level control (volume, brightness — the quick * controls). A native range so the fill + thumb need no inline style * (`accent-color` tints them); arrow keys / Neural-Band pinch-twist adjust it. * Controlled via `value` + `onChange`. Omit `onChange` for a read-only * display — the slider then leaves the D-pad focus order (`readOnly` is * meaningless on a range input; an adjustable-looking dead control is worse * than a plain level display). */export function Slider({  value,  min = 0,  max = 100,  label,  icon,  onChange,  className,}: {  value: number;  min?: number;  max?: number;  /** a11y / caption label. */  label?: ReactNode;  /** Leading glyph — typically a <GlowIcon> (volume / brightness). */  icon?: ReactNode;  onChange?: (next: number) => void;  className?: string;}) {  return (    <div className={cn("gk-slider", className)}>      {icon != null ? <span className="gk-slider__icon">{icon}</span> : null}      <input        type="range"        className={cn("gk-slider__input", onChange != null && "focusable")}        min={min}        max={max}        value={value}        // readOnly only suppresses React's controlled-input warning (the        // attribute itself is meaningless on type="range") — the real        // read-only mechanism is leaving the focus order above.        readOnly={onChange == null}        tabIndex={onChange == null ? -1 : undefined}        aria-readonly={onChange == null ? true : undefined}        aria-label={stringLabel(label)}        onChange={          onChange ? (e) => onChange(Number(e.currentTarget.value)) : undefined        }      />      {label != null ? (        <span className="gk-slider__label t-caption">{label}</span>      ) : null}    </div>  );}

Usage

<Slider  value={volume} onChange={setVolume}  icon={<GlowIcon size="md"><VolumeIcon /></GlowIcon>}  label="Volume"/>

Props

Prop

Type