Installation

Install the package, wire up Tailwind, and mount the root provider.

Or copy the source in instead

delacour writes the components into your own repository, so you own and edit button.tsx rather than upgrading a package. Same components, different delivery — bunx delacour@latest init. This page covers the package.

Install

The library ships raw .tsx — Uniwind's transform must run in your app's Metro pipeline, so a precompiled build would arrive with its classNames already dead. That means its native dependencies are peers, not dependencies: two copies of a native module register twice and break at runtime.

npm install @delacour/native-ui

Peer dependencies

Required:

bun add react-native-reanimated react-native-worklets react-native-gesture-handler \
  react-native-safe-area-context react-native-keyboard-controller react-native-svg \
  react-native-pulsar uniwind tailwindcss @gorhom/bottom-sheet \
  @central-icons-react-native/round-outlined-radius-1-stroke-1.5

Optional — resolved only if you import the subpath that needs them:

PackageNeeded by
@legendapp/listScreen.LegendList, Screen.ChatList (the default legend variant)
expo-router@delacour/native-ui/expo/navigation-theme
react-native-screensnative stack navigation

Wire up the CSS

Import the library's styles into your app's Tailwind entry, then point Tailwind's scanner at the package source.

global.css
@import "@delacour/native-ui/styles";
@source "../../../../packages/native-ui/src";

Use the real workspace path

Bun symlinks workspace packages, and Tailwind's scanner cannot follow symlinks. A @source pointing at node_modules/@delacour/native-ui resolves to nothing, and every class the library uses is silently dropped from your production build. Point it at the actual directory.

The @delacour/native-ui/styles barrel is three files: base.css (tailwindcss + uniwind), tokens.css (the @theme scale) and theme.css (the light/dark palette). Import them individually if you need to reorder them.

Mount the provider

Wrap your app's root — once, around everything.

app/_layout.tsx
import { DelacourProvider } from "@delacour/native-ui/provider";

export default function RootLayout() {
  return <DelacourProvider>{children}</DelacourProvider>;
}

DelacourProvider is four layers, outermost first: GestureHandlerRootViewSafeAreaProviderKeyboardProvider<KeyboardStateSync /> beside the children. The order is load-bearing — see Provider for what breaks at each layer.

Without the gesture root, presses stop landing. Silently — no error, no warning.

Composing the layers by hand

Only if your app already has a root stack of its own. <KeyboardStateSync /> is required, not optional polish, and must be a child of KeyboardProvider.

import { KeyboardStateSync } from "@delacour/native-ui/hooks/use-keyboard-state-sync";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { KeyboardProvider } from "react-native-keyboard-controller";
import { initialWindowMetrics, SafeAreaProvider } from "react-native-safe-area-context";

<GestureHandlerRootView>
  <SafeAreaProvider initialMetrics={initialWindowMetrics}>
    <KeyboardProvider>
      <KeyboardStateSync />
      {children}
    </KeyboardProvider>
  </SafeAreaProvider>
</GestureHandlerRootView>;

Import from subpaths

There is no package-wide barrel, and that is deliberate: it is what lets an app skip resolving optional peers it never uses.

import { Button } from "@delacour/native-ui/button";
import { Icon } from "@delacour/native-ui/icon";
import { IconHeart } from "@delacour/native-ui/icons/central";
// This does not exist, and will not be added.
import { Button } from "@delacour/native-ui";

Verify

import { Button } from "@delacour/native-ui/button";

<Button onPress={() => console.log("pressed")}>Press me</Button>;

A string child is wrapped in a Button.Label for you. If the button renders but does not respond, the gesture root is missing above it.

On this page