# Activity Indicator (/docs/activity-indicator) ## Import [#import] ```tsx import { ActivityIndicator } from '@/components/activity-indicator' ``` ## Anatomy [#anatomy] The module exposes one namespace object. **`ActivityIndicator`** is callable as the root (same element as **`ActivityIndicator.Root`**) and renders an animated **`svg`**. **`ActivityIndicator.Icon`** is the static, non-animated glyph for when you only need the spokes (for example inside a button). ```tsx ``` * **ActivityIndicator / ActivityIndicator.Root**: Animated spinner. Drive it with **`isLoading`**. On load it plays a single 360° spin, then loops a brightness wave around the eight spokes. Customize or disable motion with **`animation`**. * **ActivityIndicator.Icon**: Static spokes only — no spin, no loop. Useful as a glyph. ## Sizes [#sizes] The indicator is always square. **`size`** maps to fixed pixel dimensions (mirrors **`activityIndicatorVariants`** and **`ACTIVITY_INDICATOR_SIZE`**). | `size` | Dimensions | | ------ | ----------------- | | `sm` | 20 × 20 | | `md` | 28 × 28 (default) | | `lg` | 44 × 44 | ```tsx ``` ## Usage [#usage] ### Loading state [#loading-state] **`isLoading`** controls the animation. It defaults to **`true`**, so a bare **``** spins. When **`false`**, the spokes freeze on a coherent trail instead of unmounting — conditionally render the element yourself if you want it to disappear. ```tsx ``` ### Color [#color] The icon uses **`currentColor`**, so set the color with text utilities or any color class. ```tsx ``` ### Accessibility [#accessibility] The root renders with **`role="status"`**, **`aria-busy`** bound to **`isLoading`**, and a default **`aria-label`** of **`"Loading"`**. Override it for context. ```tsx ``` ### Inside a button [#inside-a-button] Swap in the static **`ActivityIndicator.Icon`** when you only want the glyph, or the animated root for an in-flight action. ```tsx ``` ## Animation [#animation] The default animation is two parts, matching the visionOS activity indicator: 1. **`spin`** — a one-time 360° rotation of the whole icon when loading starts. 2. **`fade`** — a looping opacity wave that travels around the spokes, creating the perpetual loading motion. Both are exposed through the **`animation`** prop, following the same `boolean | object` pattern as **`PressableFeedback`**. Pass **`true`** (default) for stock motion, **`false`** to disable all motion, or an object to tune each part independently. Setting **`spin`** or **`fade`** to **`false`** disables just that part. ```tsx // Disable the intro spin, keep the looping wave // Faster loop, shorter trail // Custom spin curve // Static — no motion at all ``` ### Reduced motion [#reduced-motion] When the user prefers reduced motion (**`useReducedMotion`**), both the spin and the fade loop are disabled automatically and the indicator renders static — no extra wiring needed. ### Animation helpers [#animation-helpers] The same primitives the component uses are exported for custom renderings or design tools: ```tsx import { resolveActivityIndicatorAnimation, getRectOpacityKeyframes, getRectStaticOpacity, ACTIVITY_INDICATOR_DEFAULT_SPIN_TRANSITION, ACTIVITY_INDICATOR_DEFAULT_FADE_TRANSITION, } from '@/components/activity-indicator' ``` * **`resolveActivityIndicatorAnimation(animation, reducedMotion)`** — normalizes the **`animation`** prop into concrete `spin` / `fade` configs (or `null` when disabled). * **`getRectOpacityKeyframes(phase, min, max)`** — the looping opacity keyframes for a spoke at a given `phase`. * **`getRectStaticOpacity(phase, min, max)`** — the frozen opacity for a spoke when not animating. ## Example [#example] ```tsx import { useState } from 'react' import { ActivityIndicator } from '@/components/activity-indicator' export default function ActivityIndicatorExample() { const [isLoading, setIsLoading] = useState(true) return (
) } ``` ## API Reference [#api-reference] ### ActivityIndicator (`ActivityIndicator.Root`) [#activityindicator-activityindicatorroot] The callable **`ActivityIndicator`** is **`ActivityIndicator.Root`**. It renders a Motion **`svg`**; besides the table below it accepts standard **`SVGMotionProps`** (ref, className, style, and other SVG / Motion attributes). ### ActivityIndicator.Icon [#activityindicatoricon] Static, non-animated spokes. Accepts **`size`** plus standard **`SVGMotionProps`**. ### ActivityIndicatorAnimation [#activityindicatoranimation] ### ActivityIndicatorSpinAnimation [#activityindicatorspinanimation] ### ActivityIndicatorFadeAnimation [#activityindicatorfadeanimation] ### ActivityIndicatorProps [#activityindicatorprops] Type alias for **`ActivityIndicatorRootProps`**. # Button (/docs/button) ## Import [#import] ```tsx import { Button, buttonVariants } from '@/components/button' ``` ## Anatomy [#anatomy] The module exposes one namespace object. **`Button`** is callable as the root control (same element as **`Button.Root`**). Use **`Button.Label`** for text inside a button (applied automatically for string children). Use **`Button.Group`** to wrap sibling buttons with shared spacing and rounded chrome. ```tsx ``` Equivalently, name the root explicitly: ```tsx Back Continue ``` * **Button / Button.Root**: Renders a ` ``` ### Labels [#labels] Plain string children are wrapped in **`Button.Label`** so typography stays consistent. For mixed content (icon + text), compose **`Button.Label`** explicitly. ```tsx ``` Override the label element or classes when needed: ```tsx ``` ### Render as another element [#render-as-another-element] Use the root **`render`** prop when the interactive surface should be another component, such as a router **`Link`**. ```tsx import { Link } from '@tanstack/react-router' ``` Rendered components must forward their ref and spread received props onto the DOM element that should receive button styles and events. ### Click sound [#click-sound] By default the root plays a grid-select sound on **`onMouseUp`**. Pass **`isSoundDisabled`** to silence it (for example when **`PressableFeedback`** already owns the interaction). ```tsx ``` ### Reusing styles without the component [#reusing-styles-without-the-component] Use **`buttonVariants`** when you need the same classes on a non-button element (custom primitives, links, or tests). ```tsx import { buttonVariants } from '@/components/button' Styled like a link button ``` ## Example [#example] ```tsx import { Button } from '@/components/button' export default function ButtonExample() { return ( ) } ``` ## API Reference [#api-reference] ### Button (`Button.Root`) [#button-buttonroot] The callable **`Button`** component is **`Button.Root`**. Beyond the table below, the root accepts normal **` ``` * **Cursor / Cursor.Root**: Context-local pointer engine. It owns pointer listeners, target resolution, native cursor hiding, and the spring MotionValues used by the pointer visual. * **Cursor.Pointer**: Portal-rendered Motion element. By default it is a 16px translucent dot that morphs into the active snap target's measured border box. * **Cursor.Snap**: Registers one explicit target element with the cursor engine. It owns spring-smoothed parallax values and exposes them to descendants through context and CSS variables. * **Cursor.SnapTarget**: Optional visual layer that consumes the nearest snap's parallax MotionValues. Use **`factor`** to scale the layer movement. * **useCursorSnapshot**: Hook for reading semantic cursor state such as `isActive`, `isPressed`, `isSnapped`, and `activeTargetId`. ## Usage [#usage] ### Root and pointer [#root-and-pointer] Wrap the interactive Vision UI shell with **`Cursor`** and mount **`Cursor.Pointer`** explicitly. ```tsx import { Cursor } from '@/components/cursor' export function AppShell({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` The root hides the native cursor only while the engine is enabled, pointer activity is inside the root, and at least one **`Cursor.Pointer`** is mounted. If **`Cursor.Pointer`** is omitted, the native cursor remains visible. ### Snap target [#snap-target] Use **`Cursor.Snap`** around each region that should become magnetic. Registration is explicit; native interactive descendants are not auto-detected. ```tsx ``` The active target is resolved by checking registered target rects. When registered regions overlap, the deepest DOM target wins, then the smallest area, then the latest registration. ### Morphing pointer [#morphing-pointer] The default pointer is a single morphing material. In free mode it renders as a 16px dot. When a snap is active, it springs to the registered element's `getBoundingClientRect()` and uses the target's computed `borderRadius`. ```tsx ``` If **`Cursor.Snap`** registers a composition wrapper with no radius, the engine uses the first rounded descendant as a radius hint. The rect still belongs to the registered snap element. ### Parallax layers [#parallax-layers] **`Cursor.Snap`** produces parallax values from the pointer's offset inside the active target. **`Cursor.SnapTarget`** consumes those values and applies Motion `x` / `y` transforms to its rendered element. ```tsx ``` Use **`factor`** as a multiplier. `0` disables movement for that layer, values above `1` exaggerate it, and negative values move opposite the pointer. ### CSS variables [#css-variables] For CSS-only consumers, **`Cursor.Snap`** also writes inherited CSS variables to the registered target: ```css [data-cursor-snap] .custom-layer { transform: translate3d(var(--cursor-parallax-x, 0px), var(--cursor-parallax-y, 0px), 0); } ``` The variables are spring-smoothed values: * **`--cursor-parallax-x`** * **`--cursor-parallax-y`** ### Combining with PressableFeedback [#combining-with-pressablefeedback] Use **`PressableFeedback.Highlight`** for the active-area glow and let **`Cursor.Pointer`** handle the cursor morph. This keeps the cursor material simple while the target owns its own hover/press affordance. ```tsx import { Button } from '@/components/button' import { Cursor } from '@/components/cursor' import { PressableFeedback } from '@/components/pressable-feedback' import { PlusIcon } from 'lucide-react' }> ``` ### Render as another element [#render-as-another-element] **`Cursor.Snap`** and **`Cursor.SnapTarget`** use Base UI's **`render`** prop. Use it when the registered or animated element must be another component. ```tsx import { motion } from 'motion/react' }> }> Motion layer ``` Rendered components must forward their ref and spread received props onto the DOM element that should be registered or animated. ### Disabled states [#disabled-states] Use **`isDisabled`** on each primitive for scoped behavior: ```tsx Open ``` The engine also disables itself for coarse pointers and `prefers-reduced-motion: reduce`. ## Example [#example] ```tsx import { Button } from '@/components/button' import { Cursor } from '@/components/cursor' import { PressableFeedback } from '@/components/pressable-feedback' export default function CursorExample() { return ( }> Launch ) } ``` ## API Reference [#api-reference] ### Cursor (`Cursor.Root`) [#cursor-cursorroot] The callable **`Cursor`** component is **`Cursor.Root`**. Beyond the table below, the root accepts standard **`React.HTMLAttributes`** (`className`, `style`, event handlers, etc.). ### Cursor.Pointer [#cursorpointer] Portal-rendered pointer visual. Beyond the table below, it accepts **`HTMLMotionProps<'div'>`** (Motion and DOM props for the pointer element). Custom **`render`** functions receive internal geometry MotionValues for `x`, `y`, `width`, `height`, `borderRadius`, and `opacity`. ### Cursor.Snap [#cursorsnap] Registers a snap target. Beyond the table below, it accepts standard **`React.HTMLAttributes`**. ### Cursor.SnapTarget [#cursorsnaptarget] Consumes the nearest snap's parallax values and applies Motion `x` / `y` transforms. It must be rendered inside **`Cursor.Snap`**. Beyond the table below, it accepts **`HTMLMotionProps<'div'>`**. ### CursorSnapshot [#cursorsnapshot] Semantic cursor state returned by **`useCursorSnapshot`** and passed to custom pointer render functions. ### CursorPointerRenderState [#cursorpointerrenderstate] State passed to custom **`Cursor.Pointer render`** functions. It includes the semantic snapshot plus pointer geometry MotionValues. # GridList (/docs/grid-list) ## Import [#import] ```tsx import { GridList, type ListRenderItemInfo } from '@/components/grid-list' ``` ## Anatomy [#anatomy] `GridList` is a single component; paging, layout, and indicators are composed internally. ```tsx ( /* your cell */ )} /> ``` * **GridList**: Root container. Measures viewport width, chunks `items` into pages, and drives horizontal paging with Framer Motion drag and spring snapping. Clears press state on `mouseup` at the wrapper level. * **Internal pager**: Each page lays cells in a **three-row** pattern (narrower top and bottom rows, wider middle row). Column counts depend on width and `itemSize` / `gutter`. * **renderCell**: You render each cell; `rowIndex` / `colIndex` describe position within the page layout. `isTapping` reflects whether that cell is currently pressed. * **Page indicators**: Dot indicators render below the grid only when there is more than one page. ## Usage [#usage] ### Basic usage [#basic-usage] Pass `items` with unique `id` values and implement `renderCell` for each cell. ```tsx type Photo = { id: string; url: string } const photos: Photo[] = [ { id: 'a', url: '/a.jpg' }, { id: 'b', url: '/b.jpg' }, ] export function Gallery() { return ( ( )} /> ) } ``` ### Cell size and spacing [#cell-size-and-spacing] Use `itemSize` for cell diameter, `gutter` for gap between cells, and `verticalSpacing` as a multiplier for row separation. Defaults are applied in the component implementation (`itemSize` 100, `gutter` 48, `verticalSpacing` 1.4). ```tsx {item.id}} /> ``` ### Typing `renderCell` [#typing-rendercell] Use `ListRenderItemInfo` with your item type. There is no flat list index on the info object; derive order from `items` if needed. ```tsx renderCell={({ item }: ListRenderItemInfo) => ( {item.url} )} ``` ### Behavior notes [#behavior-notes] 1. **Paging**: Items are chunked into pages; page size follows internal row geometry (`itemsPerPage`). 2. **Ready state**: The inner grid mounts after the first non-zero window width measurement (resize listener). 3. **Indicators**: Dots render only when there is more than one page. 4. **Gestures**: Horizontal drag changes page; release uses velocity thresholds and spring snap to the nearest page. ## Example [#example] ```tsx import { GridList, type ListRenderItemInfo } from '@/components/grid-list' type Photo = { id: string; url: string } const photos: Photo[] = [ { id: 'a', url: '/a.jpg' }, { id: 'b', url: '/b.jpg' }, ] export default function GridListExample() { return ( ) => ( )} /> ) } ``` ## API Reference [#api-reference] ### GridList [#gridlist] Props accepted by **`GridList`** (defaults for optional fields are applied in `grid-list.tsx`, not in the type). ### GridListItem [#gridlistitem] Constraint on each element of `items` (unique `id`). Used as the generic bound for `GridList`. ### ListRenderItemInfo [#listrenderiteminfo] Passed to `renderCell` for each cell. There is **no** flat list index on this object; derive ordering from `items` if needed. # Vision UI (/docs) ## What is Vision UI? [#what-is-vision-ui] Vision UI brings visionOS-like depth, glass, and spatial motion to the web with modern CSS — View Transitions, `@starting-style`, and pseudo-element highlights — plus composable and accessible React components. Motion handles gestures; route-shaped transition types handle the rest. # Ornament (/docs/ornament) ## Import [#import] ```tsx import { Ornament, useOrnament } from '@/components/ornament' ``` ## Anatomy [#anatomy] The module exposes one namespace object. **`Ornament`** is the root provider — it owns `orientation` plus the shared `isFocused` / `isPressed` state that the rail uses to drive its expand-on-focus animation. All visual leaves are flat siblings on the namespace. ```tsx } /> Home } /> People ``` * **Ornament / Ornament.Root**: Headless provider. Holds `orientation`, `isFocused`, and `isPressed` and renders no DOM of its own. * **Ornament.Tabs**: The frosted **`Surface`** that contains tabs. Renders with `role="tablist"`, animates between `collapsed` and `expanded` motion variants, and lays children out as a row or column based on `orientation`. * **Ornament.Tab**: An interactive **`Button`** styled for the rail. Wires its focus and press handlers into the provider so the surface can react. Pass **`isActive`** to render the selected style. * **Ornament.TabIcon**: Wrapper around the leading icon. Pass any node via the **`icon`** prop; child icons should set **`data-slot="icon"`** so the built-in size/opacity rules apply. * **Ornament.TabLabel**: Trailing text revealed when the rail is expanded. Strings are rendered with the standard line-clamped style; nodes are passed through untouched. * **useOrnament**: Hook for advanced consumers (e.g. a custom tab implementation outside the visual frame) that need to read `orientation` or drive `isFocused` / `isPressed` themselves. ## Usage [#usage] ### Orientation [#orientation] **`orientation`** controls both the rail layout (column vs row) and the surface track direction. Default is **`vertical`**. ```tsx } /> Search ``` ### Active state [#active-state] **`isActive`** flips the underlying **`Button`** variant from `secondary` to `default` and exposes `data-active="true"` on the element for downstream styling. ```tsx } /> Environments ``` ### Custom labels [#custom-labels] **`Ornament.TabLabel`** accepts strings (rendered with the default truncation style) or arbitrary nodes (rendered as-is) so you can add badges, counters, or shortcuts. ```tsx Inbox 12 ``` ### Driving state from outside the rail [#driving-state-from-outside-the-rail] When something outside the visible frame needs to reflect rail focus or press state, read it from the provider with **`useOrnament`**. ```tsx import { useOrnament } from '@/components/ornament' function OrnamentShadow() { const { isFocused } = useOrnament() return
} ``` ## Example [#example] ```tsx import { Ornament } from '@/components/ornament' import { AppStoreIcon, EnvironmentsIcon, PeopleIcon } from '@/components/icons' export default function OrnamentExample() { return ( } /> Home } /> People } /> Environments ) } ``` ## API Reference [#api-reference] ### Ornament (`Ornament.Root`) [#ornament-ornamentroot] The callable **`Ornament`** is **`Ornament.Root`** — a headless provider. It only forwards its children inside the context. ### Ornament.Tabs [#ornamenttabs] Renders the frosted rail surface (`role="tablist"`). Accepts standard **`React.HTMLAttributes`** in addition to **`children`**. Layout follows **`orientation`** from the root. ### Ornament.Tab [#ornamenttab] Renders a styled **`Button`**. Beyond the table below, the tab forwards normal **`