Creating Custom Fonts with React-Native-Skia

Git Stash Apply
4 min readMay 16, 2024


When working with custom designs in React Native, creating custom fonts with specific styles can be challenging. In this article, we will explore how to implement a custom font using @shopify/react-native-skia library.


The design requirements for our custom font are as follows:

Let’s talk a bit — what we need to implement:

  • The font should have a 1px stroke.
  • The font should have a bottom shadow with a width of 4px and a Y translation of 4px.


Skia is a cross-platform 2D graphics library that provides a set of drawing primitives that run on iOS, Android, macOS, Windows, Linux, and the browser. React-native-skia brings this library to react native.


  1. Install package:
yarn add @shopify-react-native-skia

2. iOS pod install:

npx pod-install ios

If you have any issues with package installation please check Official Docs


We will divide the font rendering into three main components:

  1. Outer Stroke
  2. Inner Font
  3. Outer Shadow

Preparing Skia for the Base Font

We’ll use the Anek-Latin font as our base font. (here is guide how to install custom fonts)

First, define the base font style and create the Skia font object:

import { Skia } from "@shopify/react-native-skia";

export const fontStyle = {
textStyle: {
color: Skia.Color('white'),
fontFamilies: ["Anek-Bold"],
fontSize: 36,
lineHeight: 24,
letterSpacing: -0.8,
paragraphStyle: {
foregroundPrimaryStyle: 1,
foregroundSecondaryStyle: 0,
foregroundPrimaryColor: Skia.Color('black'),
foregroundSecondaryColor: Skia.Color('white'),
foregroundStrokeWidth: 4,
shadow: {
dx: 0,
dy: 4,

Note: very important to use Skia.Color for color definition.

Create font manager using “useFont()”

export const CustomFont = () => {
const customFontMgr = useFonts({
["Anek-Bold"]: [require("../../assets/fonts/AnekLatin-Bold.ttf")],

Let’s Rock! Outer stroke.

Create paint object and paragraph primitive using Skia.Paint() and Skia.ParagraphBuilder.Make()

const outerStroke = useMemo(() => {
if (!customFontMgr) {
return null;

const foregroundPaint = Skia.Paint();

return Skia.ParagraphBuilder.Make({
textAlign: TextAlign.Center
}, customFontMgr)
.pushStyle({ ...fontStyle.textStyle, }, foregroundPaint)
}, [customFontMgr, text]);

Let’s speak a bit what we see here:

  1. Skia.Paint() — creates Paint primitive.
  2. Now we can assign any properties for foregroundPaint
  3. Skia.ParagraphBuilderMake() creates Paragraphprimitive.

Let’s render something on the screen:


return (
<Canvas style={{ width, height }}>
<Paragraph paragraph={outerStroke} x={0} y={0} width={width} />
Rendering outer style

Inner Font

Now, let’s implement the body of the font:


const innerFont = useMemo(() => {
if (!customFontMgr) {
return null;

const innerFontStyle = {
color: Skia.Color(color ?? "white"),

return Skia.ParagraphBuilder.Make(
{ textAlign: TextAlign.Center },
}, [customFontMgr, text]);


return (
<Canvas style={{ width, height }}>
<Paragraph paragraph={primaryParagraph} x={0} y={0} width={width} />
<Paragraph paragraph={secondaryParagraph} x={0} y={0} width={width} />
Looks better, right?

Outer Shadow

Finally, let’s implement the outer shadow using the Group, Paint, and Shadow components from @shopify/react-native-skia:

import { Group, Paint, Shadow } from '@shopify/react-native-skia';

// Component
return (
width: width,
height: height,
<Paragraph paragraph={outerStroke} x={0} y={0} width={width} />
<Paragraph paragraph={innerFont} x={0} y={0} width={width} />


Dynamic Canvas Size

Maybe you saw that <Canvas /> component contains { width: width, height: height} parameters.

We want our canvas to adjust its size dynamically based on the content. React-Native-Skia provides the measureText helper:

const font = useFont(require('../assets/fonts/AnekLatin-Bold.ttf'), textStyle.fontSize);
const { width, height } = font?.measureText(text);

Let’s create some hook:

// useFontMeasurements.ts

import { DataSourceParam, useFont } from "@shopify/react-native-skia";
import { useEffect, useState } from "react";

type Props = {
text: string;
size: number;
fontSource: DataSourceParam;

export const useFontMeasurements = ({ text, size, fontSource }: Props) => {
const [textParams, setTextParams] = useState<{
height: number;
width: number;
}>({ height: 0, width: 0 });
const font = useFont(fontSource, size);

useEffect(() => {
if (!fontSource || !text || !size || !font) return;

const { width, height, y } = font.measureText(text);

setTextParams({ width, height: height - y / 2 });
}, [font, size, fontSource, text]);

return {
height: textParams?.height,
width: textParams?.width,

Now we can use it in our <CustomFont />component:


const { height, width } = useFontMeasurements({
text: text,
fontSource: require("../../assets/fonts/AnekLatin-Bold.ttf"),
size: style?.fontSize ?? fontStyle.textStyle.fontSize,


You can find source code here:

Please let me know if you found this article useful and if you’re interested in more articles like this.