Components
Confirm
A decision screen: a prompt plus a two-button action bar. Drop it into a Screen stage; useDpad seeds focus on the primary action.
Installation
npx @glasskit-ui/cli add confirmInstall 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/button.tsximport type { ReactNode } from "react";import { cn } from "../lib/utils";/** * <Button> — a D-pad-focusable action. Renders a real <button> carrying * the `focusable` class, so `useDpad()` includes it in spatial navigation * and activates it on Enter/Space (the hook calls `.click()`, which fires * `onClick`). Edge + focus ring come from the additive `.focusable` recipe; * `primary` brightens the edge — no fills (apple-feel §9). */export function Button({ children, variant = "secondary", icon, disabled, onClick, type = "button", initialFocus = false, className,}: { children: ReactNode; variant?: "primary" | "secondary"; /** Optional leading glyph — typically a <GlowIcon>. */ icon?: ReactNode; disabled?: boolean; onClick?: () => void; type?: "button" | "submit" | "reset"; /** Seed the D-pad ring here when the screen mounts (`data-autofocus`). */ initialFocus?: boolean; className?: string;}) { return ( <button type={type} disabled={disabled} onClick={onClick} data-autofocus={initialFocus || undefined} className={cn( "focusable gk-btn t-body", variant === "primary" && "gk-btn--primary", className, )} > {icon} {children} </button> );}// components/glasskit/confirm.tsx"use client";import type { ReactNode } from "react";import { FocusScope } from "@glasskit-ui/react";import { cn } from "../lib/utils";import { Button } from "./button";/** * <Confirm> — a decision screen: a prompt + a two-button action bar (confirm / * cancel). Drop it into a <Screen> stage. Focus seeds on the primary action; * set `destructive` for irreversible decisions and the ring seeds on cancel * instead — a blind pinch must never destroy anything. Rendered inside a * FocusScope so the ring can't wander off the decision. */export function Confirm({ title, message, confirmLabel = "Confirm", cancelLabel = "Cancel", destructive = false, onConfirm, onCancel, className,}: { title?: ReactNode; message?: ReactNode; confirmLabel?: ReactNode; cancelLabel?: ReactNode; /** Irreversible action — seed the D-pad ring on cancel, not confirm. */ destructive?: boolean; onConfirm?: () => void; onCancel?: () => void; className?: string;}) { return ( <FocusScope> <div className={cn("gk-confirm", className)}> {title != null ? ( <p className="gk-confirm__title t-title">{title}</p> ) : null} {message != null ? ( <p className="gk-confirm__message t-body">{message}</p> ) : null} <div className="gk-confirm__actions"> <Button variant="primary" initialFocus={!destructive} onClick={onConfirm} > {confirmLabel} </Button> <Button initialFocus={destructive} onClick={onCancel}> {cancelLabel} </Button> </div> </div> </FocusScope> );}Usage
<Confirm title="End workout?" message="Your route so far will be saved." confirmLabel="End" onConfirm={end} onCancel={dismiss}/>Props
Prop
Type
Button
A D-pad-focusable action. Renders a real <button> with the focusable class, so useDpad walks it and activates it on Enter/Space. Edge + focus ring are emitted light, never fills.
List
A vertical stack of focusable rows (watchOS list spirit). Keep it short — a glanceable HUD caps at 3–5 rows. Compose List with ListRow (leading glyph, label, trailing value).