caliper uidocs

Object viewer

Pro

A physical thing you can turn, with notes pinned to its faces. It is a true six-faced cube; each face is whatever you pass, and a pin is a child of its face, so it turns with it and disappears when the face turns away.

Installs components/ui/object-viewer.tsxDepends on @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add @caliper/object-viewer
SU-4Sensor unit1
5 901234 123457✓
⚠FRAGILEHandle with care3
Width 120 mm↔Height 86 mm
↑↑THIS SIDE UP
Made in PortugalLot 2026-10

Select a note to turn the object to it.

When to use #

Use it for

  • Packaging proofs, product reviews and hardware documentation.
  • Pinning feedback to a place on a physical object.

Skip it when

  • Photographs or product models with real 3D geometry; use a model viewer.

Decisions #

Why it is built this way, so you know what to keep when you change your copy.

Behaviour

Notes are buttons
Choosing a note turns the object to its face and opens its card. Pins repeat the list and are not read out.
Several ways to turn
Drag, the arrow keys (15 degrees), the numbers 1 to 6, or the face buttons, so no one has to drag.

Installation #

terminal
pnpm dlx shadcn@latest add @caliper/object-viewer

Adds its 6 files to components/ui/, each with its own stylesheet, and installs their packages. Pro installs with your access token, set up once per project. See Pro access. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

Import it from your copy of the source. The example behind the preview comes with the source.

example.tsx
import { ObjectViewer } from "@/components/ui/object-viewer"

The worked example comes with Pro

The 29-line example behind the preview opens here with a Pro or Team licence.

Checking your licence…

API reference #

ObjectViewer

component
PropTypeDefaultDescription
label *string—Names the object for screen readers, for example "Sensor unit packaging proof".
faces *Partial<Record<ObjectFace, ReactNode>>—
notesreadonly ObjectNote[][]
onResolvedChange((id: string, resolved: boolean) => void)—
sizenumber160Edge of the cube in pixels.
statusDepthStatus"ready"
messagesDepthMessages—
onRetry(() => void)—
disabledboolean—
classNamestring—

Accessibility #

It exposes group and img semantics, with aria-roledescription, aria-label, aria-pressed and aria-live kept in step with state. Changes that matter are announced through a live region.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.