Primitives
PermissionPrompt
An explicit access request (sensors, location, camera, mic) that MRBD apps must ask for before use. A gradient-plate icon, a clear title, the reason, and allow / deny actions.
Installation
npx @glasskit-ui/cli add permission-promptInstall 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/permission-prompt.tsx"use client";import type { ReactNode } from "react";import { FocusScope } from "@glasskit-ui/react";import { cn } from "../lib/utils";/** * <PermissionPrompt> — an explicit access request (sensors, location, camera, * mic), which MRBD apps must ask for before use. A gradient-plate icon, a clear * title, the reason, and allow / deny actions. Drop it into a <Screen> stage. * It's a dialog, so it renders inside a FocusScope: the D-pad ring is * contained to the prompt until it unmounts. */export function PermissionPrompt({ icon, title, children, actions, className,}: { /** A gradient-plate <Icon> for the capability. */ icon?: ReactNode; title: ReactNode; /** Why the app needs it (keep it short + honest). */ children?: ReactNode; /** Allow / deny controls. */ actions?: ReactNode; className?: string;}) { return ( <FocusScope> <div className={cn( "flex flex-col items-center gap-[14px] text-center", className, )} role="dialog" aria-label={typeof title === "string" ? title : "Permission"} > {icon != null ? <span>{icon}</span> : null} <span className="t-title font-bold">{title}</span> {children != null ? ( <p className="t-body max-w-[30ch] text-muted-foreground">{children}</p> ) : null} {actions != null ? ( <div className="flex gap-3 mt-2">{actions}</div> ) : null} </div> </FocusScope> );}Usage
<PermissionPrompt icon={<Icon size="lg" plate tone="cyan"><LocationIcon /></Icon>} title="Use your location?" actions={<><Button onClick={deny}>Not now</Button> <Button variant="primary" onClick={allow}>Allow</Button></>}> Maps needs your location for walking directions.</PermissionPrompt>Props
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | — | A gradient-plate Icon. |
title | ReactNode | — | The request. |
children | ReactNode | — | Why the app needs it. |
actions | ReactNode | — | Allow / deny controls. |
Avatar
A contact / sender avatar: a photo when you have one, else initials on a gradient plate. The building block for notifications, chats, and calls.
ComposeFlow
The working text-entry recipe: a focusable field that opens a picker of choices when activated; choosing writes back and returns. The picker rides history, so the back gesture closes it. It stands in for the on-device dictation the platform does not expose yet.