Expo and React Native
Two mobile apps, and neither of them owns a colour.
The failure mode with a product on web and mobile is not that they look different on day one. It is that they drift — a colour is nudged on the web, the phone keeps the old one, and six months later nobody can say which is correct because both are written down in two places.
So the tokens are a workspace package. @tether/design for one, packages/tokens for Coursified, which serves web and mobile at once. A colour or a type step is declared once and imported everywhere, and there is no second place to change it.
Overrides at the workspace root, not hopeful ranges in each app.
Tether’s root package.json pins React, React DOM, React Native, react-native-safe-area-context and react-native-svg by exact version in overrides. In a workspace, two packages asking for compatible-but-different versions is normal resolution behaviour — and in a native tree it produces hooks that fail across a boundary for no visible reason. The pin is not caution; it is the difference between a build that works and a day lost to a symptom with no stack trace.
A face that is not registered does not error — it renders in the system font and looks almost right. The only way to know is to check, per platform.
iOS shadow properties do nothing on Android, which wants elevation. A card that reads correctly on one looks flat on the other.
The inset differs by device and by orientation, so a layout verified on one simulator is not verified.
None of the above is solved by being careful. It is solved by having one declaration site and checking the rendered result against it.
A design that survives the trip into React Native survives it because somebody measured, not because somebody was careful.
the working rule behind both appsTether — apps/mobile and packages/design
The mobile app and the token package sit in the same Bun workspace as the API, so a change to a shared type breaks all three builds at once rather than one of them at runtime.
The phone and the web look like the same product because there is one place to change.
That is the whole trick, and it is organisational rather than technical: the token package removes the option of two answers.