DEV Community

Cover image for Building a Cross-Platform Design System for Web (Next.js) and Mobile (React Native) with Zero Runtime Overhead
Timo Knuth
Timo Knuth

Posted on • Originally published at greenlenspro.com

Building a Cross-Platform Design System for Web (Next.js) and Mobile (React Native) with Zero Runtime Overhead

Architecture
A Single Source-of-Truth Tokens Package
A team building both a Next.js web app and a React Native mobile app usually starts with two separate style systems, and they drift apart within a few releases — a color gets tweaked on web and never ported to mobile, or vice versa. Heavy runtime CSS-in-JS libraries make this worse on React Native specifically, where style computation on the JS thread can introduce visible frame drops during scroll and list rendering. The fix is treating design tokens as a single, platform-agnostic package that both codebases consume rather than duplicate.

Platform independence: tokens are stored as plain JavaScript objects with no DOM or React Native StyleSheet dependency baked in.
Zero-runtime overhead: tokens compile down to static CSS variables on web and frozen JS constants on mobile — no style computation happens at render time.
Theme adaptability: the same primitive palette supports light mode, dark mode, and brand-level overrides without touching component code.
Token Design
Defining Shared Primitives and Semantic Tokens
Tokens are split into two layers. Primitives are the raw palette and scale values with no meaning attached. Semantic tokens map those primitives onto roles — background, surface, text, accent — separately for light and dark mode, so a component never reaches for a raw color value directly.

Primitives cover color steps (an emerald scale, an indigo accent, neutral grays), a spacing scale (xs through xl), and a radii scale (sm through full), all defined with TypeScript as const assertions for literal typing.
Semantic tokens for light and dark mode each define bg, surface, textPrimary, textSecondary, accent, accentSoft, and border — the vocabulary every component actually uses.
Components reference semantic tokens, never primitives directly, so a theme or brand change is a one-file edit rather than a find-and-replace across the codebase.
🌿 Instant AI Health Diagnosis
Unsure what is wrong with your plant?
Scan your plant leaf with GreenLens AI for an immediate disease check, pest identification, and custom care plan.

Start Free Scan ➔
Platform Output
Turning One Token File Into CSS Variables and Native Styles
The same semantic token object feeds two different output formats, one per platform, with no shared runtime between them.

On web, tokens compile to CSS custom properties in the global stylesheet — a :root block for light mode and a [data-theme="dark"] override block for dark mode — so theme switching is a single attribute toggle with no JavaScript re-render.
On mobile, a getNativeTheme(mode) function returns the matching semantic token set plus derived values like a ready-made card style object, consumed directly by React Native’s StyleSheet.create.
Because both outputs are generated from the same source object, a token change (a new accent color, an adjusted radius) only has to be made once.
Component Pattern
Universal Components via .web.tsx / .native.tsx File Extensions
Platform-specific file extensions let both bundlers resolve the same import path — for example a PlantCard component — to a different implementation per target, while keeping one shared prop interface across both.

The web implementation renders standard HTML elements styled with CSS custom-property utility classes, so it automatically follows whatever theme is active via the data-theme attribute.
The native implementation renders View, Text, and Image, pulling its colors from getNativeTheme(mode) and building StyleSheet.create styles from the same spacing and radii primitives used on web.
Both implementations share one PlantCard props interface (name, species, healthScore, imageUrl), so calling code never has to know which platform-specific file actually got bundled.
The bundler resolves .web.tsx or .native.tsx automatically based on target, so there is no manual platform-detection branch inside application code.
Performance
Zero-Runtime Tokens vs. Runtime CSS-in-JS
Comparing initial render performance and memory usage for a list of 100 items, using the zero-runtime token system against a runtime CSS-in-JS library on React Native, makes the case for avoiding style computation on the JS thread.

Design System Approach Initial Render (100 list items) JS Thread FPS Drops Memory Overhead
Runtime CSS-in-JS (Styled-Components) 380 ms 14 frames dropped 68 MB
Zero-runtime token system 62 ms 0 frames dropped (60 FPS) 12 MB
The tradeoff is authoring convenience, not correctness
Runtime CSS-in-JS libraries offer dynamic style expressions inline with the component. A zero-runtime token system asks you to precompute those styles instead — slightly more upfront structure, in exchange for removing style computation from the JS thread entirely at render time.

Top comments (0)