High-Performance Graphics With React Native Skia (2026 Guide)

A practitioner guide to React Native Skia 2.11.2: canvas model, paths, images, shaders, Reanimated-driven animation, performance traps, and what Shopify’s handover means.

High-Performance Graphics With React Native Skia (2026 Guide)

React Native Skia gives you the same 2D graphics engine that draws Chrome and Flutter, exposed as React components you render inside a <Canvas>. If you have ever tried to build a custom chart, an animated gradient, a signature pad or a frosted-glass panel out of <View> and transform, this is the library that ends that fight. It is also the library whose ownership changed this month, so we will cover that honestly at the end rather than pretending it did not happen.

Everything here is against @shopify/react-native-skia 2.11.2, the current release as of September 2026, running on React Native 0.87 with Reanimated 4.

What React Native Skia actually is

Skia is Google’s 2D rasteriser. It is the thing underneath Chrome, Android’s graphics stack and Flutter. React Native Skia binds it to React with its own React reconciler, which is the detail that explains everything else about the library.

When you write this:

import { Canvas, Circle, Group } from "@shopify/react-native-skia";

const Hello = () => (
  <Canvas style={{ flex: 1 }}>
    <Group blendMode="multiply">
      <Circle cx={64} cy={64} r={64} color="cyan" />
      <Circle cx={128} cy={64} r={64} color="magenta" />
    </Group>
  </Canvas>
);

the <Canvas> is a real React Native view. Nothing inside it is. Circle and Group are drawing instructions for a second renderer, not host components. You cannot put a <Text> or a <Pressable> in there, you cannot style them with flexbox, and the React DevTools tree stops being a layout tree the moment you cross that boundary.

That trips up almost everyone once. Internalise it early: the Canvas is a painting, not a layout. You position things with numbers.

Installing it without fighting peer dependencies

npx expo install @shopify/react-native-skia
# or
yarn add @shopify/react-native-skia && npx pod-install

The peer dependencies in 2.11.2 are stricter than people expect:

  • react >= 19.0
  • react-native >= 0.78
  • react-native-reanimated >= 4.0.0
  • react-native-worklets >= 0.7.0

Reanimated and Worklets are not optional extras. Skia’s animation model runs on the UI thread through worklets, so if you are still on Reanimated 3 you are upgrading Reanimated before you get to draw anything. On a fresh Expo project this is a non-issue. On a three-year-old app it is the actual work.

For web, Skia ships as a CanvasKit WASM build and needs one extra step after every upgrade:

npx expo install @shopify/react-native-skia
yarn setup-skia-web

Forgetting to re-run setup-skia-web after a version bump produces a blank canvas and no error, which is a fun forty minutes.

Drawing: shapes, paths, images, text

The declarative components cover most of what you need. Shapes take absolute coordinates. Paths take an SkPath, which you build with Skia.PathBuilder or parse from an SVG path string with Skia.Path.MakeFromSVGString. That second one is how most production Skia code starts: a designer hands you an SVG, you lift the d attribute, and now it is animatable.

Images load through a hook that returns null until the decode finishes:

import { Canvas, Image, useImage } from "@shopify/react-native-skia";

const Photo = () => {
  const image = useImage(require("./assets/oslo.jpg"));
  return (
    <Canvas style={{ flex: 1 }}>
      <Image image={image} fit="cover" x={0} y={0} width={256} height={256} />
    </Canvas>
  );
};

useImage takes a require, a network URL, or the name of an asset in the native bundle. fit accepts contain, cover, fill, fitWidth, fitHeight, scaleDown and none, defaulting to contain. The sampling prop is worth knowing about the first time a downscaled image looks crunchy: the default is nearest-neighbour filtering, and passing CubicSampling fixes it at a small GPU cost.

Text is the part that feels heaviest. There is a simple <Text> for single lines with a font you loaded via useFont, and a <Paragraph> API for anything with wrapping, mixed styles or custom paints:

const paragraph = useMemo(() => {
  const fg = Skia.Paint();
  fg.setShader(
    Skia.Shader.MakeRadialGradient(
      { x: 0, y: 0 }, 256,
      [Skia.Color("magenta"), Skia.Color("yellow")],
      null, TileMode.Clamp
    )
  );
  return Skia.ParagraphBuilder.Make()
    .pushStyle({ fontFamilies: ["Roboto"], fontSize: 72, color: Skia.Color("black") }, fg)
    .addText("Say hello to Skia")
    .pop()
    .build();
}, []);

Gradient-filled text with no image assets and no masking hacks. That is the sales pitch for the whole library in one snippet. Note the useMemo: building a paragraph on every render is a real cost, and forgetting to memoise it is the second most common Skia performance bug.

Animation: Reanimated values go straight into props

This is the best-designed part of the library and the least well known. You do not wrap anything in createAnimatedComponent. Shared values and derived values are valid prop values on Skia components:

import { Canvas, Circle } from "@shopify/react-native-skia";
import { useSharedValue, useDerivedValue, withRepeat, withTiming } from "react-native-reanimated";

export const Pulse = () => {
  const size = 256;
  const r = useSharedValue(0);
  const c = useDerivedValue(() => size - r.value);

  useEffect(() => {
    r.value = withRepeat(withTiming(size * 0.33, { duration: 1000 }), -1);
  }, [r, size]);

  return (
    <Canvas style={{ flex: 1 }}>
      <Circle cx={r} cy={c} r={r} color="cyan" />
    </Canvas>
  );
};

No React re-render happens while that animates. The value changes on the UI thread and Skia redraws on the UI thread. That is why Skia animations hold 60fps through a JavaScript stall that would turn an Animated.View into a slideshow.

For paths specifically, use usePathValue rather than rebuilding the path in a derived value. It mutates one path object in place on the UI thread instead of allocating a new one every frame:

const clip = usePathValue(
  () => { "worklet"; },
  rrct,
  (path) => {
    "worklet";
    return path.transform(
      processTransform3d([
        { translate: [50, 50] },
        { perspective: 300 },
        { rotateY: rotateY.value },
        { translate: [-50, -50] },
      ])
    );
  }
);

Combine that with <GestureDetector> from Gesture Handler and you have a card that tilts under your finger with zero bridge traffic. Gesture Handler shipped 3.3.0 this month with a batch of Android touch fixes, so pin a current version if you are doing gesture-driven drawing.

Shaders, when you want the GPU to do the work

Skia exposes SkSL, a GLSL-like shading language, through Skia.RuntimeEffect:

const source = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
  vec2 normalized = pos / vec2(256);
  return vec4(normalized.x, normalized.y, 0.5, 1);
}`)!;

const Gradient = () => (
  <Canvas style={{ width: 256, height: 256 }}>
    <Fill><Shader source={source} /></Fill>
  </Canvas>
);

Uniforms let you drive a shader from animated values, so noise fields, mesh gradients, dissolve transitions and shimmer effects all become one fragment program instead of forty animated views. This is genuinely the only way to do some of these effects in React Native at frame rate. It is also where a codebase goes to become unmaintainable if exactly one person on the team understands it. Write comments.

Blur, backdrop filters and the frosted-glass look

<Canvas style={{ width: 256, height: 256 }}>
  <Image image={image} x={0} y={0} width={256} height={256} fit="cover" />
  <BackdropBlur blur={4} clip={{ x: 0, y: 128, width: 256, height: 128 }}>
    <Fill color="rgba(0, 0, 0, 0.2)" />
  </BackdropBlur>
</Canvas>

Cross-platform blur in six lines, which is not a sentence you could write in React Native five years ago without pulling in a native library per platform. Worth noting that iOS 26 and 27 shipped their own Liquid Glass system materials, and if you want the real platform look on iOS you should use those instead. We covered that split in React Native vs Swift. Skia is for effects you want to be identical on both platforms, not for imitating one platform’s chrome.

Snapshots: turning a canvas into an image

const ref = useCanvasRef();
// later
const image = ref.current?.makeImageSnapshot();
const bytes = image?.encodeToBytes(); // Uint8Array

This is how you build share cards, export a signature, or generate a thumbnail of a user’s drawing. You can pass a rectangle to snapshot only part of the canvas. It is synchronous and it is not cheap, so do not put it in a scroll handler.

Performance: what actually costs you

Skia is fast, and it is still possible to make it slow. In rough order of how often we have seen each one bite:

  1. Re-rendering the Canvas from React state. If a useState update re-renders the component that owns the Canvas, the whole scene is rebuilt. Drive animation through shared values, not state.
  2. Rebuilding objects every render. Paths, paragraphs, fonts, RuntimeEffect instances and Paint objects should be created once. useMemo or module scope.
  3. Too many Canvases. Each one is a native surface. A hundred-row list with a Canvas per row is a hundred surfaces. Draw the whole list into one Canvas, or use a normal view and reserve Skia for the cells that need it.
  4. Large blurs on large areas. Blur radius cost is not linear. A blur={40} over a full screen on a mid-range Android device will show up in your frame times.
  5. Snapshotting in a hot path. As above.

Two props on <Canvas> are worth knowing: androidWarmup draws the first frame directly on the Android compositor, which removes a visible flash on mount, and highBitDepth renders into a surface with more than 8 bits per channel if you are doing wide-gamut work and seeing banding in gradients.

When not to reach for Skia

Adding Skia is adding a graphics engine to your bundle. Before you do, check whether the boring answer works:

  • A static gradient or shadow? Use expo-linear-gradient and a shadow style. No canvas needed.
  • A standard chart? Many chart libraries are already built on Skia. Use one rather than drawing axes by hand.
  • A Lottie or Rive file from a designer? Use the runtime built for it. Skia is for effects you are programming, not animations you are playing.
  • Material-style UI surfaces? That is a component library problem. React Native Paper exists for a reason.
  • Anything needing text selection, accessibility labels or keyboard focus. Canvas content is invisible to screen readers. If the thing you are drawing carries meaning, it needs an accessible sibling.

That accessibility point deserves more weight than it usually gets. A chart drawn in Skia is a picture to VoiceOver. Give it an accessibilityLabel on the wrapping view that says what the data shows.

The elephant: Shopify is handing Skia over

On September 10, 2026, Shopify announced it is migrating its mobile apps from React Native back to Swift and Kotlin. That post spells out what happens to the libraries Shopify maintains, and Skia is one of them.

The specifics, so you can plan rather than panic:

  • Shopify continues sponsoring React Native Skia through the end of 2026.
  • William Candillon, who created the library and has been its public face for years, will fork the repo in the coming months and publish it under a new name.
  • The Shopify/react-native-skia repository is archived once that transition completes.

So the maintainer is staying and the package name is changing. That is a very different situation from abandonment, and it is worth saying plainly because the week’s commentary blurred the two. The library is MIT licensed, at 2.11.2, and pulling over a million downloads a week. It is not going anywhere.

What we would actually do: keep shipping Skia today, do not rip anything out, and put a reminder in your calendar for Q1 2027 to check what the new package is called and budget an afternoon for the rename. If you are starting a greenfield project right now and Skia is a maybe rather than a must, it is a reasonable moment to check whether the boring answer above covers you. If you need programmable graphics at 60fps in React Native, there is still no real alternative, and a rename is a cheap price.

We track this sort of thing weekly. The September 14 digest has the rest of the fallout, including what happens to FlashList and Restyle.

FAQ

Does React Native Skia work with Expo?

Yes. npx expo install @shopify/react-native-skia and it works in development builds and EAS builds. It does not run in Expo Go, because it ships native code. If you are unclear on that distinction, our Expo guide covers the Go versus development build split.

Does it work on the web?

Yes, through a CanvasKit WASM build. Run yarn setup-skia-web after install and after every upgrade. Expect a meaningful WASM payload, so lazy-load the route that uses it.

Do I need Reanimated to use Skia?

For static drawing, no. For animation, effectively yes: Reanimated 4 and Worklets are peer dependencies in 2.11.2 and the animation model is built on them.

Is Skia faster than Reanimated?

Wrong comparison. Reanimated animates React Native views on the UI thread. Skia draws pixels on the UI thread. They are complementary, and in practice you use Reanimated’s values to drive Skia’s drawing.

Can I put buttons or text inputs inside a Canvas?

No. Canvas children are drawing commands, not host components. Overlay real React Native views on top of the Canvas with absolute positioning, or place the Canvas behind them.

Will the Shopify handover break my app?

Not on any timeline you need to react to this quarter. Sponsorship runs to the end of 2026, the maintainer is forking rather than leaving, and the package stays MIT. Plan for a package rename in 2027, not a migration.

Leave a Reply 0

Your email address will not be published. Required fields are marked *