API reference
Exports of @sigx/lynx-appearance v0.26.0.
Import everything from the package entry:
import {
AppearanceProvider,
useSystemColorScheme,
useSystemColorSchemeMT,
setStatusBarStyle,
setStatusBarBackgroundColor,
setNavigationBarStyle,
setSystemBarsStyle,
isAvailable,
readGlobalColorScheme,
} from '@sigx/lynx-appearance';
The native module is named Appearance and ships for both iOS and Android. No runtime permissions are required. Every native setter resolves (and never rejects) a SetterResult; on hosts where the module is not registered (web preview, SSR, tests, unlinked apps) they resolve { ok: false, reason: 'unsupported' }.
Components
AppearanceProvider
Mount once near the app root, above any consumer of useSystemColorScheme. It renders a <view class={props.class} style={props.style}> wrapping the default slot, seeds a background-side ColorScheme signal from readGlobalColorScheme (falling back to 'light'), exposes it via the useAppearanceContext DI handle, and subscribes to the native APPEARANCE_EVENT to update the signal live on system dark-mode flips. It unsubscribes on unmount.
export const AppearanceProvider = component<AppearanceProviderProps>(
({ props, slots }) => () => JSX,
)
- It is cheap: one signal plus one event subscription.
- The initial value is correct on cold start with no flash, because the native publisher writes
lynx.__globalProps.appearancebefore the main thread's first paint. - On unwired hosts (web preview, tests) the subscription simply never fires and the signal stays at the seeded default.
- See AppearanceProviderProps for props.
Hooks
useSystemColorScheme
Background-thread reactive read of the current system color scheme. Returns the live signal; components and effects that read .value re-render or re-run when the user flips dark mode in system settings.
export function useSystemColorScheme(): PrimitiveSignal<ColorScheme> | Computed<ColorScheme>
- Returns — a signal of
ColorScheme. Read with.value. - If no
AppearanceProvideris in scope, it returns a process-level fallback signal seeded once fromlynx.__globalProps— the value is correct on cold start but does not update live. This lets a consumer such as aThemeProvideruse the hook even without a provider mounted. - Platform: shared (background thread).
useSystemColorSchemeMT
Main-thread synchronous read of the current system color scheme, for use inside 'main thread'-marked worklet bodies. Reads lynx.__globalProps directly via readGlobalColorScheme with no subscription — callers re-evaluate on each worklet invocation.
export function useSystemColorSchemeMT(): ColorScheme
- Returns — the current
ColorScheme. Returns'light'when the publisher has not populated yet (cold start before first publish) or on non-Lynx hosts. - Platform: shared (main thread).
useFontScale
Background-thread reactive read of the OS font scale — the effective text-size multiplier the engine applies to every font-relevant CSS length. 1 is the default.
export function useFontScale(): Computed<number>
- Returns — a stable
Computed<number>. Read.valuein render or effects and the component re-renders when the user changes the system text size. - Re-exported from
@sigx/lynx-core, where it sits next to the native publisher. - Platform: shared (background thread).
The engine already scales plain <text> and font-size. You do not need this to honour Dynamic Type — that happens whether you read it or not. Use it to adapt around larger text: swap a row layout for a stacked one, grow custom-drawn text, size icons to match.
const scale = useFontScale();
return () => (
<view style={{ flexDirection: scale.value > 1.3 ? 'column' : 'row' }}>
…
</view>
);
useFontScaleMT
Main-thread synchronous read of the effective font scale, for 'main thread'-marked worklet bodies. Reads lynx.__globalProps directly with no subscription — callers re-evaluate per worklet invocation.
export function useFontScaleMT(): number
- Returns — the effective scale, or
1when the publisher has not populated yet or on non-Lynx hosts. - Platform: shared (main thread).
readGlobalFontScale
Synchronous, non-reactive read of the raw font-scale map.
export function readGlobalFontScale(): { scale: number; os: number } | null
scale— the policy-clamped effective value the engine applies.os— the raw system setting (Dynamic Type /Configuration.fontScale), equal toscaleunless the app'sfontScale.min/maxconfig clamped it.- Returns
nullwhen the publisher has not populated yet (early cold start) or outside a Lynx host (web preview, SSR, tests). Treatnullas "unknown — assume 1". - Safe on both threads;
__globalPropsis mirrored across both.
Not reactive. A read taken during the render triggered by onFontScaleChanged can still see the previous os, because the globalProps mirror updates independently of the event. Prefer useFontScale for the live effective value and treat os as informational.
Functions
setSystemBarsStyle
Convenience setter that applies the status-bar tint plus (optionally) the status-bar background and nav-bar tint in one call. Fields are optional; omitting any leaves that surface untouched. The order is deterministic: status bar, then status-bar background, then navigation bar.
export async function setSystemBarsStyle(
opts: SystemBarsStyleInput,
): Promise<SetterResult>
opts— aSystemBarsStyleInput; fully partial.- Returns — the aggregate
SetterResult:{ ok: false }with the first non-'unsupported'failure'sreasonif any leg failed, otherwise{ ok: true }.'unsupported'legs (for example, status-bar background on iOS) are intentionally ignored so a partially-supported platform still reports success for the legs that apply. Resolves{ ok: false, reason: 'unsupported' }when no native module is registered. Never rejects. - Platform: shared.
setStatusBarStyle
Set the status-bar content tint (clock and icons).
export function setStatusBarStyle(
style: SystemBarStyle,
): Promise<SetterResult>
style— aSystemBarStyle.'light'= light content (white-ish icons; use behind a dark theme);'dark'= dark content (use behind a light theme).- Returns — a
SetterResult. On iOS the host view controller must forwardpreferredStatusBarStyletoAppearanceModule.preferredStatusBarStylefor this to visibly take effect (the lynx-cli iOS template wires this automatically). Resolves{ ok: false, reason: 'unsupported' }(never rejects) when the native module is not registered. - Platform: shared (effective on iOS + Android).
setStatusBarBackgroundColor
Android only — set the status-bar background color.
export function setStatusBarBackgroundColor(
color: string | null,
): Promise<SetterResult>
color— a color string. Passnull(or omit / pass'transparent') to clear.- Returns — a
SetterResult. iOS resolves{ ok: false, reason: 'unsupported' }, since iOS has no separate status-bar background. On Android 15+ (API 35) edge-to-edge is enforced and this is a no-op at the system level — overlay your own background view inside the safe-area top padding instead. Resolves{ ok: false, reason: 'unsupported' }when the native module is not registered. Never rejects. - Platform: Android only.
setNavigationBarStyle
Android only — set the navigation-bar content tint (style) plus an optional background color.
export function setNavigationBarStyle(
opts: { style: SystemBarStyle; color?: string },
): Promise<SetterResult>
opts.style— aSystemBarStylefor the nav-bar content tint.opts.color— optional background color.- Returns — a
SetterResult. iOS resolves{ ok: false, reason: 'unsupported' }, since there is no separate navigation bar. Resolves{ ok: false, reason: 'unsupported' }when the native module is not registered. Never rejects. - Platform: Android only.
readGlobalColorScheme
Synchronously read the current system color scheme from lynx.__globalProps.appearance.colorScheme.
export function readGlobalColorScheme(): ColorScheme | null
- Returns — the current
ColorScheme, ornullwhen the publisher has not populated yet (early cold start) or when running outside a Lynx host (web preview, SSR, tests). Treatnullas "unknown, fall back to your default theme". Validates the value is exactly'dark'or'light', else returnsnull. - Safe on both the background and main threads, since
__globalPropsis mirrored across both. - Platform: shared (background + main thread).
isAvailable
Quick check for whether the native Appearance module is registered in the current build (delegates to isModuleAvailable('Appearance') from @sigx/lynx-core).
export function isAvailable(): boolean
- Returns —
trueif the module is registered. All setters call this internally to short-circuit to the unsupported result, so you rarely need it directly. - Platform: shared.
Injectables
useAppearanceContext
The DI handle for the appearance context, created via defineInjectable. Its default factory returns null so consumers outside an AppearanceProvider get a clear null signal rather than a phantom 'light' signal that silently never updates.
export const useAppearanceContext = defineInjectable<AppearanceContextValue | null>(
() => null,
)
- Returns — the provided
AppearanceContextValuewhen inside a provider, otherwisenull. - Prefer the
useSystemColorSchemehook, which wraps this with a null-check and fallback signal. - Platform: shared.
Constants
APPEARANCE_EVENT
export const APPEARANCE_EVENT = 'appearanceChanged';
The GlobalEventEmitter event name fired by the native publishers (iOS AppearancePublisher.swift, Android AppearancePublisher.kt) every time the host's system color scheme flips. The payload mirrors the map stored under lynx.__globalProps.appearance (a { colorScheme } object). Exported as a single shared constant so the iOS/Android publishers and the JS listener agree on one string. Typed as the literal 'appearanceChanged'.
FONT_SCALE_EVENT
export const FONT_SCALE_EVENT = 'onFontScaleChanged';
The global event fired by the Lynx engine — not a sigx publisher — whenever the host calls LynxView.updateFontScale(). Payload: { scale }, the new effective scale. The name is engine-owned upstream.
FONT_SCALE_GLOBAL_KEY
export const FONT_SCALE_GLOBAL_KEY = 'fontScale';
The key under lynx.__globalProps where the native FontScalePublisher writes the font-scale map before the main thread's first paint — so a cold start renders at the right size rather than correcting after.
GLOBAL_PROPS_KEY
export const GLOBAL_PROPS_KEY = 'appearance';
The key under lynx.__globalProps where the native publisher writes the appearance map. A single string shared by the iOS/Android publishers, the JS reader, and tests. Typed as the literal 'appearance'.
Types
ColorScheme
The two color schemes the platform reports. The iOS unspecified and Android UI_MODE_NIGHT_UNDEFINED states both collapse to 'light' at the native publisher boundary, so JS only ever sees these two values.
export type ColorScheme = 'light' | 'dark';
SystemBarStyle
Tint of system-bar content (clock, icons), not its background. 'light' = light-colored icons (legible on a dark background); 'dark' = dark-colored icons.
export type SystemBarStyle = 'light' | 'dark';
SystemBarsStyleInput
Argument to setSystemBarsStyle. Fully partial — omitted fields are left alone.
export interface SystemBarsStyleInput {
/** Status-bar content tint. `'light'` = light icons (legible on dark bg). */
statusBar?: SystemBarStyle;
/** Android only — status-bar background color. `null` clears (transparent). */
statusBarBackground?: string | null;
/** Android only — navigation-bar tint + optional background. */
navigationBar?: { style: SystemBarStyle; color?: string };
}
statusBar— status-bar content tint.statusBarBackground— Android only; status-bar background color,nullclears to transparent.navigationBar— Android only; nav-bar tint plus an optional background color.
SetterResult
The result envelope returned (resolved, never rejected) by every native setter.
export interface SetterResult {
ok: boolean;
/** Present when `ok === false` — `'unsupported'` on iOS for nav-bar and
* status-bar-background calls, or an Android failure message. */
reason?: string;
}
ok— indicates success.reason— present only whenok === false. It is'unsupported'on iOS for nav-bar / status-bar-background calls and on any platform when the module is not registered, or an Android failure message (for example,'No hosting Activity found').
AppearanceContextValue
The DI shape exposed by AppearanceProvider. Holds a readonly background-side reactive colorScheme signal that re-renders consumers when the system color scheme flips.
export interface AppearanceContextValue {
/** BG-side reactive color scheme. Re-renders consumers on system flip. */
readonly colorScheme: PrimitiveSignal<ColorScheme>;
}
AppearanceProviderProps
Props for AppearanceProvider: an optional class, an optional style, and a default slot for children.
export type AppearanceProviderProps =
& Define.Prop<'class', string, false>
& Define.Prop<'style', Record<string, string | number>, false>
& Define.Slot<'default'>;
class— optional class string for the host view.style— optional inline style for the host view.defaultslot — your app content.
RawAppearanceProps
Shape of the raw appearance map the native publisher writes under lynx.__globalProps.appearance.
export interface RawAppearanceProps {
colorScheme?: ColorScheme;
}
colorScheme— optionalColorScheme.
