Skip to content
OTFotf
All posts

A cross-platform UI contract gives AI coding agents fewer ways to break your app

D
DaveAuthor
8 min read
A cross-platform UI contract gives AI coding agents fewer ways to break your app

AI coding agents are good at producing a screen that looks plausible. They are much less reliable at preserving the invisible agreements that make an app feel like one product across web, iOS, and Android.

That gap becomes expensive as soon as a team moves beyond a single prototype. One agent invents a new spacing value. Another creates a mobile-only button state. A third copies a card but changes its loading behavior. Every individual change may be reasonable, yet the product gradually loses its visual rhythm and interaction logic.

The practical fix is not to ask agents to “be consistent.” It is to give them a cross-platform UI contract: a small, explicit set of rules that defines what can vary, what must stay shared, and how a contributor proves that a change fits the system.

This is useful for any team building a shared web and mobile experience, especially when several developers or agents work in parallel. The contract turns design-system intent into implementation guidance that can be checked during generation and review.

Start with the product promises

A UI contract should begin with user-visible promises, not a catalog of component names. Write down the qualities the interface must preserve on every platform.

For example:

  • Primary actions should be recognizable and placed predictably.
  • Important status changes should have a visual and accessible representation.
  • Forms should communicate errors next to the field that needs attention.
  • Destructive actions should require an intentional gesture or confirmation.
  • Loading should preserve context instead of making the screen jump.
  • Touch targets should remain comfortable on small screens.

These statements give an agent a reason behind the rule. “Use ButtonPrimary” is easy to follow in one file. “Use the primary action style for the one action that advances the user’s current task” is easier to apply when the right component is not obvious.

Keep the list short. A contract that tries to describe every visual detail becomes background noise. Aim for roughly five to ten product promises, then connect each promise to tokens, components, or review checks.

Define the shared vocabulary

Agents need stable names for the decisions your team wants to reuse. Create a vocabulary for tokens and primitives before describing larger screens.

The vocabulary can include:

  • Color roles such as surface, text, muted text, border, accent, success, warning, and danger.
  • Spacing steps with a clear progression rather than arbitrary pixel values.
  • Type roles such as display, heading, body, label, and caption.
  • Shape roles for cards, controls, overlays, and full-bleed surfaces.
  • Elevation or emphasis levels.
  • Motion roles for entrance, feedback, and progress.
  • Component states such as default, pressed, focused, disabled, loading, invalid, and selected.

The important detail is to name intent rather than appearance. color.blue500 tells an agent what a value looks like today. color.action tells it why that value exists and leaves room for the system to evolve.

A compact token example might look like this:

export const space = {
  xs: 4,
  sm: 8,
  md: 16,
  lg: 24,
  xl: 32,
} as const;

export const color = {
  surface: '#FFFFFF',
  text: '#17202A',
  mutedText: '#667085',
  action: '#E85D2A',
  danger: '#C9372C',
} as const;

The exact values are less important than the boundary. Tell the agent when it may choose an existing token and when adding a new token is justified. A useful default is: use an existing semantic token; propose a new one only when an existing role cannot express the requirement.

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

Separate shared behavior from platform expression

Cross-platform does not mean identical pixels. It means that the product’s meaning and behavior are stable while each platform gets an appropriate expression.

Put these decisions in the shared contract:

  • The purpose of the component.
  • Its required states.
  • Its content hierarchy.
  • Its keyboard, touch, and accessibility expectations.
  • Its validation and error behavior.
  • Its analytics or event semantics, when applicable.

Then document the allowed platform differences:

  • Navigation patterns may use a tab bar on mobile and a sidebar on larger screens.
  • A mobile action sheet may correspond to a web menu or popover.
  • A dense data table may become stacked rows or horizontally scrollable content.
  • Keyboard focus indicators matter on web, while touch feedback matters on mobile.

This distinction prevents two common agent mistakes. The first is forcing desktop layouts onto narrow screens. The second is creating separate implementations whose behavior drifts because “mobile” and “web” were treated as unrelated products.

A good rule is: share intent, state, content structure, and tokens; allow composition and input expression to vary when the platform requires it.

Describe components as state machines

A component name is not a specification. Agents often generate the happy path and forget the states that users experience when something goes wrong.

For every important component, list its states and transitions. A submit button, for instance, may need to support:

  • Ready: action is available.
  • Focused: keyboard or assistive focus is visible.
  • Pressed: interaction feedback is visible.
  • Disabled: action is unavailable and the reason is not hidden behind color alone.
  • Loading: repeated submission is prevented and progress is communicated.
  • Error: the failed action leaves the user with a clear next step.
  • Success: completion is confirmed without removing useful context too quickly.

You do not need a formal state-machine library to make this useful. A table in a contributor document or a typed state union can be enough:

type SubmitState =
  | 'ready'
  | 'focused'
  | 'pressed'
  | 'disabled'
  | 'loading'
  | 'error'
  | 'success';

Ask the agent to implement the states before it composes the screen. This changes the generation process from “draw a button” to “satisfy a known behavioral surface.” It also makes review faster because missing states are visible omissions rather than subjective polish issues.

Give agents an order of operations

A contract works best when it tells an agent how to make decisions, not just what the final code should resemble. Use a repeatable sequence:

  1. Identify the user task and the primary action.
  2. Find the closest existing component and screen pattern.
  3. Reuse semantic tokens and required component states.
  4. Decide which layout choices are shared and which are platform-specific.
  5. Implement the smallest composition that satisfies the task.
  6. Check narrow, wide, loading, empty, error, and success conditions.
  7. Report any new token, component, or exception before treating the work as complete.

This sequence is valuable because agents are strongly influenced by the nearest example. If the nearest example is a one-off screen with hard-coded values, the agent will likely copy its weaknesses. Make the preferred path easier to discover than the exception path: organize examples by reusable pattern, use descriptive filenames, and include a short “when not to use this” note for components with narrow purposes.

Add an exception protocol

No design system covers every product decision. The contract should make exceptions safe instead of pretending they will not happen.

Require an agent or developer introducing an exception to state:

  • Which rule is being bypassed.
  • Why an existing pattern is insufficient.
  • Whether the exception is platform-specific or shared.
  • What user problem it solves.
  • Whether it should become a reusable pattern.
  • How the exception will be tested.

This creates a useful friction point without blocking experimentation. It also gives reviewers a clear question: is this genuinely new product behavior, or did the contributor miss an existing primitive?

Keep exceptions close to the code and easy to search. A short comment, decision record, or component story is more useful than an undocumented convention known only to the person who added it.

Turn the contract into review checks

The last part of the contract is evidence. Ask for checks that can be performed consistently by a human, an agent, or automation.

A practical review checklist includes:

  • Does the screen use semantic tokens rather than unexplained constants?
  • Are all required component states represented?
  • Does the primary action remain clear on every supported width?
  • Do empty, loading, error, and success states preserve the user’s context?
  • Can keyboard users reach and understand interactive controls?
  • Are touch targets and gestures appropriate for mobile?
  • Does the implementation reuse an existing pattern where one exists?
  • Are platform differences intentional and documented?
  • Were new tokens or exceptions called out explicitly?

For cross-platform projects, check the actual platform guidance rather than relying on a generic “responsive” label. The React Native accessibility documentation is a useful primary reference for the mobile side of this conversation, but your contract should also record the conventions specific to your product.

The checklist should be part of the agent’s completion response. For example: “I reused the form field primitive, added invalid and loading states, used the action color token, verified the compact layout, and documented one platform-specific navigation choice.” That answer is more valuable than a confident claim that the screen is done.

Keep the contract small and alive

A UI contract is not a giant design-system manual. It is the smallest set of decisions that prevents predictable drift when work is distributed across people and agents.

Start with one product surface and a handful of high-frequency components. Watch where agents still improvise: spacing, status states, responsive composition, naming, or accessibility. Add rules only where repeated ambiguity creates rework. Remove rules that no longer guide decisions.

Review the contract alongside the interface. When the product changes its navigation model, tone, or supported platform, update the contract as part of the same change. Stale guidance is worse than no guidance because it makes incorrect output look compliant.

The payoff is not visual sameness. It is a shared product language that survives parallel implementation. Agents can move quickly, developers can review against explicit expectations, and users get an experience that feels intentional wherever they encounter it.

ai-toolscross-platformdesign-system
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