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/.| Wear | Phone counterpart | What it is |
|---|---|---|
ScalingLazyColumn | LazyColumn | The primary list. Items scale down toward the edges to suit a round screen |
Chip | Button | Full-width action with an icon and a label |
CompactChip | IconButton | Smaller, icon-led |
ToggleChip | Switch row | On/off in list form |
TimeText | — | Curved clock across the top, which watch apps are expected to show |
SwipeToDismissBox | Back handling | Swipe 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
- $navigate — routing, the same on both
- @store — the natural place for shared state
- Configuration — the
[wear]table