Skip to content
OTFotf
All posts

Unify Your Codebase: AI Agents Meet Cross-Platform Consistency with OTF Kits

D
DaveAuthor
7 min read
Unify Your Codebase: AI Agents Meet Cross-Platform Consistency with OTF Kits

How AI coding agents are getting impressive. Ask for a button, get a button. Ask for a form, get a form. The thing they're still bad at — sometimes catastrophically — is writing the same button on three platforms. That gap is where cross-platform codebases quietly fragment. Same component name, same props, same tokens, rendered through platform-native primitives: that's the contract that closes the gap, and it's what an agent can extend reliably instead of reinventing each surface.

The three-button problem

You asked your coding agent for a primary button. It wrote one. Then you asked for the same button on the home screen of your iOS app. It wrote a different one. Then Android. A third one. None of them match. None of them behave the same when the label is too long. None of them announce the same thing to a screen reader.

This isn't a failure of the model. It's a failure of the contract. The agent had no shared specification to write against — just three separate platforms with three separate idioms, and its job was to satisfy each one locally. Local optima all the way down.

Add a fourth surface (a watch app, a TV app, a PWA shell) and the divergence compounds. What shipped as a <Button> on day one is now a PrimaryButton, an IOSButton, a MaterialButton, and a BigScreenButton. The codebase has traded one source of truth for twelve.

Why agents amplify this, not fix it

A human developer who has shipped cross-platform for a decade will write the button once and import the same component everywhere — not because they're clever, but because they've been burned by the alternative. They have scar tissue. An agent has no scar tissue. It has a context window and a tendency to treat every request as a fresh problem.

Concretely: when you prompt an agent with "add a button to sign in", it has to choose between dozens of valid implementations on each platform. Without a constraint, it picks the locally-conventional one. Conventional on the web means a styled <button> with focus rings. Conventional on iOS means a configuration-backed UIButton. Conventional on Android means a MaterialButton with a ShapeAppearanceOverlay. Same word, three different mechanical objects, three different accessibility stories, three different long-text behaviors.

The agent isn't wrong. The platforms genuinely don't agree. But the user-facing question — "does this look and behave the same?" — has a single answer, and the agent is producing three.

a single <Button> component in a shared codebase rendering to web, iOS, and Android with p

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.

See the live demo

The contract has to come from the codebase, not the prompt

You can't prompt-engineer your way out of this. Agents don't fail because they misunderstand the task; they fail because the repository gives them three different definitions of "button" to satisfy. The fix is to make the repository answer the question, not the prompt.

That means a single import path that resolves to platform-appropriate output from one API. The component name is the contract. The props are the contract. The shipped behavior is the contract. The platform implementation is a detail.

import { Button } from "@otfdashkit/ui";

export function SignInCTA() {
  return (
    <Button
      intent="primary"
      size="md"
      onPress={() => signIn()}
      accessibilityLabel="Sign in to your account"
    >
      Sign in
    </Button>
  );
}

The same source file imports the same name on web, iOS, and Android. The import resolves to the platform-correct implementation under the hood — web gets a <button> with proper focus management, iOS gets a configuration-backed UIButton, Android gets a MaterialButton — but the call site is identical. The agent has exactly one thing to write. So it does.

one component name, one props contract, three platform-native renderings — the agent write

Tokens are the half that's actually portable

Components alone aren't enough. Visual consistency across platforms requires a single source of truth for color, spacing, radius, typography, motion. Hard-coding #3B82F6 in three places is the same bug in three places. An agent will happily help you write all three.

The tokens ship as a single package:

npm install @otfdashkit/tokens

Then every component reads from it. A theme flip — dark mode, or a rebrand from blue to green — is one file change:

// theme.ts
export const theme = {
  color: {
    primary: "#10B981", // was #3B82F6
    surface: "#0B0F19",
    onSurface: "#F8FAFC",
  },
  radius: { md: 8, lg: 12 },
  space: { 4: 16, 6: 24 },
};

The web bundle, the iOS bundle, and the Android bundle all read this same export. The agent never sees a hex code; it sees theme.color.primary. When you change the theme, every button on every platform tracks. The half-portable thing (the component) and the fully-portable thing (the token) compose into a system that behaves like one codebase.

The agent config that keeps it this way

A consistent codebase doesn't survive contact with a coding agent if the agent's instructions don't reinforce it. By default, an agent asked to "add a card" will invent a card. The patterns it picks will be plausible, local, and quietly different from the rest of the app.

OTF kits ship with the agent config inline:

<!-- CLAUDE.md (excerpt) -->
UI components: import from @otfdashkit/ui on web, @otfdashkit/ui-native on iOS/Android.
Never create a new component if an existing one in the kit covers the case.
Tokens come from @otfdashkit/tokens. Never hard-code color, spacing, or radius.
<!-- .cursorrules (excerpt) -->
- Use kit components before reaching for raw primitives.
- Use semantic token names (color.primary) not hex values.
- One component name across platforms. No IfIosButton, IfAndroidButton.

Plus 20+ tested prompts in ai/prompts/ that walk an agent through the common tasks: adding a screen, wiring a form, building a navigation shell. The agent extends the kit instead of regenerating it.

This is the part that makes the consistency durable. The contract isn't just in the code; it's in the rules the agent reads before it touches the code.

What you actually ship

A concrete example: a sign-in form that lives in one file and renders on web, iOS, and Android.

import { Button, TextField, Card, Stack } from "@otfdashkit/ui";
import { useState } from "react";

export function SignInForm({ onSubmit }: { onSubmit: (email: string) => void }) {
  const [email, setEmail] = useState("");
  const valid = /\S+@\S+\.\S+/.test(email);

  return (
    <Card padding="lg">
      <Stack gap="md">
        <TextField
          label="Email"
          value={email}
          onChange={setEmail}
          keyboardType="email-address"
          autoComplete="email"
        />
        <Button
          intent="primary"
          size="md"
          disabled={!valid}
          onPress={() => onSubmit(email)}
          accessibilityLabel="Continue with email"
        >
          Continue
        </Button>
      </Stack>
    </Card>
  );
}

Same file, same imports, same props, same Stack for layout, same Card for surface. On web the components render as semantic HTML. On iOS they render as UIButton + UITextField with VoiceOver labels. On Android they render as MaterialButton + TextInput with contentDescription. The agent wrote one file. The user got three native experiences. The designer got one visual.

Constraint beats instruction

The temptation, when an agent starts drifting, is to add more instructions. "Don't use MaterialButton directly. Don't use raw hex. Don't create platform-specific files." Each rule is correct. The aggregate is a long preamble the agent reads once and forgets by turn four.

The actual fix is structural: remove the ability to drift. If there is no platform-specific button in the codebase — because the only button is the one the kit exports — the agent cannot import a different one. Constraint beats instruction. The codebase is the spec.

What this enables

A team that has shipped a cross-platform surface this way can ask their agent for a feature and trust the output. The agent writes a button, the button is the same on every platform, the tokens carry the brand, the rules keep the agent within the system. Iteration speed is bounded by the model's latency, not by the human review needed to fix eleven different divergences.

The free SDK is MIT, around 200 components, on npm as @otfdashkit/ui, @otfdashkit/ui-native, and @otfdashkit/tokens. Install one, write a component, watch it ship to three platforms. The full-stack kits ($99 each, $149 for the bundle) wire the rest: auth, billing, DB, Stripe, mobile build, custom domain. Same principle, more surface.

AI agents are going to write more of our code, not less. The repository that scales with that trend is the one that gives the agent a single answer to every question it would otherwise be free to invent. One button. One name. One API. Three platforms. The agent writes the same thing every time because there is only one thing to write.

ai-toolscross-platformreact-native
OTF Fitness Kit

Stop wiring. Start shipping.

  • Login, database, and backend already connected — nothing to set up
  • iOS + Android + web from one codebase
  • AI configs pre-tuned + 40+ tested prompts included