Documentation

Wear OS

A watch app is a second set of routes under src/wear/, sharing whatever code you want it to share with the phone app.

Layout

src/
├── routes/              # the phone app
├── stores/              # shared by both
├── models/              # shared by both
└── wear/
    ├── routes/          # the watch app
    │   └── +screen.wh
    ├── components/      # watch-only
    └── stores/          # watch-only

Routing under src/wear/routes/ works exactly as it does on the phone — see $navigate.

Building and running

whitehall build --wear    # build the watch APK
whitehall run --wear      # build and run on a watch or watch emulator

Configuration

# whitehall.toml
[wear]
min_sdk = 30      # Wear OS 3.0, the floor for Compose on watches
target_sdk = 36

Components

Wear has its own component library, because a round 45mm screen is not a small phone. The equivalents:

The shadcn library is not part of a watch app, whatever preset the project is on. Wear draws with its own design system, so <Alert>, <Dialog> and the rest belong to the phone side — use the Wear components below, or write your own under src/wear/components/.
WearPhone counterpartWhat it is
ScalingLazyColumnLazyColumnThe primary list. Items scale down toward the edges to suit a round screen
ChipButtonFull-width action with an icon and a label
CompactChipIconButtonSmaller, icon-led
ToggleChipSwitch rowOn/off in list form
TimeTextCurved clock across the top, which watch apps are expected to show
SwipeToDismissBoxBack handlingSwipe right to go back, the platform gesture
// src/wear/routes/+screen.wh
<ScalingLazyColumn>
  <TimeText />
  <Chip onClick={() => $navigate("/settings")} label="Settings" icon="settings" />
  <Chip onClick={() => $navigate("/workout")} label="Start Workout" icon="play" />
</ScalingLazyColumn>

Sharing code

Import aliases are what decide whether something is shared or watch-only. Phone code is reachable from the watch; the reverse is not:

// from either app
import $app.stores.Auth              // src/stores/Auth.wh — shared

// from the watch app only
import $wear.stores.WatchAuth        // src/wear/stores/WatchAuth.wh
import $wear.components.WatchCard    // src/wear/components/WatchCard.wh
Put the logic — stores, models, $fetch calls — in the shared directories, and keep src/wear/ for screens. The two apps then differ only in their UI, which is the part that genuinely has to differ.
The watch is a separate APK with its own min_sdk, not a build variant of the phone app. It ships and installs independently.

See Also