Design Once, Run Everywhere: One UI File Across Web, iOS and Android
What it really takes to design once and run a UI on web, iOS and Android: shared layout and motion, platform-specific parts, testing, and where ExodeUI fits.
Core Takeaway
Design once, run everywhere means one source file describes a UI component and each platform plays it with a runtime, instead of rebuilding the same motion in React, SwiftUI and Flutter. It works when layout, states, motion and inputs live in the shared file, and it stays honest when navigation, accessibility and system gestures remain platform-specific. ExodeUI supports this model with runtime packages for React, Vue, Svelte, Angular, Solid, React Native, SwiftUI and Flutter.
What "design once, run everywhere" actually requires
It requires more than an export button. The shared file has to describe how the component lays out at different sizes, what it looks like in each state, how it moves between states, and how the app drives it with inputs. Each platform also needs a runtime that can play the file and connect those inputs to real events. If any of those pieces are missing, the promise turns into another handoff.
Shared file vs per-platform rebuild
The traditional route is to design once and rebuild three times: web in CSS and JavaScript, iOS in SwiftUI and Android in Kotlin or Flutter. Each rebuild introduces small differences in timing, spacing and gesture handling. A shared file route keeps the component definition in one place and lets each platform play it. The trade-off is that a runtime sits between the file and the platform, so you give up some direct control over platform-specific rendering.
What should stay platform-specific
A shared component should not pretend that every platform is the same. Navigation patterns, back gestures, system dialogs, permissions and accessibility APIs differ. The shared file should own the interactive visual component; the host app should own the platform chrome and the semantic controls around it. For example, a shared animated button can sit inside a native button element that exposes the right role and label.
How runtimes work
A runtime reads the file, builds the visual tree and updates it as inputs change. Your application code sets inputs such as pressed, disabled or loading, and listens for events such as confirm or dismiss. Because the visual definition is data rather than hand-written code per platform, the runtime is what makes the same file portable. The runtime is also a dependency, so measure its effect on your own build instead of assuming it is free.
Testing the same file on every environment
Test the file where it will run, not only in the editor. Check the layout at the smallest and largest supported sizes, verify every state with real content, and test touch, mouse and keyboard input separately. Then check accessibility: focus order, labels, reduced motion and screen reader behaviour. A shared file reduces visual drift, but it does not remove the need to test each platform.
Where ExodeUI fits
ExodeUI stores a component in an open .exode file and provides runtime packages for React, Vue, Svelte, Angular, Solid, React Native, SwiftUI and Flutter. The file can include layout and breakpoints, timelines, state machines, spring physics, inputs and events. The same file can be edited in the browser or desktop editor, and AI coding tools can work on it through the MCP server.
When not to use one shared file
A shared file is a poor fit when the interface depends heavily on platform-only APIs, when you need pixel-level control over native rendering, or when the component is mostly a video or 3D scene. It is also a poor fit if your team cannot test each target. In those cases, platform-specific code is the honest choice, and a shared file should cover only the parts that genuinely benefit from it.
Production Implementation Snippet
import { ExodeUICanvas } from 'exodeui';
// The same component file can be played by the runtime your app already uses.
// Web, React Native, SwiftUI and Flutter packages read the same .exode source.
export function ShareSheet({ open, progress, onClose }) {
return (
<ExodeUICanvas
src="/share_sheet.exode"
inputs={{ open, progress }}
onTrigger={(name) => name === 'close' && onClose()}
/>
);
}Key Principles to Remember
- Design once and run everywhere needs shared layout, states, motion and inputs, plus a runtime per platform
- Navigation, permissions and accessibility APIs should stay platform-specific
- A runtime is a dependency; measure its effect on your build
- Test every target for layout, input, motion and accessibility
- ExodeUI provides runtimes for React, Vue, Svelte, Angular, Solid, React Native, SwiftUI and Flutter