Installation

The package, its native peers, and the one thing your app has to provide.

bun add @delacour/react-native-charts@alphabunx expo install @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-worklets

The package ships raw .ts and .tsx, and every native module it draws with is a peer. Two copies of Skia or Reanimated register twice and fail at runtime, which is why they are not dependencies.

Do not install native modules with your package manager

bun add react-native-reanimated fetches the newest release, which on any older SDK is a package that fails at the linker rather than at install time. expo install asks the SDK what it supports.

Peer dependencies

PackageRangeNeeded for
@shopify/react-native-skia>=2.6the canvas, paths and text
react-native-reanimated>=4path morphing and the scrub's shared values
react-native-gesture-handler>=2.28the scrub's pan and the pie's tap
react-native-worklets>=0.5the UI-thread functions Reanimated 4 runs
react>=19
react-native>=0.81

d3-scale and d3-shape are the package's only runtime dependencies. They are pure JavaScript and arrive on their own.

The gesture root

Your app needs a GestureHandlerRootView around everything that takes touches. The package does not render one — a nested root is dead weight, and every React Native app that handles touch already has one. If you also use @delacour/react-native-ui, its DelacourProvider is that root.

app/_layout.tsx
import { GestureHandlerRootView } from "react-native-gesture-handler";

export default function RootLayout() {
  return <GestureHandlerRootView style={{ flex: 1 }}>{children}</GestureHandlerRootView>;
}

Without the gesture root a scrub never activates and a pie never reports a tap. Silently — no error, no warning.

The worklets plugin

Reanimated 4 compiles its UI-thread functions with the react-native-worklets/plugin Babel plugin. babel-preset-expo adds it for you once react-native-worklets is installed; a bare React Native app lists it last in babel.config.js.

babel.config.js
module.exports = {
  presets: ["module:@react-native/babel-preset"],
  plugins: ["react-native-worklets/plugin"],
};

Verify

A chart needs a height. The root fills whatever view it is placed in and measures itself with onLayout, so give it a box.

import { CartesianChart, ChartLine } from "@delacour/react-native-charts";
import { View } from "react-native";

const rows = [
  { day: "Mon", revenue: 120 },
  { day: "Tue", revenue: 180 },
  { day: "Wed", revenue: 150 },
  { day: "Thu", revenue: 210 },
  { day: "Fri", revenue: 240 },
];

<View style={{ height: 240 }}>
  <CartesianChart data={rows} xKey="day" yKeys={["revenue"]}>
    <ChartLine color="#0A84FF" curve="monotone" yKey="revenue" />
  </CartesianChart>
</View>;

A chart in a zero-height view draws nothing and says nothing: the plot rect has no area, and ready on the context is false.

Fonts

No font ships with the package. useSystemFont resolves one from the families the operating system already has, so an axis is legible with no expo-font and no config plugin. Call it above the chart and pass the result in — Composition explains why it cannot be called inside.

import { CartesianChart, ChartXAxis, useSystemFont } from "@delacour/react-native-charts";

const font = useSystemFont(undefined, 12);

<CartesianChart data={rows} font={font} xKey="day" yKeys={["revenue"]}>
  <ChartXAxis color="#8E8E93" />
</CartesianChart>;

iOS's system font is not Android's, so the same labels measure differently and the plot rect differs by a few points across platforms. An app that needs identical layout on both loads its own font and passes that instead.

On this page