GlassKit UI
Components

Tabs

A top-level tab strip (the home's quick-controls | home | apps pager). Each tab is D-pad-focusable; the active one gets an accent underline. Controlled via value + onChange.

Active tabHome

Home

600 × 600 · live

Installation

npx @glasskit-ui/cli add tabs

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/tabs.tsximport type { ReactNode } from "react";import { cn } from "../lib/utils";export type TabItem = { id: string; label: ReactNode };/** * <Tabs> — a top-level tab strip (the home's quick-controls | home | apps * pager). Anchor it at the top of the view — pass it as <Screen>'s `status` * slot — so context stays glanceable above the content it switches. * Selected = accent underline; focused = the system focus ring (two distinct * affordances). Controlled via `value` + `onChange`. RTL-safe (logical * layout). * * Deliberate ARIA deviation: no `aria-controls`/`tabpanel` wiring — on the * lens a tab switch swaps the whole 600×600 screen, so there is no co-rendered * panel to point at. */export function Tabs({  items,  value,  onChange,  className,}: {  items: TabItem[];  value: string;  onChange?: (id: string) => void;  className?: string;}) {  return (    <div className={cn("gk-tabs", className)} role="tablist">      {items.map((t) => {        const on = t.id === value;        return (          <button            key={t.id}            type="button"            role="tab"            aria-selected={on}            onClick={onChange ? () => onChange(t.id) : undefined}            className={cn("focusable gk-tab t-body", on && "gk-tab--on")}          >            {t.label}          </button>        );      })}    </div>  );}

Usage

<Tabs  value={tab}  onChange={setTab}  items={[    { id: "controls", label: "Controls" },    { id: "home", label: "Home" },    { id: "apps", label: "Apps" },  ]}/>

Props

Prop

Type