Skip to main content
Skia provides a powerful shading language called SkSL that allows you to create custom shader effects. The syntax is very similar to GLSL, making it accessible if you have graphics programming experience.

Overview

SkSL (Skia Shading Language) lets you write custom shaders for unique visual effects. You can experiment with SkSL shaders in the online editor. If you’re familiar with GLSL or looking to convert GLSL shaders to SkSL, check out the differences between GLSL and SkSL.

Creating a Runtime Effect

First, compile your shader code using RuntimeEffect.Make:
import { Skia } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
  // The canvas is 256x256
  vec2 canvas = vec2(256);
  // normalized x,y values go from 0 to 1
  vec2 normalized = pos/canvas;
  return vec4(normalized.x, normalized.y, 0.5, 1);
}`);

if (!source) {
  throw new Error("Couldn't compile the shader");
}

Shader Component

Use the Shader component to apply your custom shader:

Props

NameTypeDescription
sourceRuntimeEffectCompiled shader from RuntimeEffect.Make
uniforms{ [name: string]: number | Vector | Vector[] | number[] | number[][] }Uniform values to pass to the shader
childrenShaderChild shaders to use as uniforms

Simple Shader Example

import { Canvas, Shader, Fill, Skia } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
  // normalized x,y values go from 0 to 1, the canvas is 256x256
  vec2 normalized = pos/vec2(256);
  return vec4(normalized.x, normalized.y, 0.5, 1);
}`)!;

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

Using Uniforms

Uniforms are variables that allow you to pass parameters to your shaders. They’re perfect for creating dynamic, interactive effects.

Supported Uniform Types

  • float, float2, float3, float4
  • float2x2, float3x3, float4x4
  • int, int2, int3, int4
  • Arrays: uniform float3 colors[12]

Basic Uniforms Example

import { Canvas, Shader, Fill, Skia, vec } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
uniform vec2 c;
uniform float r;
uniform float blue;

vec4 main(vec2 pos) {
  vec2 normalized = pos/vec2(2 * r);
  return distance(pos, c) > r ? vec4(1) : vec4(normalized, blue, 1);
}`)!;

const UniformShader = () => {
  const r = 128;
  const c = vec(2 * r, r);
  const blue = 1.0;
  
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Fill>
        <Shader source={source} uniforms={{ c, r, blue }} />
      </Fill>
    </Canvas>
  );
};

Animated Uniforms

Combine with React Native Reanimated for dynamic effects:
import { Canvas, Shader, Fill, Skia, vec } from "@shopify/react-native-skia";
import { useDerivedValue, useSharedValue, withRepeat, withTiming } from "react-native-reanimated";
import { useEffect } from "react";

const source = Skia.RuntimeEffect.Make(`
uniform float time;
uniform vec2 resolution;

vec4 main(vec2 pos) {
  vec2 uv = pos / resolution;
  float wave = sin(uv.x * 10.0 + time) * 0.5 + 0.5;
  return vec4(wave, uv.y, 1.0 - wave, 1.0);
}`)!;

const AnimatedShader = () => {
  const time = useSharedValue(0);
  
  useEffect(() => {
    time.value = withRepeat(
      withTiming(Math.PI * 2, { duration: 3000 }),
      -1,
      false
    );
  }, []);
  
  const uniforms = useDerivedValue(() => ({
    time: time.value,
    resolution: vec(256, 256),
  }));
  
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Fill>
        <Shader source={source} uniforms={uniforms} />
      </Fill>
    </Canvas>
  );
};

Nested Shaders

Pass other shaders as inputs to your custom shader:
import { Canvas, Shader, Fill, ImageShader, Skia, useImage } from "@shopify/react-native-skia";

const source = Skia.RuntimeEffect.Make(`
uniform shader image;

half4 main(float2 xy) {
  xy.x += sin(xy.y / 3) * 4;
  return image.eval(xy);
}`)!;

const NestedShader = () => {
  const image = useImage(require("./assets/oslo.jpg"));
  
  if (!image) {
    return null;
  }
  
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Fill>
        <Shader source={source}>
          <ImageShader
            image={image}
            fit="cover"
            rect={{ x: 0, y: 0, width: 256, height: 256 }}
          />
        </Shader>
      </Fill>
    </Canvas>
  );
};

Common Shader Patterns

Gradient Effect

const gradientShader = Skia.RuntimeEffect.Make(`
uniform vec2 resolution;
uniform vec4 color1;
uniform vec4 color2;

vec4 main(vec2 pos) {
  float t = pos.y / resolution.y;
  return mix(color1, color2, t);
}`)!;

Checkerboard Pattern

const checkerboard = Skia.RuntimeEffect.Make(`
uniform vec2 resolution;
uniform float size;

vec4 main(vec2 pos) {
  vec2 cell = floor(pos / size);
  float checker = mod(cell.x + cell.y, 2.0);
  return vec4(vec3(checker), 1.0);
}`)!;

Circle Pattern

const circles = Skia.RuntimeEffect.Make(`
uniform vec2 resolution;
uniform float radius;

vec4 main(vec2 pos) {
  vec2 center = resolution / 2.0;
  float dist = distance(pos, center);
  float circle = step(dist, radius);
  return vec4(vec3(circle), 1.0);
}`)!;

Ripple Effect

const ripple = Skia.RuntimeEffect.Make(`
uniform vec2 resolution;
uniform float time;
uniform vec2 center;

vec4 main(vec2 pos) {
  float dist = distance(pos, center);
  float wave = sin(dist * 0.05 - time * 3.0) * 0.5 + 0.5;
  return vec4(vec3(wave), 1.0);
}`)!;

Advanced Techniques

Multiple Shader Inputs

const source = Skia.RuntimeEffect.Make(`
uniform shader background;
uniform shader foreground;
uniform float blend;

half4 main(float2 xy) {
  half4 bg = background.eval(xy);
  half4 fg = foreground.eval(xy);
  return mix(bg, fg, blend);
}`)!;

const MultiShader = () => {
  const image1 = useImage(require("./assets/bg.jpg"));
  const image2 = useImage(require("./assets/fg.jpg"));
  const blend = useSharedValue(0.5);
  
  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Fill>
        <Shader source={source} uniforms={{ blend }}>
          <ImageShader image={image1} fit="cover" rect={{ x: 0, y: 0, width: 256, height: 256 }} />
          <ImageShader image={image2} fit="cover" rect={{ x: 0, y: 0, width: 256, height: 256 }} />
        </Shader>
      </Fill>
    </Canvas>
  );
};

Color Manipulation

const colorEffect = Skia.RuntimeEffect.Make(`
uniform shader input;
uniform float saturation;

vec3 rgb2hsv(vec3 c) {
  vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
  vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
  vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
  float d = q.x - min(q.w, q.y);
  float e = 1.0e-10;
  return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

vec3 hsv2rgb(vec3 c) {
  vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
  vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
  return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

half4 main(float2 xy) {
  half4 color = input.eval(xy);
  vec3 hsv = rgb2hsv(color.rgb);
  hsv.y *= saturation;
  return vec4(hsv2rgb(hsv), color.a);
}`)!;

Performance Tips

  • Keep shader code simple and efficient
  • Avoid complex branching (if/else) when possible
  • Use built-in functions (sin, cos, etc.) efficiently
  • Minimize uniform updates
  • Cache compiled RuntimeEffects
  • Test on target devices for performance

Debugging Shaders

Compilation Errors

const source = Skia.RuntimeEffect.Make(shaderCode);
if (!source) {
  console.error("Shader compilation failed");
  // Handle error
}

Visual Debugging

Output debug values as colors:
const debug = Skia.RuntimeEffect.Make(`
vec4 main(vec2 pos) {
  // Visualize position as color
  return vec4(pos.x / 256.0, pos.y / 256.0, 0, 1);
}`)!;

Built-in Functions

SkSL supports many built-in functions:
  • Math: sin, cos, tan, pow, sqrt, abs, floor, ceil, fract, mod
  • Vector: dot, cross, length, distance, normalize
  • Interpolation: mix, clamp, smoothstep, step
  • Color: mix for blending colors

See Also

Build docs developers (and LLMs) love