React: Everything needed to write a SymbioteNative app with React
# What is SymbioteNative?
> A framework-agnostic renderer that lets non-React frameworks drive real React Native Fabric views.
SymbioteNative is a framework-agnostic renderer for real native iOS and Android apps. It keeps React Native’s native stack — Fabric, JSI, Yoga, Hermes, and the host platforms — and replaces only the JavaScript renderer that talks to Fabric. React is not privileged inside React Native’s renderer. Fabric exposes a JSI-bound mutation surface through `global.nativeFabricUIManager`. React Native’s React renderer is one client of that surface. SymbioteNative lets other framework adapters become clients too. ## The shortest version [Section titled “The shortest version”](#the-shortest-version)
```txt
React · Vue · Angular · Svelte · Solid
↓
thin framework reconciler
↓
@symbiote-native/engine
↓
nativeFabricUIManager
↓
stock React Native Fabric
```
The output is still native Fabric views. SymbioteNative is not a WebView, not a React Native fork, and not a compatibility shim that pretends React components are framework-agnostic. ## Why this exists [Section titled “Why this exists”](#why-this-exists) React Native has an excellent native runtime, but most of the ecosystem can only use it through React. If you write Vue, Svelte, Solid, or Angular, your choices usually collapse to a WebView, a rewrite, or a separate native abstraction. SymbioteNative opens the renderer seam that already exists in Fabric and gives each UI framework a thin adapter over one shared native engine. ## What works today [Section titled “What works today”](#what-works-today) * React is the reference adapter and canary, live on the framework switcher. * Vue 3 proves the same engine can be driven by a non-React framework, also live on the switcher. * Angular is a real, tested adapter too, also live on the switcher — see the [status page](/docs/project/status/) for what’s still catching up. * Svelte is a real, tested adapter too — a DOM shim over stock compiled Svelte output, verified on-device on iOS and Android, also live on the switcher. * Solid is a real, tested adapter too, a Solid custom renderer over `solid-js/universal`’s own `createRenderer` with full component parity, also live on the switcher, verified on device like the other four adapters (see the [status page](/docs/project/status/)). * The engine owns mutation-to-Fabric persistence, event normalization, and commit batching. * The native core remains stock React Native. Start with the [quick start](/docs/quick-start/) or read [how it works](/docs/how-it-works/).
# Use with an AI agent
> Feed SymbioteNative's docs to Claude Code, Cursor, Codex or Copilot — llms.txt corpora, per-page Markdown, and a paste-ready rules block.
Every coding agent was trained before SymbioteNative existed. Asked for a native screen it writes React Native: a `View` from `react-native`, `StyleSheet.create`, a `react-native-*` component library. All three are wrong here, and nothing in its training says so. Give it a corpus, the Markdown twin of the page it needs, and a rules file. ## Hand it the docs [Section titled “Hand it the docs”](#hand-it-the-docs) | URL | What it is | Size | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------- | -------- | | [`/llms.txt`](https://docs.symbiote-native.dev/llms.txt) | The index — every set below, described so an agent can pick | \~4 KB | | [`/llms-full.txt`](https://docs.symbiote-native.dev/llms-full.txt) | The entire documentation as one Markdown file | \~900 KB | | [`/llms-small.txt`](https://docs.symbiote-native.dev/llms-small.txt) | The same, with non-essential content stripped | \~800 KB | | [`/_llms-txt/react.txt`](https://docs.symbiote-native.dev/_llms-txt/react.txt) | One framework only — swap `react` for `vue`, `angular`, `svelte`, `solid` | \~260 KB | Prefer the single-framework set. Five adapters’ worth of near-identical examples in one context window is how an agent writes Vue’s `@press` into a React file.
```sh
# Claude Code, Codex, Gemini CLI — anything that can read a URL
> Read https://docs.symbiote-native.dev/_llms-txt/vue.txt before you touch this project.
```
## Any page as Markdown [Section titled “Any page as Markdown”](#any-page-as-markdown) Append `.md` to any docs URL for that page’s source, no HTML chrome:
```plaintext
https://docs.symbiote-native.dev/docs/learn/vue/ -> .../docs/learn/vue.md
```
The same thing sits under every page title: a Copy for agent button, a Markdown link, and a one-click hand-off to ChatGPT, Claude, Cursor or Copilot. ## Pin the rules [Section titled “Pin the rules”](#pin-the-rules) A corpus is read once; a rules file stays in front of the agent. Paste this into `AGENTS.md`, `CLAUDE.md`, or `.cursor/rules/symbiote.md` at your project root:
```md
# SymbioteNative
This app renders real native iOS/Android views through SymbioteNative, not
through React Native's own renderer and not through a WebView. Docs:
https://docs.symbiote-native.dev/llms.txt
- Import from `@symbiote-native/` (`react` | `vue` | `angular` |
`svelte` | `solid`). NEVER import from `react-native` in app code.
- `react-native` (>=0.86) and `react` still stay top-level dependencies of the
app — they are the runtime singleton and the Metro version anchor. Do not
remove them, do not import from them.
- Primitives are lowercase intrinsic tags, not imported components: `view`,
`text`, `pressable`, `image`, `image-background`, `scroll-view`, `text-input`,
`switch`, `activity-indicator`, `safe-area-view`, `modal`, `refresh-control`,
`input-accessory-view`, `button`, `touchable-opacity`,
`touchable-native-feedback`, `touchable-without-feedback`,
`touchable-highlight`, `sticky-header`. React/Vue/Svelte/Solid need no import;
Angular needs `imports: [SYMBIOTE_ELEMENTS]` on the component.
- Events follow the framework's own idiom: `onPress` (React, Svelte, Solid),
`@press` (Vue), `(press)` (Angular).
- `pressable` has no render-prop/slot child. Track pressed state yourself with
`onPressIn`/`onPressOut` (Angular: `(pressIn)`/`(pressOut)`).
- `FlatList`, `SectionList`, `VirtualizedList`, `VirtualizedSectionList` and
`KeyboardAvoidingView` ARE imported from the adapter package — their render
item returns a framework element, which a tag cannot express.
- Style with a CSS class (`className` on React, `class`/`[class]` elsewhere,
or an SFC `
```
* **iOS** — a `BootSplash.storyboard` file alongside the app’s existing storyboards. Re-run the same command (with `--brand`/`--dark-*` if you have a generator license key) any time the logo or colors change — it’s idempotent, not a one-shot scaffold. ## 3. Wire the generated theme into your native entry points [Section titled “3. Wire the generated theme into your native entry points”](#3-wire-the-generated-theme-into-your-native-entry-points) The generator does **not** do this part — it writes the theme/storyboard, but your app’s own `MainActivity`/`AppDelegate`/`Info.plist` still have to reference them. ### Android — `MainActivity.kt` [Section titled “Android — MainActivity.kt”](#android--mainactivitykt) Call `RNBootSplash.init` in `onCreate`, **before** `super.onCreate`:
```kotlin
import android.os.Bundle
import com.zoontek.rnbootsplash.RNBootSplash
class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
RNBootSplash.init(this, R.style.BootTheme)
super.onCreate(savedInstanceState)
}
// ...
}
```
### iOS — `AppDelegate.swift` + `Info.plist` [Section titled “iOS — AppDelegate.swift + Info.plist”](#ios--appdelegateswift--infoplist) Call `RNBootSplash.initWithStoryboard` from `customize(_:)`:
```swift
import RNBootSplash
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
override func customize(_ rootView: RCTRootView) {
super.customize(rootView)
RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
}
}
```
Then point `Info.plist`’s launch-screen key at the generated storyboard:
```diff
UILaunchStoryboardName
LaunchScreen
BootSplash
```
Run `pod install` inside `ios/` after adding the dependency — `RNBootSplash`’s native pod only links once CocoaPods has resolved it. Verify the native binary is actually wired Skipping the Android `RNBootSplash.init` call is **not** silent in JS: calling `hide()` / `isVisible()` before it runs throws `Error: react-native-bootsplash has not been initialized` (a real, frequently-reported setup mistake — see [zoontek/react-native-bootsplash#227](https://github.com/zoontek/react-native-bootsplash/issues/227)). A missed iOS storyboard/`Info.plist` step, by contrast, IS silent — the screen just shows the OS default (blank) or the wrong storyboard. Rebuild and run on a real device/simulator after this step and confirm your logo/background shows at cold launch before moving on to step 4. ## 4. Hide it from JS once your app is ready [Section titled “4. Hide it from JS once your app is ready”](#4-hide-it-from-js-once-your-app-is-ready) With the native binary wired, `hide()` is the simple case — call it once your JS tree has mounted: * React
```tsx
import { useEffect } from 'react';
import { hide } from '@symbiote-native/splash-screen/react';
useEffect(() => {
hide();
}, []);
```
* Vue
```vue
```
* Angular Called once from the root component’s `ngOnInit`:
```ts
import { Component, OnInit } from '@angular/core';
import { hide } from '@symbiote-native/splash-screen/angular';
@Component({ /* ... */ })
export class App implements OnInit {
ngOnInit(): void {
hide();
}
}
```
* Svelte
```svelte
```
* Solid
```tsx
import { onMount } from 'solid-js';
import { hide } from '@symbiote-native/splash-screen/solid';
onMount(() => {
hide();
});
```
If you want a fade transition gated on real readiness (layout committed + logo/brand images loaded + your own `ready` flag) instead of an immediate cut, reach for `useHideAnimation` — covered with full React/Vue/Angular/Svelte/Solid examples on the [package page](/docs/packages/splash-screen/#the-animated-case-usehideanimation). ## Known upstream gotchas [Section titled “Known upstream gotchas”](#known-upstream-gotchas) These are `react-native-bootsplash` behaviors this wrapper inherits as-is — worth knowing before you file a bug against `@symbiote-native/splash-screen` itself: * **Dark mode follows the OS appearance setting, not an in-app theme toggle.** `darkBackground`/ `darkLogo` in the manifest are picked based on `getConstants().darkModeEnabled` — the *system* dark-mode flag read before JS runs, at native paint time. If your app has its own theme switcher independent of the OS setting, the native splash can briefly flash the *other* color scheme for a frame before your JS-driven UI repaints ([zoontek/react-native-bootsplash#743](https://github.com/zoontek/react-native-bootsplash/issues/743)). There is no JS-side fix — it’s a property of native paint happening before any JS runs. * **Android: relaunching via a notification can re-show, and occasionally get stuck on, the boot theme**, with `isVisible()` reporting `false` and `hide()` having no visible effect in that stuck state — an open upstream edge case, not something this wrapper can route around ([zoontek/react-native-bootsplash#736](https://github.com/zoontek/react-native-bootsplash/issues/736)). If you see a splash reappear only on notification-triggered launches, this is why. ## Recap [Section titled “Recap”](#recap) | Step | What | Where | | ---- | ------------------------------------------------------------ | ---------------------------------------------------- | | 1 | Install the wrapper | `package.json` | | 2 | Generate the binary (assets + native config) | `npx symbiote-splash-screen generate` | | 3 | Wire the generated theme/storyboard into native entry points | `MainActivity.kt`, `AppDelegate.swift`, `Info.plist` | | 4 | Hide it from JS | `hide()` / `useHideAnimation` in app code | Steps 1–3 are native, one-time setup per app; step 4 is the only part that lives in your framework code, and it’s identical in shape across React, Vue, Angular, Svelte, and Solid.
# How to: style a component
> Styling with a CSS class or CSS Modules (preferred), or StyleSheet.create.
You need to style a native view and want the shortest path for your framework. ## Any adapter — a plain CSS class [Section titled “Any adapter — a plain CSS class”](#any-adapter--a-plain-css-class) A standalone `.css`/`.module.css` file import (`className` on React, `class`/`[class]` on Vue/Angular/Svelte/Solid) works identically on every adapter — this is what every current example app (`examples/react`, `examples/vue-sfc`, `examples/vue-tsx`, `examples/angular`, `examples/svelte`, `examples/solid`) actually does:
```tsx
import './App.css';
Native surface
;
```
`@symbiote-native/css-parser` compiles the rule at build time; `className="card"` resolves it back at render time through a runtime class registry shared by every adapter. Plain CSS, CSS Modules, plus optional SCSS/Sass, Less, and Stylus preprocessing (`
```
```svelte
Native surface
```
## The alternative — `StyleSheet.create` [Section titled “The alternative — StyleSheet.create”](#the-alternative--stylesheetcreate) Every adapter re-exports `StyleSheet` from `@symbiote-native/engine`. It’s still fully supported and needs no build-time CSS step — reach for it for a value that’s genuinely computed at runtime, or if you’d rather skip CSS entirely:
```tsx
import { StyleSheet } from '@symbiote-native/react';
const styles = StyleSheet.create({
card: { padding: 16, borderRadius: 12, backgroundColor: '#111827' },
});
Native surface
;
```
`StyleSheet.create()` is identity at runtime — the engine flattens plain objects too. Its value is preserving literal types and giving the file one predictable style block at the bottom, not a different rendering path from CSS. ## Want typo-safe `.module.css` keys [Section titled “Want typo-safe .module.css keys”](#want-typo-safe-modulecss-keys) A bare `.module.css` import type-checks as `Record` unless you wire two pieces from `@symbiote-native/css-parser` — add it as a devDependency:
```sh
pnpm add -D @symbiote-native/css-parser
```
package.json
```json
{ "scripts": { "pretypecheck": "css-dts ." } }
```
tsconfig.json
```json
{
"compilerOptions": {
"plugins": [{ "name": "@symbiote-native/css-parser/typescript-plugin" }]
}
}
```
`pretypecheck`’s `css-dts .` writes a real, narrowed `.d.ts` next to every `.module.css` file so a typo fails `tsc`; the `typescript-plugin` gives you matching autocomplete live in the editor, no watch process needed. See the [Styling guide](/docs/learn/styling/#real-key-narrowing-css-dts-and-the-typescript-plugin) for what each piece covers. ## Choose based on your adapter [Section titled “Choose based on your adapter”](#choose-based-on-your-adapter) * Want CSS syntax → a standalone `.css`/`.module.css` file import works on every adapter; Vue and Svelte additionally support an inline `