Components
Stepper
Adjust a value in discrete steps (glasses have no fine slider). The − and + are D-pad-focusable; bounds disable the ends. Controlled via value + onChange.
Installation
npx @glasskit-ui/cli add stepperInstall 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/stepper.tsximport type { ReactNode } from "react";import { cn } from "../lib/utils";/** * <Stepper> — adjust a value in discrete steps (glasses have no fine slider). * The − and + are D-pad-focusable buttons; useDpad walks them and activates on * Enter/Space. Controlled: pass `value` + `onChange`. Bounds disable the ends. */export function Stepper({ value, onChange, min, max, step = 1, label, unit, className,}: { value: number; onChange?: (next: number) => void; min?: number; max?: number; step?: number; label?: ReactNode; unit?: ReactNode; className?: string;}) { const clamp = (n: number) => Math.min(max ?? Infinity, Math.max(min ?? -Infinity, n)); const atMin = min != null && value <= min; const atMax = max != null && value >= max; const name = typeof label === "string" ? label : "value"; return ( <div className={cn("gk-stepper", className)}> {label != null ? ( <span className="gk-stepper__label t-caption">{label}</span> ) : null} <div className="gk-stepper__row"> <button type="button" className="focusable gk-stepper__btn" onClick={onChange ? () => onChange(clamp(value - step)) : undefined} disabled={atMin} aria-label={`Decrease ${name}`} > − </button> <span className="gk-stepper__value t-readout"> {value} {unit != null ? ( <span className="gk-stepper__unit"> {unit}</span> ) : null} </span> <button type="button" className="focusable gk-stepper__btn" onClick={onChange ? () => onChange(clamp(value + step)) : undefined} disabled={atMax} aria-label={`Increase ${name}`} > + </button> </div> </div> );}Usage
<Stepper label="Brightness" value={value} min={1} max={5} onChange={setValue}/>Props
Prop
Type
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.
Toggle
A binary switch, D-pad-focusable. Controlled via checked + onChange. The knob slides on a logical margin, so it mirrors correctly under RTL.