Skip to content

PATTERN Cited by 1 source

Design-system component library cross-platform

Problem

You maintain a design system — buttons, cards, dialogs, typography, etc. — and you want a single design-system component library that renders on web + iOS + Android with consistent branding and behaviour. Naive approaches:

  • Separate libraries per platform — drift accumulates; every design change has to happen in three places.
  • Runtime-branching single library — ships all platform code to every platform; web bundle carries native deps it doesn't need.
  • Pure cross-platform without escape hatches — subset constraint forces giving up platform-specific polish.

Pattern

Build a cross-platform component library using three layered capabilities together:

  1. HTML-subset UI substrate — write components with react-strict-dom's html.* elements. See patterns/html-subset-to-native-ui-mapping.
  2. Cross-platform styling via design tokens — use StyleX + a design-token package (e.g. Zalando's ZDS tokens) so styles resolve per-platform at build time. See concepts/design-token-cross-platform-theming.
  3. Per-platform escape hatches — for components where the HTML subset isn't enough:
  4. patterns/compat-native-escape-hatch (compat.native for one-off platform-specific props).
  5. patterns/platform-specific-ts-file-resolution (Foo.native.ts / Foo.ios.ts / Foo.android.ts for fully divergent implementations, using Metro's file resolution).

Discipline: types live in a separate file

When components have per-platform implementations (patterns/platform-specific-ts-file-resolution), put the types in a dedicated file (e.g. Button.types.ts) so every implementation typechecks against the same contract. This ensures consumers see one stable component API regardless of which platform they're on.

Zalando's post states this explicitly: "we started using a simple pattern where types would live in a separate file so that we can have safe type checking between multiple implementations." (Source: sources/2025-10-02-zalando-accelerating-mobile-app-development-with-rendering-engine-and-react-native)

Example

A cross-platform DefaultMessage component (from Zalando's 2025-10 disclosure):

import { tokens } from "@zds/tokens/tokens.stylex";

export const DefaultMessage = ({ style, ...props }: MessageProps) => {
  const defaultStyle = [styles.primaryStyle, style];
  return <BaseMessage {...props} style={defaultStyle} />;
};

const styles = css.create({
  primaryStyle: {
    backgroundColor: tokens.colorBackgroundDefault,
    borderWidth: tokens.borderWidthS,
    borderColor: tokens.colorBorderSecondary,
    borderStyle: "solid",
  },
});
  • BaseMessage is a cross-platform primitive (possibly with .native.ts / .ts variants for per-platform implementations, depending on complexity).
  • tokens.colorBackgroundDefault etc. resolve to CSS variables on web, RN style primitives on mobile.
  • One consumer API; one set of imports; works identically on all three platforms within the HTML + tokens subset.

When to reach for this pattern

  • You're shipping UI across web + iOS + Android and have a design system that should remain consistent.
  • You want one library to maintain, one design-token source of truth.
  • You accept the cross-platform subset constraint (see concepts/cross-platform-ui-subset-tradeoff) and have a disciplined escape-hatch story.

When not to

  • Only shipping mobile (iOS + Android, no web) — the HTML framing is overhead you don't need. Native-primitive RN components with a cross-platform style system may be simpler.
  • Only shipping web — no cross-platform layer is needed; plain HTML + CSS suffices.
  • You don't have the engineering investment to maintain three layered capabilities + escape hatches. This is real work.

Seen in

Last updated · 507 distilled / 1,218 read