Skip to content

System UI

@symbiote-native/system-ui wraps expo-system-ui — setting and reading the root view’s background color — so every SymbioteNative adapter can reach it, not just React. Like device and local auth, both exports here are one-shot async calls with no per-instance state, so there is no hook/composable/service to wrap — the React, Vue, Angular, Svelte, and Solid entry points are plain re-exports of the same core.

OS platform Support
iOS ✅ live
Android ✅ live
Framework adapter Support
React ✅ live
Vue ✅ live
Angular ✅ live
Svelte ✅ live
Solid ✅ live
Terminal window
npm install @symbiote-native/system-ui

Scaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --system-ui (or add --system-ui in an existing app) installs and wires this for you — see @symbiote-native/cli.

expo-system-ui and expo-modules-core come along as regular dependencies, pinned to exact versions — never install either yourself, and never add the expo meta-package to your project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).

expo-system-ui needs no runtime permission on either platform — it only sets/reads a stored background color, nothing gated by a permission prompt.

All five adapters — React, Vue, Angular, Svelte, Solid — re-export the exact same two functions; there is no per-adapter hook/composable/service to reach for, since nothing here holds live state or a subscription.

import { useEffect } from 'react';
import { setBackgroundColorAsync } from '@symbiote-native/system-ui/react';
export default function RootScreen() {
useEffect(() => {
void setBackgroundColorAsync('black');
}, []);
return (
<view>
<text>Root background set to black</text>
</view>
);
}
Signature Description
setBackgroundColorAsync(color: ColorValue | null): Promise<void> Changes the root view background color. Call this outside of your component tree (e.g. in the root file), since it affects the whole app. null clears the override rather than setting an actual color
getBackgroundColorAsync(): Promise<ColorValue | null> Gets the current root view background color, in hex format. null if the background color is not set
  • setBackgroundColorAsync(null) clears the override rather than setting an actual color — it’s passed straight through to the native module without running processColor.
  • On web, the raw ColorValue is passed through untouched instead of being run through RN’s processColor — matching upstream, since processColor is a native-color-parsing step that doesn’t apply on that platform.
  • On iOS/Android, a non-null color is run through RN’s own processColor before reaching the native module — the same conversion RN’s style pipeline uses internally.
  • Where do I call setBackgroundColorAsync? In the root file, outside any component, so the color is set before the first frame and no flash shows during transitions.
  • How do I follow dark and light mode? Read the color scheme and call it again when it changes.
  • getBackgroundColorAsync returns null. No root background color has been set yet.
  • Can I set userInterfaceStyle at runtime? No. The Android style and iOS background color are native build settings and need a new binary.

Sources: Expo docs: SystemUI, Evan Bacon: stack-based root background component.

@symbiote-native/system-ui ships zero React/Vue/Angular/Svelte/Solid logic in expo-system-ui itself — its two functions are hand-ported, verbatim, into this package’s own core/, resolving the native module through expo-modules-core’s requireNativeModule rather than the expo meta-package this project never installs:

packages/system-ui/src/
├── core/ # framework-agnostic: setBackgroundColorAsync/getBackgroundColorAsync;
│ # native-module.ts resolves the native module via expo-modules-core's
│ # requireNativeModule
├── react/ # @symbiote-native/system-ui/react — export * from '../core'
├── vue/ # @symbiote-native/system-ui/vue — export * from '../core'
├── angular/ # @symbiote-native/system-ui/angular — export * from '../core'
├── svelte/ # @symbiote-native/system-ui/svelte — export * from '../core'
└── solid/ # @symbiote-native/system-ui/solid — export * from '../core'

Same shape as device’s and local auth’s adapter entries: single-file re-exports with no lifecycle code at all, since every export here is a stateless one-shot async call — there is nothing for a hook, composable, or service to subscribe to or clean up. The native code itself is never vendored or copied — expo-modules-autolinking resolves it straight out of node_modules (see the native setup guide).