Skip to content

Platforms

React Native hosts

Host registration, Fabric evidence, text boundaries and platform limitations.

packages/toned-react/NATIVE-HOSTS.md ↗Rendered from the package’s Markdown

Toned owns declaration resolution and differential patch requests. The application owns its native renderer, concrete host components and committed parent topology. A method called setNativeProps does not prove any of those contracts.

Install a nativeHost adapter alongside the native binding configuration. An adapter declares its renderer/version, validates actual host identity, applies merge patches and supplies reset values for removed style fields and host props. The native binding registers accepted refs only during commit and retains that registration until deferred ownership cleanup completes. Two controllers may share one host only through the same adapter instance.

TypeScript
import { defineReactNativeHost } from '@toned/core/stylesheet'
import native from '@toned/react/react-native'

const nativeHost = defineReactNativeHost({
  renderer: 'fabric',
  version: appReactNativeVersion,
  // Integration-owned identity check; never just `'setNativeProps' in value`.
  isHost: isApplicationFabricHost,
})
const config = { ...native, nativeHost, resolveElement: applicationElements }

defineReactNativeHost supplies the React Native null-reset/merge-patch protocol. It does not certify a version merely because a caller passes that version string. Custom renderers supply their own NativeHostAdapter, including different reset values if needed. Caller baselines come from committed host props; native nodes do not expose a reliable generic style readback API. Other imperative writers must coordinate ownership through the same integration.

Verified Android profile

The Fabric acceptance app runs Toned's built JavaScript against real Android hosts. The verified profile is React Native 0.86.0 / React 19.2.3 / Fabric / Hermes / Android 16 API 36 / arm64. This is a versioned application adapter, not a claim that every Fabric release, platform or object exposing setNativeProps is supported.

The latest acceptance run passes eight automatic scenarios and a real emulator-driven press/release gesture, with 69 assertions. Native measurements, Android drawing paint and hint-colour readback establish the result; requested JavaScript patches are not substituted for those observations. The retained acceptance evidence records the exact runtime, emulator fingerprint, frozen-source input/APK hashes and assertions from the final device execution.

ContractDevice evidence
Primitive mountingReal View, Text, TextInput and Pressable dimensions; only connected native Element refs are accepted, and a method-forwarding composite is rejected.
Direct interaction updatesNative focus/blur and physical press/release change layout/opacity and restore resting values. Focus does not require a React primitive commit.
Caller ownership and removalRemoving imperative minHeight reveals the caller's changed height; placeholder removal restores the platform default; explicit caller hint colours survive active state and later caller commits.
AppearanceNative text drawing paint changes/restores colour and alpha. RN Text uses spans, so the probe resolves the first glyph's paint rather than reading the unused TextView default.
Concurrent renderingAn actually suspended variant render leaves the committed native width unchanged; resolving it updates the same host.
Portable motionNative width/alpha during entry, interruption, reduced-motion settlement and retained exit; removed minHeight resets; frames do not require React commits, and disposal clears scheduled work/subscriptions. The test drives the JS frame clock deterministically and does not certify UI-thread animation.
Adaptive layoutsActual parent onLayout measurements select finite row/stack variants, retain the current layout through hysteresis, respond to an explicit text-scale input and preserve both native child refs.
Host lifetimeVariant updates and callback-ref handoffs preserve the mounted host; cleanup balances; detaching one of two controllers preserves the survivor's active style and future updates.

The APK bundles JavaScript with dev support disabled and has no Internet permission. The runner checks installed permissions, waits for Android's install broadcasts, starts its own read-only emulator and requires every scenario plus the native gesture to pass in one mounted run. It records an input/APK receipt and rejects stale builds. The isolated consumer has one exact React installation outside the embedded checkout. See the app README for the repeatable command.

The native reporter is test instrumentation, not a library dependency. It reads mounted Android views on the UI thread; Toned still writes through the declared setNativeProps/null-reset adapter. Native mount latency is retried only for an explicit NOT_MOUNTED response and has a bounded deadline.

Scope of that evidence

The verified profile covers the contracts listed above. It does not establish:

  • iOS, Paper, other React Native versions or external styling engines. Toned ships no Unistyles integration; see INTEGRATIONS.md for composition boundaries.
  • Native relationship topology, virtualized-list recycling, arbitrary semantic state readers, rotation/viewport acceptance, general container-query semantics, UI-thread animation or native grid. The adaptive test covers its own measured-parent input adapter; it does not establish those other protocols. Library fixture tests cover the declared protocols; each concrete integration needs its own native scenarios before advertising those capabilities.
  • A frame-by-frame absence of transient paint between all possible native commits. The app checks the final native result and the suspended committed tree; its bounded readback polling is not a native drawing-frame trace.

For relationships, a host supplies committed parent links (parentOf), topology notifications (subscribeTopology) and semantic state readers as needed. Modern RN primitive refs provide public node traversal, which can help implement parent lookup; traversal alone does not notify the library of committed moves or certify portals, recycling and cleanup. The core does not invent a missing observer.

The Expo demo is an integration sketch, not an acceptance target; support claims rest only on the pinned app above. @toned/react has no React Native dependency and ships no View/Text implementation. Applications choose their concrete primitives and adapter. Fixtures beside native-host.ts, applyStyles.ts and native-patches.test.ts are unit coverage, distinct from this renderer evidence.

Native grid

Native grid is unavailable: grid declarations are rejected on native. The native host must expose a layout engine with track and placement mutation in its native measurement lifecycle. Computing a parallel JS rectangle layout would violate the selected architecture and diverge from intrinsic native text measurement. React context supplies ownership/topology, not a replacement layout engine.

Upstream Yoga grid work is not an integrated React Native host contract. A host must expose the needed bindings and pass track, placement, intrinsic measurement and responsive-update conformance before Toned can enable native grid. Web grid is available independently.

Native viewport queries require getViewportWidth and subscribeViewport on the host adapter. Widths are logical layout units; changes publish condition facts to committed controllers without React renders. Numeric thresholds and explicit px widths are supported. Raw CSS queries and CSS-dependent units such as rem are rejected with a named diagnostic: a native declaration must express the corresponding semantic condition, rather than asking a missing browser to parse it. Container-only sheets do not require viewport capability. No native path falls back to a missing window.matchMedia.

Semantic state facts

Ordinary local states and source-part conditions use the same committed native behavior facts as relationships. hover, active and focus come from primitive events. Other public state names/aliases require nativeHost.readState(host, name) and subscribeState(notify); subscribeTopology remains a compatible notification fallback. Ordinary state rules require no parent traversal. An optional states list declares the supported names; otherwise the reader must throw for unsupported names. Readers receive public names such as selected, not web selector strings. focus-visible requires the integration's input-modality decision and must never be silently substituted with focus.

Readers run only for committed hosts. Notifications update controller facts and host patches directly, without rendering React components. Repeated hosts retain their own semantic state; subscriptions transfer at committed candidate handoff and stop on disposal. Native host acceptance should exercise state toggling, source-part targets, sibling isolation and missing capability errors in addition to the mounting/ownership gate above. These are library fixture results until a concrete application runs that gate on its chosen renderer.