How OTF's Fitness kit ships iOS, Android, and web from a single Expo codebase
Building for web, iOS, and Android isn’t a luxury anymore. Users expect your new SaaS, marketplace, or personal project to work everywhere — not "coming soon" banners. This used to mean three codebases, three design systems, three test matrices, and three times the surface for every bug and changelist. The OTF Fitness kit changes that math. One codebase ships to all major platforms — and the code you write to add a workout, paywall, or feed really does ship the same to the App Store, Play Store, and web.
This is hard. Getting one codebase to materialize as a proper app — pixel for pixel, native look, and touch feel on all three surfaces — forces choices at every layer. It's not a "compile to mobile" magic trick. But it's not theoretical: it's running at fitness-preview.otf-kit.dev for mobile, and it's what ships on web for every demo.
The thesis: shipping the same source to real web, iOS, and Android — with the same design tokens, component APIs, and logic — halves the moving parts a solo or small team faces. There are edges, but the payoff is real.
What the same-codebase approach enables
Forget for a second the technical curiosity. For a solo builder or small team, every duplicate surface (separate iOS and Android code) isn't just annoying — it's extra dead weight. More code to keep updated. More divergent styling. More places for small changes to slip out of sync.
With a single repo driving all three platforms:
- Add a new workout screen? It lands everywhere.
- Refactor a hook? One pass, no porting.
- Tweak a theme color or fix a spacing nit? The change is visible instantly on the devices you care about.
The unit of change gets smaller and the cost of change crashes. Everything you test and ship feels lighter and more maneuverable.
One component, everywhere: banishing platform splits
In most open-source projects that claim "cross-platform", the moment you need polish, you hit files like Button.web.jsx, Button.ios.jsx, and Button.android.jsx. Divergence creeps in. The OTF outcome is different: every component, from <Button> to <Card>, exposes the same props, shapes, and tokens — and renders visually and functionally the same on each platform.
You never need to write this:
// common—antipattern with most stacks
import ButtonWeb from './Button.web'
import ButtonIOS from './Button.ios'
import ButtonAndroid from './Button.android'
// then patch up divergent APIs manuallyWith OTF's kit, it's always:
// One Button that looks and behaves the same everywhere
import { Button } from '@otfdashkit/ui-native'
<Button variant="primary" size="lg" icon={<Check />} />This outcome is non-trivial. Behind the scenes, each platform's actual base (web or mobile) primitives are used, but that abstraction never leaks unless you go looking for it. You focus on what the component is, not where it runs.
One codebase. iOS, Android, and web.
The Fitness Kit ships with auth, a database, and a backend already connected — no setup. Live demo at fitness-preview.otf-kit.dev.
Theming and tokens: flipping styles globally
A recurring pain with prior cross-platform toolchains is keeping the shadows, border radius, and color palette truly in sync between web and mobile — much harder than it sounds. OTF ships design tokens as the atomic layer of the design system. You change one value (color.primary = '#006BFF'), and every button, card, nav rail updates everywhere — iOS, Android, and web — without hunting for theme folders.
A single theme file for all platforms:
// tokens.ts
export const color = {
primary: "#006BFF",
surface: "#ffffff",
background: "#F6F7F9",
}
// Usage in any component
<Button style={{ backgroundColor: color.primary }}>Go</Button>Change it once, verify in a single run, and ship with confidence. No divergence between Figma, the web build, and either native app.
Truths and tradeoffs: where one codebase holds or breaks
It's dishonest to claim this is "perfect". Three realities you can’t paper over:
-
Platform idioms are real. Users expect modals, navigation, and input to match what their OS provides. Native navigation feels different than a browser tab. OTF's kits abstract this by shipping platform-sensitive primitives where needed. But there are divergences: gesture navigation is handled natively; some drag-to-dismiss gestures "just work" on mobile, not web.
-
Form factors force design choices. A workout tracker’s big chart may suit landscape tablets, but squeeze on an iPhone mini. Responsive design is necessary — but the same tokens and layout system can power all variants from the same code.
-
Build and deploy quirks persist. The codebase is unified, but mobile builds still require Xcode for iOS and the Play Store pipeline for Android. You can ship all three from one source, but not from one binary. OTF’s ship scripts automate the boilerplate, but you need each platform’s tools for the last mile.
The core: The single-codebase approach works as long as you honor where platforms really differ and keep the abstractions honest. Where the physical device demands a split, OTF exposes APIs to plug in a platform-specific chunk, but you rarely need them.
Setup: from install to device in minutes
Here's the actual sequence to go from a fresh repo to three real apps. No boilerplate — just npm and Expo tooling.
# 1. Clone the OTF Fitness kit
git clone my-fitness-app
cd my-fitness-app
# 2. Install dependencies
npm install
# 3. Start in Expo: runs iOS, Android, and web simulators in one shell
npm run start
# 4. For production: ship to the App Store, Play Store, or deploy the web build
npm run build:web # Generates the web output
npm run build:ios # Builds the native iOS app (requires Xcode)
npm run build:android # Builds the native Android app (requires Android SDK)On web, the result is already production-grade. For mobile: plug in your app identity (icons, bundle id) and connect your device or store account.

Real payoffs: surface area, onboarding, and maintenance
Three places this pattern crushes the old multi-codebase approach:
-
Onboarding teammates or collab partners: A new engineer or freelancer reads one design system, one folder structure, one testing surface. Fewer onboarding docs, no "which app" confusion.
-
Bug fixes and UI tweaks: No more patching the iOS "shadow" and then fixing a slightly different bug on Android the next week. A tweak is universal — the only necessary platform split is when you truly hit a divergent device API (camera, biometrics, platform notifications).
-
Prototyping and product velocity: You build a new feature — a social feed, a progress chart — and immediately demo it on all devices. Stakeholders or users can scan a QR code and run it on their actual phone, not just in a web tab.
A direct comparison:
| Old way (3 codebases) | OTF Fitness kit (1 codebase) | |
|---|---|---|
| Layout | 3 divergent systems | 1 theme, 1 grid |
| Components | 3 APIs, polyglot props | 1 API, shared shape |
| Bug fixes | Multiply by 3 | Patch once everywhere |
| Shipping | Pain per platform | Single build script, 1 config |
The net: you spend your cycles on the part users see and value, not plumbing.
Where the abstraction leaks (and why it’s not fatal)
Every strong abstraction leaks. Here’s where solo builders should expect to do extra work:
- Deep OS integrations: If you're writing a complex Bluetooth stack or using device-native SDKs, you’ll write some native modules. OTF gives you an escape hatch, but these aren't cross-platform magic.
- Complex animations: Lottie and native animations are possible, but sometimes you hit limits where platform APIs differ. For most fitness apps, the lib coverage is enough, but game-level fluidity may need platform work.
- Store policies: App Store compliance, push notification entitlements, etc. — not solvable by codebase sharing. OTF doesn't magic these away.
Takeaway: For 90% of the UI and app logic, one codebase holds. For the last 10%, you use focused platform code — but that’s a conscious trade, not a daily grind.
What OTF wraps for you — and why the tactic survives the next platform churn
Competitors like Lovable and Bolt pitch cross-platform kits, but the real durable edge isn't the underlying platform, it's a layer above: OTF's kits ship all the AI-agent-readable config, code comments, and prompt docs you need to extend the kit with any modern coding agent. Regenerate the app? Ship an AI-built feature? Your agent sees CLUADE.md, .cursorrules, and 20+ prompt scripts per kit — and builds new screens or components that match the design and code idioms of the base kit.
The piece that never changes: real business code is written once. If a model, framework, or library churns, you update the underlying wiring — not the app logic, not the component shape, not the token system. This shields solo builders and small teams from the next cycle of platform fragmentation.
Summary: one codebase holds for more than it breaks
Shipping a real product with one source — verified design, shared tokens, and real mobile builds — is no longer aspirational. The OTF Fitness kit puts this in reach: your changes land on web and both app stores, with the real logic and polish users expect. The sharp edges are honest, but the moving part count is cut in half. Build it once, and ship it everywhere users care.
Stop wiring. Start shipping.
- Auth, DB, and backend already connected — no Supabase setup needed
- iOS + Android + web from one codebase
- CLAUDE.md pre-tuned + 40+ tested AI prompts included