Components
Progress
Emitted progress in two shapes: a continuous linear bar (a native <progress>, so the fill needs no inline style; also covers countdowns) and discrete step-of-N dots for wizards.
Installation
npx @glasskit-ui/cli add progressInstall 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/progress.tsximport type { ReactNode } from "react";import { cn, stringLabel } from "../lib/utils";/** * <Progress> — emitted progress, two shapes: * - "linear" a continuous bar (also covers countdown: feed a decreasing * value and a time label). Uses a native <progress>, so the * dynamic fill needs no inline style. * - "step" discrete step-of-N dots (wizard / pinch-advance). * * `value` is clamped to [0, max]. For "step", value = completed steps. */export function Progress({ value, max = 100, variant = "linear", label, className,}: { value: number; max?: number; variant?: "linear" | "step"; /** Optional caption shown with the bar (e.g. "3 of 5", "0:42 left"). */ label?: ReactNode; className?: string;}) { const clamped = Math.max(0, Math.min(value, max)); if (variant === "step") { // Steps must be a sane integer count — a fractional or negative max // would make Array.from misrender the dots. const steps = Math.max(0, Math.floor(max)); return ( <div className={cn("gk-steps", className)} role="progressbar" aria-valuenow={clamped} aria-valuemin={0} aria-valuemax={steps} aria-label={stringLabel(label)} > {Array.from({ length: steps }, (_, i) => ( <span key={i} className={cn("gk-step", i < clamped && "gk-step--on")} /> ))} </div> ); } return ( <div className={cn("gk-progress", className)}> <progress className="gk-progress__el" value={clamped} max={max} aria-label={stringLabel(label)} /> {label != null ? ( <div className="gk-progress__meta t-caption">{label}</div> ) : null} </div> );}Usage
<Progress value={64} label="Downloading · 64%" /><Progress variant="step" value={2} max={4} />Props
Prop
Type
Meter
A bounded ring gauge for a level (battery, signal, effort) — distinct from Progress, which tracks task completion. The arc fills via an SVG stroke-dashoffset; value is clamped to [0, max].
Readout
A single-value complication: label + value + optional unit. The glanceable archetype — one number legible in a 1–2 second glance, with tabular numerals.