GlassKit UI
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.

Zoom
2 ×

Focus − / + and press Enter

600 × 600 · live

Installation

npx @glasskit-ui/cli add stepper

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/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