orn-ui
← All components

Getting Started

Three steps: install, wrap your app in UIProvider, then import components. Skipping step 2 is the single most common way to break orn-ui — every hook and every component reads theme, icons and labels from it.

1. Install

Either the whole package, or just the components you need via the CLI (no npm dependency for the second option — see each component page for its own command).

pnpm add orn-ui
/
npx orn-ui add button

Compatibility

Verified on Expo SDK 54, 55, 56 and 57 — every one of them, not just the newest. Each SDK gets its own sandbox with that release's exact react-native and react, and both the type check and the full test suite run against it in CI. Bare React Native works the same way: nothing in the library imports Expo.

Expo SDKreact-nativereact
540.81.519.1.0
550.83.1019.2.0
560.85.319.2.3
570.86.219.2.3

Declared as peerDependencies: react-native >=0.81.0 and react >=19.1.0. react-native-safe-area-context >=5.4.0 is optional — only orn-ui/safe-area imports it.

No native build required: orn-ui ships no native modules, so it runs inside Expo Go on every SDK listed here.

2. Wrap your app in UIProvider

UIProvider is the single entry point of the library. It resolves the active theme (system or manual override) and injects icons, safe-area insets and default labels to the whole tree. Mount it once, above your navigator — root of App.tsx is the usual place.

import { UIProvider } from 'orn-ui';

export default function App() {
  return (
    <UIProvider>
      {/* your navigator / screens go here */}
    </UIProvider>
  );
}
Every orn-ui component and hook (useColors, useTheme, Button, Input, all of them) throws "this hook must be used within a <UIProvider>" if rendered outside one. There is no fallback and no silent default — it fails loudly at runtime, not at build time.

UIProvider props

NameTypeDefaultDescription
childrenReact.ReactNodeYour app — mounted once, above the navigator.
theme?ThemePairdefaultThemeLight/dark pair. Build one with createTheme().
defaultMode?'system' | 'light' | 'dark''system'Initial mode, uncontrolled.
mode?'system' | 'light' | 'dark'Controlled mode — pass with onModeChange to manage it yourself.
onModeChange?(mode: ThemeMode) => voidCalled on toggle when mode is controlled.
icons?IconRendererrenderDefaultIconIcon renderer. Defaults to the zero-dep glyphs bundled with orn-ui.
insets?EdgeInsetszeroInsetsSafe-area insets, typically from useSafeAreaInsets().
labels?Partial<Labels>defaultLabelsOverride any built-in string (Close, Cancel, Search…).
allowFontScaling?booleanfalseOff by default, for parity across the library.

Recommended: SafeAreaUIProvider

UIProvider defaults insets to {top:0,bottom:0,left:0,right:0} — harmless until you open a full-screen Modal (slides under the notch) or a BottomSheet (sits flush against the gesture bar). SafeAreaUIProvider is UIProvider with useSafeAreaInsets() already wired in — use it instead of UIProvider and this is handled.

It lives in its own subpath (orn-ui/safe-area), on purpose: it is the only file in the library that imports a third-party package (react-native-safe-area-context). Nothing in the main orn-ui entry point references it, so a plain import { Button } from 'orn-ui' never pulls it in — the rest of the library stays zero-dependency.

Needs react-native-safe-area-context as a peer (optional — only required if you use this import):

pnpm add react-native-safe-area-context
import { SafeAreaUIProvider } from 'orn-ui/safe-area';

export default function App() {
  return (
    <SafeAreaUIProvider>
      {/* your navigator / screens go here */}
    </SafeAreaUIProvider>
  );
}
Already mounting a <SafeAreaProvider> higher up (some React Navigation templates do)? Pass mountSafeAreaProvider={false} — a nested provider measures its own View's frame, not the window's, so insets read zero from inside it.

SafeAreaUIProviderProps

Plus every UIProvider prop above except insets (still accepted, but optional — it overrides the measured value instead of being required).

NameTypeDefaultDescription
insets?EdgeInsetsmeasured via useSafeAreaInsets()Explicit override — wins over the measured value. Useful for tests, Storybook, or a screen that does not fill the window.
mountSafeAreaProvider?booleantrueAlso mount <SafeAreaProvider>. Set false if your app already has one above.

3. Use components

Now every component page on this site works as shown — copy a variant snippet, it renders. See the component list below.

Browse components →