The most useful unit of cross-platform development is not the platform. It is the feature.
If a product runs on iOS, Android, Windows, macOS, and Linux, the natural organizational temptation is to create five client teams. Each team becomes fluent in its operating system, owns its repository, and receives feature requests from somewhere above. That arrangement looks tidy, but it distributes responsibility for one user-visible behavior across five queues. The feature no longer has one owner. It becomes a negotiation.
I prefer the opposite division: give a feature to one engineer or one small team, and let that team carry it through every supported platform and every relevant layer. The implementation may be different on each platform. The responsibility should remain whole.
Native differences are product facts
Cross-platform frameworks are useful, but they do not abolish operating systems.
A real application eventually meets facilities that are not generic rectangles on a screen: text input, accessibility, drag and drop, window management, background execution, notifications, file access, cameras, media sessions, secure storage, share sheets, system settings, and platform-specific animation or transition behavior. These facilities have different lifecycles, permissions, failure modes, and conventions. Even when a framework exposes a common API, the product still has to be tested against the native behavior beneath that API.
The problem becomes sharper for a modern interface that deliberately participates in the character of its host system. A native view is not merely pixels. It carries state and often depends directly on system services. Abstracting it can be worthwhile, but the abstraction has a cost, and the most distinctive new platform capabilities are usually the least abstractable ones.
Domain rules, protocol definitions, test vectors, data models, and carefully chosen libraries can often be shared. React Native, Flutter, a web view, or another common layer may also be the right economic choice. I would now state my 2021 argument this way: separate implementations are optional; separate platform integration is unavoidable.
Pretending otherwise merely hides the work in adapters, wrappers, escape hatches, build systems, and bug reports.
A universal renderer moves the boundary
Another approach is to own every pixel through OpenGL, Vulkan, Metal, Direct3D, WebGL, HTML, SVG, or a custom rendering engine. This can produce fast, animated, visually consistent interfaces. It can also be the correct choice for a game, a design tool, a visualization system, or a product whose visual language matters more than platform convention.
But a universal renderer does not make the surrounding operating system disappear. It moves the boundary. The team must now build or integrate text shaping, input, focus, accessibility semantics, native menus, clipboard behavior, high-DPI rendering, assistive technology support, power management, and many other details. A sufficiently ambitious custom UI begins to resemble an engine of its own.
The important question is therefore not, “Can one toolkit draw this on every screen?” It is, “Which behavior should be shared, and where do we deliberately join each platform?”
That question has no framework-independent answer. It must be answered feature by feature.
Feature ownership prevents parity from becoming theater
Suppose a product is adding document scanning. A platform-silo organization might send the camera work to two mobile teams, upload work to a service team, review UI to a web team, and export behavior to desktop teams. Each group can complete its local ticket while the feature as a whole remains inconsistent.
A feature-oriented team instead owns the complete promise:
- how capture begins on each device;
- which native camera and permission APIs are used;
- how failures are presented;
- what processing occurs locally or remotely;
- how results synchronize;
- how accessibility works;
- and what “the same feature” actually means when the devices are different.
The code does not have to look the same. The user-visible contract does.
This is why “write once, run everywhere” is often the wrong management promise. Every platform still needs integration and testing. Many failures occur precisely at the seams: mismatched lifecycle rules, subtly different types, asynchronous callbacks crossing language boundaries, incomplete wrappers, and operating-system upgrades that change assumptions.
A shared codebase can reduce duplication. It cannot replace platform accountability.
Grow engineers across the stack
Feature ownership works only if knowledge is allowed to move. An engineer who begins in Xcode should not be permanently confined to iOS, and an Android or Windows specialist should not become a human API gateway for everyone else. Specialists remain valuable, but their knowledge should raise the capability of the whole feature team.
Pairing, review, small cross-platform changes, shared debugging sessions, and explicit platform notes can turn narrow expertise into team expertise. The goal is not to make every engineer equally deep in five enormous ecosystems. The goal is to make it normal for the person responsible for a behavior to follow that behavior across boundaries, ask for help where needed, and leave the next person more capable.
Feature ownership describes how people carry responsibility; microservices and microfrontends describe how software is divided. A feature team may work in a monolith, several services, multiple native clients, or all of the above. What matters is that the human division of work follows the product result more closely than the repository map.
Use one contract, not one illusion
The durable cross-platform strategy I advocate is:
- Define a feature contract in terms of user behavior and data.
- Decide explicitly which logic is safe and valuable to share.
- Implement native integration where each platform demands it.
- Keep one feature owner or one feature team accountable across all targets.
- Test on every platform; do not treat compilation through a common framework as parity.
- Feed platform knowledge back into the team instead of building permanent silos.
Sometimes this yields separate native codebases. Sometimes it yields a shared core with native shells. Sometimes a cross-platform UI framework carries most of the product and a few native modules carry the exceptions. The architecture can vary without weakening the principle.
The false economy is to optimize first for the fewest code files and only later discover that nobody owns the complete experience. I would rather duplicate a modest amount of code than duplicate responsibility, arguments, and partially compatible behavior.
One feature should have one coherent promise. The same people should be able to trace that promise through every platform on which it appears.