Components
Clock
The home time / date complication: a big tabular time, a quieter date, and an optional meta line (weather, alarm). Pass preformatted strings — you own the locale.
Installation
npx @glasskit-ui/cli add clockInstall 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/clock.tsx"use client";import { useEffect, useMemo, useState, type ReactNode } from "react";import { cn } from "../lib/utils";/** * <Clock> — the home time/date complication: a big tabular time and a quieter * date line. Self-ticking by default (minute-aligned — no per-second renders); * pass `time` to control it instead (the prop always wins, and you own the * formatting). In live mode an omitted `date` auto-formats too. Optional * `meta` for a trailing line (weather, alarm). */export function Clock({ time, date, meta, locale, hour12, className,}: { /** Controlled, preformatted time. Omit to tick live. */ time?: ReactNode; /** Date line. In live mode, omitting it shows the live date. */ date?: ReactNode; meta?: ReactNode; /** BCP 47 locale for live formatting (default: the device locale). */ locale?: string; /** Force 12/24-hour live time (default: the locale's convention). */ hour12?: boolean; className?: string;}) { const live = time == null; // null until mounted → server and first client render agree on the // placeholder, then the first tick swaps in the real time (hydration-safe). const [now, setNow] = useState<Date | null>(null); useEffect(() => { if (!live) return; let timer: ReturnType<typeof setTimeout>; const tick = () => { const d = new Date(); setNow(d); // Re-tick just past the next minute boundary. timer = setTimeout( tick, 60_000 - (d.getSeconds() * 1000 + d.getMilliseconds()) + 50, ); }; tick(); return () => clearTimeout(timer); }, [live]); // Intl formatters are expensive to construct — build once per locale, not // per minute tick. const timeFormat = useMemo( () => new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "2-digit", hour12, }), [locale, hour12], ); const dateFormat = useMemo( () => new Intl.DateTimeFormat(locale, { weekday: "long", month: "long", day: "numeric", }), [locale], ); const liveTime = now ? timeFormat.format(now) : "--:--"; const liveDate = now ? dateFormat.format(now) : null; const shownTime = live ? liveTime : time; const shownDate = date ?? (live ? liveDate : null); return ( <div className={cn("gk-clock", className)}> <span className="gk-clock__time">{shownTime}</span> {shownDate != null ? ( <span className="gk-clock__date t-body">{shownDate}</span> ) : null} {meta != null ? ( <span className="gk-clock__meta t-caption">{meta}</span> ) : null} </div> );}Usage
// self-ticking, device locale<Clock meta="72° · Sunny" />// or controlled — you own the formatting<Clock time="9:41" date="Tuesday, June 9" />Props
Prop
Type
Badge
A small count or status pill. Pure display — hairline by default, accent emphasis for the one thing that should draw the eye (the accent gradient, for the one thing that needs the eye).
Cue
A caption / hint line: what to do next, or a transient status. Dim by default; set emphasis='accent' for a live state. No glow on body text.