# Achieve True Cross-Platform Consistency with OTF's Unified Component System

> Discover how OTF's unified component system eliminates the cross-platform parity tax, letting you write components once and deploy them everywhere with identica
> By Dave · 2026-07-12
> Source: https://otf-kit.dev/blog/cross-platform-consistency

Writing a component once and shipping it to web, iOS, and Android — same name, same props, same pixels — is the boring dream every cross-platform framework has been chasing for a decade. Most of them get 80% of the way there and then quietly leak the last 20% as platform-specific escape hatches. We tried most of them. Here's the part that actually works.

## The parity tax, line by line

Every cross-platform project ships a balance sheet nobody wants to read. Three button components that drift apart over six months. Three inputs where the Android one loses its focus ring. Three loading spinners that look related but aren't. None of it is dramatic. All of it costs.

The line items:

- **Build drift.** You write `<Button>` on Tuesday, ship a new prop on Wednesday on iOS, push the same prop to web on Friday, forget Android until QA pings you three weeks later. The bug isn't the prop — it's the calendar.
- **Design drift.** A designer tweaks the primary colour in Figma on a Tuesday. The web theme updates in a PR the same day. Native ships the old shade for a quarter because the token export from Figma never reached the native repos.
- **Refactor drag.** Changing one component means auditing three codebases, three type files, three test suites, three sets of snapshots, and three different review queues. The PR is 200 lines. The deployment dance is 2,000.

Three platforms. Three of everything. That's the tax.

## One component, three platforms, one API

The same import path, the same props, the same JSX. Web:

```tsx
import { Button } from '@otfdashkit/ui'

export function CTA() {
  return (
    <Button variant="primary" size="md" onPress={startCheckout}>
      Get started
    </Button>
  )
}
```

Native (iOS + Android, same file):

```tsx
import { Button } from '@otfdashkit/ui-native'

export function CTA() {
  return (
    <Button variant="primary" size="md" onPress={startCheckout}>
      Get started
    </Button>
  )
}
```

Same `variant`. Same `size`. Same `onPress` shape. The ~200 components in the library cover the ground most apps actually need — buttons, inputs, sheets, lists, dialogs, tabs, toasts, navigation primitives — and every one of them ships with the same surface area on every platform.

That isn't a marketing claim. It's the API. You write `<Button>` once, you ship it three times.

## Tokens are the source of truth

Components are half the problem. The other half is colour, spacing, type, radius — the visual grammar. If those drift, the components drift with them, and no amount of component-library discipline will save you.

Install the token package alongside the components:

```bash
npm i @otfdashkit/ui @otfdashkit/ui-native @otfdashkit/tokens
```

Tokens look like this:

```ts
import { tokens } from '@otfdashkit/tokens'

export const theme = {
  color: {
    bg: { primary: tokens.color.blue[600], surface: tokens.color.gray[50] },
    text: { primary: tokens.color.gray[900], muted: tokens.color.gray[600] },
  },
  space: { 1: tokens.space[1], 2: tokens.space[2], 4: tokens.space[4] },
  radius: { sm: tokens.radius.sm, md: tokens.radius.md },
}
```

Flip `bg.primary` from blue to teal once, on one platform. All three update. The same `<Button>` on iOS, Android, and web renders with the same hex, the same padding, the same border-radius. Visual parity is a property of the token graph, not a hope held by the team.

Theme switching is the same primitive. A dark theme is a second token export. Components don't know it exists — they consume whatever the current theme hands them, and the theme is the only thing that knows which mode is active.

## The 24-item gate before anything ships

Most cross-platform libraries ship whatever compiles. The cross-platform kits ship after a script gates them against a 24-item design checklist. Touch targets meet platform minimums. Contrast passes WCAG AA. Focus rings are visible against every background token. Safe-area insets are honoured on every sheet and modal.

The checklist is enforced, not aspirational. A kit doesn't reach `npm publish` until the script returns zero. That's the difference between "looks right on my laptop" and "passes review on a real device, in dark mode, with the user's preferred text size".

A few items from the actual list:

| Check                                   | Why it matters                            |
| --------------------------------------- | ----------------------------------------- |
| Tap target ≥ 44pt iOS / 48dp Android    | App Store and Play Store floor            |
| Contrast ratio ≥ 4.5:1 for body text    | WCAG AA, also just legible                |
| Dark-mode parity per token              | No "almost dark" surfaces                 |
| Safe-area inset handling                | Notch, dynamic island, gesture bar        |
| Reduced-motion fallback                 | System setting respected, not ignored     |

If a kit fails any one of those, it doesn't ship. The gate is the contract.

## AI agents extend, they don't regenerate

This is the part most cross-platform stacks get wrong. Hand an AI coding agent a fresh empty repo and it invents its own conventions. Hand it a kit with no documentation and it does the same thing, but louder.

Every full-stack kit ships the conventions the agent needs to extend the kit instead of replacing it:

```text
my-app/
├── CLAUDE.md            # how the kit is structured
├── .cursorrules         # what the agent must / must not change
├── ai/
│   └── prompts/         # 20+ tested prompts for common tasks
└── src/
    └── ...              # your code, on top of the kit
```

`CLAUDE.md` says: this is a cross-platform kit, components live here, tokens flow through here, do not regenerate `<Button>`. `.cursorrules` says the same thing in Cursor's dialect. The 20+ prompts in `ai/prompts/` are the ones actually used to extend real kits — add a screen, wire auth, add a Stripe product, scaffold a new page, hook up a server action.

The agent extends. It doesn't replace. Convention beats configuration, and the convention is written down.



![one component source feeding web, iOS, and Android through a shared token graph; AI agent ](https://cdn.otf-kit.dev/blog/cross-platform-consistency/inline-1.png)



## From dev to production in one script

Cross-platform parity doesn't end at "it renders correctly in dev". You still need a domain, DNS, TLS, and a mobile build that survives App Store review. The SaaS Dashboard kit and the Fitness kit both ship with a deploy script that wires all of it:

```bash
./scripts/production.sh my-app.example.com
```

What it does, in order: validates env, builds web, builds the iOS archive, builds the Android AAB, points DNS at the deployment, issues and pins the TLS cert, runs the post-deploy smoke checks. One command. The script is the same one used internally — the same one that keeps saas.otf-kit.dev and fitness-preview.otf-kit.dev alive.

That's the boring half of cross-platform nobody talks about at a hackathon and everybody pays for the week before launch.

## What this enables

A team of two shipping an iOS app, an Android app, and a web app from one component tree. A designer updating one token and seeing it on three platforms the same day. An AI agent that asks "where do I add a new screen?" and gets a real answer from `CLAUDE.md` instead of inventing a fourth codebase. A release that doesn't need a "wait, does this work on Android?" channel in Slack on Friday afternoon.

You can start in two ways:

```bash
# Option A — copy-paste CLI, scaffolds into the current directory
npx otf-kit init

# Option B — straight npm install, bring your own scaffold
npm i @otfdashkit/ui @otfdashkit/ui-native @otfdashkit/tokens
```

Free MIT SDK on npm. ~200 components, three platforms, one API. Paid full-stack kits at $99 each (Everything Bundle $149) ship auth, billing, DB, Stripe, and the production script wired — you own the code. 15 single-page landing templates at $9 each when all you need is a marketing surface.

## Closing

Cross-platform parity is one of those problems everyone agrees is real and almost nobody fixes at the level it deserves. The fix isn't a cleverer framework. It's the same component name on every platform, the same token graph feeding it, and a checklist that stops bad builds before they ship. The model layer underneath will keep churning — context windows will grow, prices will drop, agents will get smarter. The render target underneath the model layer won't.

Write `<Button>` once. Ship it three times. Sleep on launch night.