Lynx 3.9: Autolink, Web-Aligned CSS, and ReactLynx Portal
← All PostsLynx 3.9 is now officially released!
This release focuses on three themes: Autolink, which makes a native capability something you can package, publish, and install; CSS behavior that is closer to the Web platform; and ReactLynx updates around Portal, rendering, and bundle output. It also includes a set of focused updates for Elements, Lynx APIs, and performance. Let's take a look at what's new in Lynx 3.9.
Autolink
Before 3.9, using a native capability meant going into the native projects: adding a dependency in Gradle and in the Podfile, then registering the Elements, Native Modules, and Services by hand in the Android and iOS startup paths. Lynx 3.9 collapses that into a unit you can publish and install: a Lynx native library is an npm package that declares its native entry points in a lynx.lib.json manifest at the package root, which Autolink discovers at build time and connects to the host app.
Consuming a library
Setting up Autolink is a one-time step: on Android, enable one Gradle plugin in settings.gradle and one in the app's build.gradle; on iOS, install cocoapods-lynx-library and call use_lynx_library! in the Podfile. Then install the library:
Re-sync on Android, re-run pod install on iOS, and the Elements, Native Modules, and Services that library ships are registered when LynxEnv initializes.
Authoring a library
To ship a native capability as a library, scaffold it with create-lynx-library:
The generated package carries a lynx.lib.json: the manifest that declares where the library's native entry points are, and the file Autolink scans to discover installed libraries. Type declarations, a JavaScript facade, native source directories, and an example app are generated alongside it. For codegen, the native markers, and publishing, see the Autolink guide.
CSS, Closer to the Web
The CSS work in Lynx 3.9 is not about adding more properties. The goal is to make Lynx styling closer to the syntax and behavior Web developers already know, so migrated styles are easier to reason about and AI-generated Lynx code is less likely to misuse Web-only assumptions. This release fills in compatibility gaps such as !important and flex shorthand parsing, and continues to expand Grid, Sticky, and Motion Path.
!important priority support
With engineVersion 3.9 or later, you can append !important to a CSS value to increase that declaration's priority in the cascade. For example, the color of <view className="foo" /> below resolves to red rather than blue:
flex shorthand parsing
The flex shorthand expands to a combination of flex-grow, flex-shrink, and flex-basis. Starting in 3.9, an omitted flex-basis is parsed as 0% instead of the previous 0.
Although the CSS specification defines 0 as the default, browsers commonly parse the flex shorthand as 0%. This change brings Lynx layout behavior closer to the Web. For details, see Flex basis 0 vs 0%.
Grid, Sticky, and Motion Path
Lynx 3.9 also adds the following CSS capabilities:
- New
grid-rowandgrid-columnshorthands for setting the correspondinggrid-*-startandgrid-*-endvalues. For example,grid-row: 1 / span 2is equivalent togrid-row-start: 1; grid-row-end: span 2. - Standard
position: stickybehavior, matching the Web. - HarmonyOS support for more Motion Path features, including
offset-path,offset-distance, andoffset-rotate.
ReactLynx
ReactLynx brings createPortal and an optimization that reduces both the depth and the number of Snapshots, plus a dual-thread Minimizer on the build side.
Portal
In React, a Portal renders a component subtree into a container elsewhere in the element tree while keeping it in the React tree — the usual answer for overlays, dialogs, and anything that has to escape its surrounding layout. Since @lynx-js/react 0.121.0, Portal is available in ReactLynx through createPortal.
Where React DOM's Portal takes a DOM node as its container, ReactLynx takes a node reference — a NodesRef. Attach a ref to the hosting element to obtain its NodesRef, then pass that as createPortal's second argument. For the other ways to obtain a node reference, see Manipulating Elements.
A Portal only changes where an element is physically placed. In every other way the JSX you render into it behaves as a child of the component that renders it: React Context from the parent tree still reaches it, and state updates behave as they do in any other component. Three notes differ from the Web:
- Event bubbling follows Preact's behavior. ReactLynx's
createPortalbuilds on Preact, where events do not bubble up through Portal components, so events follow the page's actual element structure rather than the React tree. - The portal subtree does not take part in Instant First-Frame Rendering (IFR) — it renders on the background thread only.
- Mounting across pages or across native containers is not supported. For those cases, reach for lynx-ui's
Overlaycomponent, or the<overlay>XElement directly.
Snapshot depth and count optimization
At compile time ReactLynx extracts the static element structure of your JSX into a Snapshot, so at runtime only the dynamic parts inside it need updating. (We will cover Snapshots in detail in a later post.)
In @lynx-js/react 0.120.0 we extended Preact's diff algorithm so a child can declare which slot of its parent it belongs to. That lets the compiler drop the intermediate nodes it used to need, leaving a shallower element tree and fewer Snapshots — less work to render. It is on by default and needs no app-side changes.
Dual-thread Minimizer
On the build side, a dual-thread Minimizer mechanism performs finer-grained compression and tree-shaking across main-thread and background-thread bundles. You can enable the default optimization through optimizeBundleSize, or configure the two separately with Minify.mainThreadOptions and Minify.backgroundOptions.
Misc Updates
Element Updates
<frame> adds the enable-multi-async-thread boolean attribute to control whether embedded Lynx pages use multiple async threads. It also adds preset-height and preset-width for specifying dimensions before content initialization, plus a bindloadmetrics event for tracking frame loading and performance metrics.
Lynx API
Lynx API requestResourcePrefetch gains a new config parameter with awaitComplete and awaitTimeout, so callbacks can run after prefetch fully completes.
Performance
On HarmonyOS, the new native image pipeline is enabled by default. It moves image loading, decode caching, and animated image frame scheduling down into the image node, which reduces per-frame computation and refresh overhead. In our internal image animation benchmark, both total time (TotalTime) and CPU time (CPUTime) dropped by roughly 10%.
Upgrade Guide
To upgrade to Lynx 3.9, follow the official integration guide and update your Lynx dependency versions.
Lynx 3.9 includes some breaking changes, so validate the affected platforms, engineVersion, page switches, and host integration path for your app before upgrading. We will keep the monthly releases smaller, focused, and easier to adopt.
