Lynx is an open-source cross-platform native UI framework created by ByteDance (the company behind TikTok). It was open-sourced in early 2025. You write the UI in TypeScript with React-like APIs, and Lynx renders it with native views on Android and iOS instead of a WebView.
Why Lynx for this project
This project exists to learn Lynx by building something real. A Flappy Bird clone is a good fit because it exercises:
- Element-based rendering — no canvas, all positioning via
<view>+ CSS transforms - Per-frame animation — a main-thread frame loop that updates element styles without React renders
- Touch input — main-thread tap handlers with no cross-thread round trip
- Asset loading — images, sprites, audio
- Cross-platform code — Android and iOS hosts plus web preview from one bundle
- CI/CD — automated build and release pipeline
How Lynx differs from other frameworks
| Feature | Lynx | React Native | WebView (Cordova) |
|---|---|---|---|
| Rendering | Native engine on mobile | Native renderer (Fabric in the New Architecture) | Browser engine |
| UI elements | <view>, <image>, <text> etc. |
<View>, <Image>, <Text> |
HTML elements |
| Styling | CSS with platform/version-specific support | JavaScript style objects and supported layout/style properties | Web CSS |
| Execution | Main-thread rendering + background JavaScript | New Architecture uses JSI; not the legacy serialized bridge | Browser main thread, with workers available |
| JS engine | PrimJS on these native hosts; platform-dependent runtimes elsewhere | Hermes by default | Browser-dependent (for example V8 or JavaScriptCore) |
| Component APIs | ReactLynx, with its own runtime and compatibility APIs | React Native | React DOM |
| Build tool | Rspack (@lynx-js/rspeedy) |
Metro | Webpack/Vite |
Key Lynx concepts used in Speedy Bird
React Native 0.82 and newer run only on the New Architecture. Framework behavior evolves; consult the linked official references instead of treating this table as a benchmark or exhaustive feature list.
Elements used here
The ReactLynx game uses three built-in elements:
<view>— every container, positioned absolutely with transforms<image>— bird sprites, pipe tiles, background, ground, medals, digits<text>— score numbers on the game-over panel
This application does not use canvas or extended elements for its Lynx renderer. Lynx and its platform extensions offer additional elements such as video, SVG, and canvas integrations; availability depends on the platform and registered components. On the web, Lynx’s web runtime renders the same elements as custom elements inside the <lynx-view> shadow root.
CSS differences
- Layout and styling are interpreted by Lynx on native targets, not by a browser stylesheet engine
- The game uses explicit absolute positioning, flex rows, overflow clipping, and transforms
- Length units include
px,ppx,rpx,em,rem,vh, andvw; percentages are supported where the property permits them
The project primarily uses pixels and percentages. See the Lynx length reference for definitions and platform compatibility.
Dual-threaded architecture and Main Thread Script
React reconciliation (diffing, state updates) runs on a background thread. The main thread handles native rendering and touch events. Normally every update crosses between the two, which is fine for UI but too slow for a game updated every frame. Main Thread Script lets selected functions run on the main thread instead:
'main thread'functions handle taps (main-thread:bindtap) and run the frame loop withlynx.requestAnimationFrameuseMainThreadRefbinds elements (main-thread:ref) and keeps the controller between callssetStylePropertiesmoves the bird, pipes, and scenery directly, without Reactimport ... with { runtime: 'shared' }makes the pure engine module callable from main-thread coderunOnBackgroundsends discrete changes (score, state, sounds) to React;runOnMainThreadlets background code start, pause, or resume the loop
See Game engine for the frame-by-frame flow.
Native Modules
Sound, saved scores, and screen-reader announcements go through one native module, SpeedyBirdModule, read from the background thread’s NativeModules (src/platform/host.ts). Each host implements it: the standalone web host registers an ES module through <lynx-view>’s nativeModulesMap and handles calls with onNativeModulesCall. A host without the module still runs the game silently. The website’s home page uses the same browser implementation (web-host/host.ts) as the standalone host.