The same graphics engine as Chrome and Flutter, right inside the app
React Native Skia brings Google's Skia graphics engine, the same one powering Chrome, Android, and Flutter, directly into React Native through JSI. Instead of building shapes out of native views or SVG elements, Skia draws onto a single native surface, which makes custom graphics like signature pads, custom charts, or image filters noticeably faster than classic view-based approaches.
Table of Contents
- 1. What is Skia, and why React Native Skia?
- 2. The canvas foundation: Canvas, Group, and Paint
- 3. Drawing paths: Skia.Path for custom shapes
- 4. Practical example: a signature pad with touch events
- 5. Practical example: a performant custom chart
- 6. Animating Skia values with Reanimated
- 7. How Skia renders: its own drawing path instead of a view hierarchy
- 8. Skia compared to SVG-based solutions
- 9. When React Native Skia pays off, and when it does not
- 10. Summary
- 11. FAQ
1. What is Skia, and why React Native Skia?
Skia is Google's open source 2D graphics engine, used for years in Chrome, ChromeOS, Android itself, and, for several versions now, in Flutter as well. It provides a fast, cross-platform drawing API that renders paths, text, images, and shaders onto a bitmap surface, regardless of whether Metal on iOS or OpenGL or Vulkan on Android sits underneath. React Native Skia, built by Shopify, wires exactly this engine into React Native through JSI and exposes it as declarative React components.
The key difference from classic React Native rendering is that Skia does not create a native view per element. A canvas with a hundred drawn circles stays a single native surface with a hundred draw commands, whereas a view-based approach would create a hundred individual native views, each with its own layout and rendering overhead. For custom, often data-driven graphics, that is exactly why Skia stays so much faster in practice.
2. The canvas foundation: Canvas, Group, and Paint
Every Skia drawing starts with a Canvas component that reserves a fixed area of the app as a drawing surface. Inside the canvas, primitives such as Circle, Rect, Path, or Text are placed declaratively as React children, similar to what you know from SVG libraries, except everything ultimately renders onto the same native surface instead of individual views.
Visual properties like color, fill, stroke width, or opacity are controlled through Paint objects or direct props such as color and style. Group lets you combine several primitives and transform them together, or apply a shared shader across them, which is very handy for reusable graphic building blocks.
import { Canvas, Circle, Path, Group } from '@shopify/react-native-skia';
function BasicShapes() {
return (
<Canvas style={{ width: 300, height: 300 }}>
<Group>
<Circle cx={150} cy={100} r={60} color="#4338ca" />
<Path
path="M 40 220 L 120 180 L 200 240 L 260 200"
color="#22d3ee"
style="stroke"
strokeWidth={4}
/>
</Group>
</Canvas>
);
}
3. Drawing paths: Skia.Path for custom shapes
For anything beyond simple circles and rectangles, the imperative Path API is the central building block. Calling Skia.Path.Make() creates an empty path object, which is then built up step by step using methods like moveTo, lineTo, and cubicTo, exactly like the Canvas 2D API in the browser, only with native performance behind it.
That path object can then be passed as a path prop to a Path component and recalculated as often as needed, say on every touch event of a signature pad or on every new data point of a chart. Because path calculation itself is plain JavaScript or worklet code, it combines well with Reanimated, which this article covers later on.
4. Practical example: a signature pad with touch events
A signature pad is a classic use case for Skia: on every touch-move event, a new point gets appended to the current path, usually with quadTo instead of individual lineTo segments so the corners between points stay smooth. The path itself lives in a ref or shared value so it does not need to be recreated through React on every single frame.
Once the signature is done, the result can be exported straight from the canvas as an image via makeImageSnapshot(), for example to send it as a base64 string to a backend. This export happens natively, without a separate screenshot mechanism, which makes signature pads with Skia considerably simpler than writing a dedicated native bridge for it used to be.
import { Canvas, Path, Skia, useCanvasRef } from '@shopify/react-native-skia';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import { useState } from 'react';
function SignaturePad() {
const canvasRef = useCanvasRef();
const [path, setPath] = useState(() => Skia.Path.Make());
const pan = Gesture.Pan()
.onStart((e) => {
path.moveTo(e.x, e.y);
setPath(path.copy());
})
.onUpdate((e) => {
path.lineTo(e.x, e.y);
setPath(path.copy());
});
async function exportSignature() {
const image = canvasRef.current?.makeImageSnapshot();
const base64 = image?.encodeToBase64();
await uploadSignature(base64);
}
return (
<GestureDetector gesture={pan}>
<Canvas ref={canvasRef} style={{ width: 320, height: 200 }}>
<Path path={path} color="#111827" style="stroke" strokeWidth={3} />
</Canvas>
</GestureDetector>
);
}
5. Practical example: a performant custom chart
Charts with many data points are where Skia's advantage really shows. Instead of creating an individual view or SVG element per data point, the entire line is calculated as a single path from the data values and handed to the native surface in one draw command. With several hundred points, the result stays smooth, while view-based approaches often noticeably stutter at that scale.
On top of that, the fill area under the line can get a LinearGradient shader without any extra library, since Skia ships shaders as a native concept. That produces charts that look close to native analytics dashboards, without needing image assets or complex SVG definitions to get there.
function LineChart({ points }: { points: number[] }) {
const path = useMemo(() => {
const p = Skia.Path.Make();
points.forEach((value, i) => {
const x = i * (300 / (points.length - 1));
const y = 200 - value * 1.5;
i === 0 ? p.moveTo(x, y) : p.lineTo(x, y);
});
return p;
}, [points]);
return (
<Canvas style={{ width: 300, height: 200 }}>
<Path path={path} style="stroke" strokeWidth={2.5} color="#22d3ee">
<LinearGradient start={vec(0, 0)} end={vec(0, 200)} colors={['#4338ca', '#22d3ee']} />
</Path>
</Canvas>
);
}
6. Animating Skia values with Reanimated
React Native Skia was built from the ground up to work with Reanimated. Instead of changing props like color, radius, or path through regular React state, shared values can be passed directly as values into Skia components, so changes get redrawn without a React re-render and without a detour through the JS thread, straight on the UI thread.
That enables things like path interpolation, smoothly morphing between two shapes, driven by a single shared value between zero and one. Because both Reanimated and Skia sit on the same JSI foundation, this combination stays at 60 frames per second even for complex, data-driven animations.
const progress = useSharedValue(0);
const animatedRadius = useDerivedValue(() => {
return interpolate(progress.value, [0, 1], [40, 90]);
});
<Canvas style={{ width: 200, height: 200 }}>
<Circle cx={100} cy={100} r={animatedRadius} color="#4338ca" />
</Canvas>
7. How Skia renders: its own drawing path instead of a view hierarchy
Normal React Native rendering goes through Fabric's view hierarchy, with layout calculation, diffing, and creating individual native views. Skia, by contrast, draws directly onto a GPU-backed surface, without every primitive being its own native component. Changes to a Skia canvas therefore do not trigger a React reconciliation pass across hundreds of child components, only a fresh draw pass on the canvas itself.
That makes Skia particularly suited to scenarios with many small, frequently changing elements, such as particle effects, waveform visualizations, or real-time drawing, where a view-based approach would simply need too many individual layout passes to stay smooth.
8. Skia compared to SVG-based solutions
react-native-svg ultimately renders every SVG element through its own native view per node, which is perfectly fine for static or moderately complex illustrations and has the advantage of working directly with existing SVG assets from a design team. With many elements or frequent changes, say animated charts, that view-per-element approach becomes a bottleneck.
Skia, on the other hand, comes with a steeper learning curve, since paths and transforms are often built imperatively rather than purely declaratively, and it noticeably increases the app's native binary size. Switching pays off specifically where performance or custom drawing logic matters more than quickly wiring up ready-made icons.
9. When React Native Skia pays off, and when it does not
For a handful of static icons or a simple illustration, react-native-svg or a plain optimized PNG remains the simpler, lower-maintenance choice. Skia pays off where graphics are interactive, data-driven, or animated, say signature pads, custom charts, image filters, or drawing tools, where view-based approaches hit their limits.
A good rule of thumb: once more than a dozen graphic elements need to be drawn simultaneously on screen and recalculated regularly, it is worth taking a serious look at Skia. Below that, the extra effort usually is not justified.
| Use case | Recommended solution | Why | Effort |
|---|---|---|---|
| A handful of static icons | react-native-svg or PNG | No rendering overhead needed | Low |
| Interactive signature pad | React Native Skia | Direct path access, high frame rate | Medium |
| Custom chart with many data points | React Native Skia | One canvas draw call instead of hundreds of views | Medium to high |
| Image filter or photo editor | React Native Skia with shaders | GPU-close shader pipeline available | High |
| Simple illustration without interaction | SVG or Lottie | Declarative and lower maintenance | Low |
| Particle effect or waveform | React Native Skia | Many elements without view overhead | High |
Mironsoft
React Native app development and Magento integration
A mobile app for the Magento shop that actually runs smoothly?
We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.
App Concept
Plan the architecture and feature scope of a Magento-connected app together.
Magento API Integration
Cleanly connect product catalog, cart, and checkout to the shop API.
Store Publishing
Guide the App Store and Google Play release process without pitfalls.
10. Summary
React Native Skia
Core idea
Skia draws all primitives onto one shared native surface instead of creating a view per element.
Core API
Skia.Path.Make() builds custom paths imperatively, similar to the browser's Canvas 2D API.
Reanimated combo
Shared values can be passed directly as Skia props and get redrawn without a React re-render.
Rule of thumb
Once about a dozen graphic elements change at once, switching from SVG to Skia is worth it.