Primitives
Screen
The on-lens layout shell: a status region, a centered stage for the one task, and a cue region, with safe margins that keep the surface mostly black.
Installation
npx @glasskit-ui/cli add screenInstall the SDK (it provides GlassViewport, useDpad and the stylesheet), then copy these files into your project:
npm install @glasskit-ui/react// components/lib/utils.tsimport { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";export type { ClassValue };/** * Merge class names the shadcn way: clsx joins conditionals, tailwind-merge * de-dupes conflicting Tailwind utilities so a consumer's `className` override * wins (e.g. passing `px-2` beats the component's `px-6`). Lens components are * Tailwind utilities + `--gk-*` tokens, so this de-dupe matters. */export function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs));}/** * 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/screen.tsximport type { ReactNode } from "react";import { cn } from "../lib/utils";/** * <Screen> — the on-lens layout shell: a status region (block-start), a * centered stage (the one task), and the cue region (block-end). Renders * *inside* a <GlassViewport>; it does not own the 600×600 surface. * * The `cue` is the screen's one narration line: pass the hint text and Screen * renders it as a polite `role="status"` live region (announces updates to * screen readers + the glasses TTS without stealing focus). Set `cueLive` for a * live/active state (accent). One cue per screen keeps the live region sane. * * Keeps ≥50–60% of the surface pure black (apple-feel §3) by reserving the * center for a single readout/action and pinning chrome to the edges. */export function Screen({ status, cue, cueLive = false, children, className,}: { /** Optional top region (e.g. a Heading). System status is OS chrome, not an * app component, so most app screens leave this empty. */ status?: ReactNode; /** The bottom narration line: the hint or transient status for this view. * Rendered as a polite `role="status"` live region. */ cue?: ReactNode; /** Accent the cue for a live/active state ("Recording", "Connected"). */ cueLive?: boolean; /** The stage: the one task for this view. */ children: ReactNode; className?: string;}) { return ( <div className={cn("flex h-full flex-col gap-[14px] p-[22px]", className)}> {status ? ( <div data-screen-status className="flex-none"> {status} </div> ) : null} <div data-screen-stage className="flex min-h-0 flex-1 flex-col items-center justify-center gap-5 text-center" > {children} </div> {cue != null ? ( <div data-screen-cue role="status" className={cn( "t-caption flex flex-none items-center justify-center gap-2", cueLive ? "text-primary" : "text-foreground-faint", )} > {cue} </div> ) : null} </div> );}Usage
<Screen cue="One task per view"> <Readout label="Pace" value="8'42" unit="/mi" /></Screen>Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The stage content. |
status | ReactNode | — | Optional top region. |
cue | ReactNode | — | Bottom narration line; rendered as a polite role=status live region. |
cueLive | boolean | false | Accent the cue for a live/active state. |
className | string | — | Extra classes. |
Launcher
The app grid: the entry screen for a multi-app surface. Two columns of D-pad-focusable cards on gradient icon plates. Keep it to about six apps so the whole grid is one glance.
Grid
An aligned, vertically-scrolling multi-column layout: every cell shares the same track, so rows and columns line up. Drop any children in; it scrolls vertically and keeps a D-pad-focused child in view.