npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-drawpad

v0.3.1

Published

A reusable drawpad component for Expo React Native

Downloads

404

Readme

expo-drawpad

npm version npm downloads license

A smooth, animated drawing pad component for React Native and Expo applications. Perfect for signatures, sketches, and interactive drawing experiences.

Features

  • Smooth Drawing: Responsive touch gestures with customizable stroke width and color
  • 🎬 Path Animation: Replay drawings with smooth animations
  • 🎨 Multiple Brush Types: Solid, dotted, dashed, and highlighter brush styles
  • 🌈 Gradient Support: Apply beautiful gradients to your strokes
  • ↩️ Undo/Redo: Built-in undo functionality for better user experience
  • 🧹 Clear Canvas: Easy erase functionality to start fresh
  • 📱 Cross-Platform: Works on iOS, Android, and Web
  • 🎯 TypeScript: Full TypeScript support with type definitions
  • 🪶 Lightweight: Minimal dependencies and optimized performance
  • 📁 Path Management: Get, set, and add paths programmatically
  • 📄 SVG Export: Export drawings as SVG strings
  • 🎛️ Customizable Animation: Control animation duration and easing
  • 🎪 Event Callbacks: Handle draw start and end events

Installation

npm install expo-drawpad

Peer Dependencies

Make sure you have these peer dependencies installed:

npm install react-native-gesture-handler react-native-reanimated react-native-svg react-native-worklets

For Expo projects, you can install them with:

npx expo install react-native-gesture-handler react-native-reanimated react-native-svg react-native-worklets

Quick Start

Minimal Example

import React, { useRef } from "react";
import { View, Button } from "react-native";
import DrawPad, { DrawPadHandle } from "expo-drawpad";

export default function App() {
  const drawPadRef = useRef<DrawPadHandle>(null);

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <View style={{ flex: 1, backgroundColor: "#f0f0f0", borderRadius: 10 }}>
        <DrawPad ref={drawPadRef} />
      </View>

      <View style={{ flexDirection: "row", marginTop: 20, gap: 10 }}>
        <Button title="Clear" onPress={() => drawPadRef.current?.erase()} />
        <Button title="Undo" onPress={() => drawPadRef.current?.undo()} />
      </View>
    </View>
  );
}

Full Example with Animation

import React, { useRef } from "react";
import { View, Button } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import DrawPad, { DrawPadHandle } from "expo-drawpad";

export default function App() {
  const drawPadRef = useRef<DrawPadHandle>(null);
  const pathLength = useSharedValue(0);
  const playing = useSharedValue(false);
  const signed = useSharedValue(false);

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <View style={{ flex: 1, backgroundColor: "#f0f0f0", borderRadius: 10 }}>
        <DrawPad
          ref={drawPadRef}
          stroke="#000000"
          strokeWidth={3}
          pathLength={pathLength}
          playing={playing}
          signed={signed}
        />
      </View>

      <View style={{ flexDirection: "row", marginTop: 20, gap: 10 }}>
        <Button title="Clear" onPress={() => drawPadRef.current?.erase()} />
        <Button title="Undo" onPress={() => drawPadRef.current?.undo()} />
        <Button title="Play" onPress={() => drawPadRef.current?.play()} />
        <Button title="Stop" onPress={() => drawPadRef.current?.stop()} />
      </View>
    </View>
  );
}

API Reference

Props

| Prop | Type | Required | Default | Description | | ------------------- | ---------------------- | -------- | ------------------------------- | --------------------------------------------------- | | stroke | string | No | "grey" | Color of the drawing stroke (hex, rgb, etc.) | | pathLength | SharedValue<number> | No | - | Reanimated shared value for total path length | | playing | SharedValue<boolean> | No | - | Reanimated shared value for animation state | | strokeWidth | number | No | 3.5 | Width of the drawing stroke | | signed | SharedValue<boolean> | No | - | Optional shared value to track if anything is drawn | | pathProps | PathProps | No | - | Additional SVG path properties | | gradient | gradientProps | No | - | Gradient configuration for stroke | | brushType | BrushType | No | "solid" | Brush style: solid, dotted, dashed, or highlighter | | onDrawStart | () => void | No | - | Callback when drawing starts | | onDrawEnd | () => void | No | - | Callback when drawing ends | | animationDuration | number | No | pathLength.value * 2 | Custom animation duration in milliseconds | | easingFunction | EasingFunction | No | Easing.bezier(0.4, 0, 0.5, 1) | Custom easing function for animations |

Types

export interface gradientProps {
  colors: string[];
  locations?: number[];
  start?: { x: number; y: number };
  end?: { x: number; y: number };
}

export type BrushType = "solid" | "dotted" | "dashed" | "highlighter";

Methods (via ref)

| Method | Description | | --------------------------- | -------------------------------------- | | erase() | Clears the entire drawing canvas | | undo() | Removes the last drawn path | | play() | Starts the drawing animation playback | | stop() | Stops the current animation playback | | getPaths() | Returns array of all drawn paths | | setPaths(paths: string[]) | Sets the paths programmatically | | addPath(path: string) | Adds a single path to the canvas | | getSVG() | Returns Promise of SVG content |

Usage Examples

Basic Drawing with Different Brush Types

import React, { useRef, useState } from "react";
import { View, Button, StyleSheet } from "react-native";
import DrawPad, { DrawPadHandle, BrushType } from "expo-drawpad";

export function BrushTypesExample() {
  const drawPadRef = useRef<DrawPadHandle>(null);
  const [brushType, setBrushType] = useState<BrushType>("solid");

  return (
    <View style={styles.container}>
      <View style={styles.canvas}>
        <DrawPad
          ref={drawPadRef}
          brushType={brushType}
          stroke="#2563eb"
          strokeWidth={4}
        />
      </View>

      <View style={styles.controls}>
        <Button title="Solid" onPress={() => setBrushType("solid")} />
        <Button title="Dotted" onPress={() => setBrushType("dotted")} />
        <Button title="Dashed" onPress={() => setBrushType("dashed")} />
        <Button
          title="Highlighter"
          onPress={() => setBrushType("highlighter")}
        />
      </View>
    </View>
  );
}

Drawing with Gradient

import React, { useRef } from "react";
import { View, Button } from "react-native";
import DrawPad, { DrawPadHandle } from "expo-drawpad";

export function GradientExample() {
  const drawPadRef = useRef<DrawPadHandle>(null);

  const gradient = {
    colors: ["#ff6b35", "#f7931e", "#ffd23f"],
    locations: [0, 0.5, 1],
    start: { x: 0, y: 0 },
    end: { x: 1, y: 1 },
  };

  return (
    <View style={{ flex: 1 }}>
      <DrawPad ref={drawPadRef} gradient={gradient} strokeWidth={6} />

      <Button title="Clear" onPress={() => drawPadRef.current?.erase()} />
    </View>
  );
}

Drawing with Event Callbacks

import React, { useRef, useState } from "react";
import { View, Text, Button } from "react-native";
import DrawPad, { DrawPadHandle } from "expo-drawpad";

export function CallbackExample() {
  const drawPadRef = useRef<DrawPadHandle>(null);
  const [isDrawing, setIsDrawing] = useState(false);
  const [pathCount, setPathCount] = useState(0);

  const handleDrawStart = () => {
    setIsDrawing(true);
  };

  const handleDrawEnd = () => {
    setIsDrawing(false);
    setPathCount((prev) => prev + 1);
  };

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <Text>Drawing: {isDrawing ? "Yes" : "No"}</Text>
      <Text>Paths drawn: {pathCount}</Text>

      <View style={{ flex: 1, backgroundColor: "#f0f0f0", margin: 10 }}>
        <DrawPad
          ref={drawPadRef}
          onDrawStart={handleDrawStart}
          onDrawEnd={handleDrawEnd}
          stroke="#10b981"
        />
      </View>

      <Button
        title="Clear"
        onPress={() => {
          drawPadRef.current?.erase();
          setPathCount(0);
        }}
      />
    </View>
  );
}

SVG Export

import React, { useRef } from "react";
import { View, Button, Alert } from "react-native";
import DrawPad, { DrawPadHandle } from "expo-drawpad";

export function SVGExportExample() {
  const drawPadRef = useRef<DrawPadHandle>(null);

  const handleExportSVG = async () => {
    try {
      const svgString = await drawPadRef.current?.getSVG();
      if (svgString) {
        // You can save this SVG string to a file or send it to a server
        Alert.alert("SVG Exported", "Check console for SVG string");
        console.log(svgString);
      }
    } catch (error) {
      Alert.alert("Error", "Failed to export SVG");
    }
  };

  return (
    <View style={{ flex: 1 }}>
      <DrawPad ref={drawPadRef} stroke="#8b5cf6" strokeWidth={3} />

      <View style={{ flexDirection: "row", padding: 20, gap: 10 }}>
        <Button title="Export SVG" onPress={handleExportSVG} />
        <Button title="Clear" onPress={() => drawPadRef.current?.erase()} />
      </View>
    </View>
  );
}

Signature Pad

import React, { useRef } from "react";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import DrawPad, { DrawPadHandle } from "expo-drawpad";

export function SignaturePad() {
  const drawPadRef = useRef<DrawPadHandle>(null);
  const pathLength = useSharedValue(0);
  const playing = useSharedValue(false);
  const signed = useSharedValue(false);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Please sign below</Text>

      <View style={styles.canvas}>
        <DrawPad
          ref={drawPadRef}
          stroke="#2563eb"
          strokeWidth={2}
          pathLength={pathLength}
          playing={playing}
          signed={signed}
        />
      </View>

      <View style={styles.buttons}>
        <TouchableOpacity
          style={styles.button}
          onPress={() => drawPadRef.current?.erase()}
        >
          <Text>Clear Signature</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20 },
  title: { fontSize: 18, marginBottom: 20, textAlign: "center" },
  canvas: {
    flex: 1,
    backgroundColor: "white",
    borderRadius: 10,
    borderWidth: 1,
    borderColor: "#ddd",
  },
  buttons: { marginTop: 20 },
  button: {
    padding: 15,
    backgroundColor: "#f0f0f0",
    borderRadius: 5,
    alignItems: "center",
  },
});

Drawing with Animation

import React, { useRef } from "react";
import { View, Button } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import DrawPad, { DrawPadHandle } from "expo-drawpad";

export function AnimatedDrawing() {
  const drawPadRef = useRef<DrawPadHandle>(null);
  const pathLength = useSharedValue(0);
  const playing = useSharedValue(false);

  const handleReplay = () => {
    drawPadRef.current?.play();
  };

  return (
    <View style={{ flex: 1 }}>
      <DrawPad
        ref={drawPadRef}
        stroke="#ff6b35"
        strokeWidth={4}
        pathLength={pathLength}
        playing={playing}
      />

      <Button title="Replay Drawing" onPress={handleReplay} />
    </View>
  );
}

Requirements

  • React Native 0.81.4+
  • React Native Reanimated 4.1.1+
  • React Native Gesture Handler 2.28.0+
  • React Native SVG 15.12.1+
  • React Native Worklets 0.5.1+
  • Expo SDK 51+
  • iOS 13.0+
  • Android API Level 23+

Troubleshooting

Common Issues

Gestures not working

Make sure you have properly configured react-native-gesture-handler in your project. For React Native CLI projects, you may need to complete the platform-specific installation steps.

Animation not smooth

Ensure react-native-reanimated is properly installed and configured. For React Native CLI projects, you may need to rebuild your app after installation.

SVG not rendering

Verify that react-native-svg is correctly installed and linked. For React Native CLI projects, you may need to run cd ios && pod install.

Performance Tips

  • Use strokeWidth between 1-10 for optimal performance
  • Limit drawing area size for better performance on older devices
  • Consider using signed shared value to track drawing state efficiently

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Changelog

0.3.0

  • NEW: Added multiple brush types (solid, dotted, dashed, highlighter)
  • NEW: Added gradient support for strokes
  • NEW: Added SVG export functionality (getSVG() method)
  • NEW: Added path management methods (getPaths(), setPaths(), addPath())
  • NEW: Added draw event callbacks (onDrawStart, onDrawEnd)
  • NEW: Added customizable animation duration and easing
  • NEW: Added additional path properties support
  • 🔧 IMPROVED: Smoother drawing with quadratic curves instead of linear paths
  • 🔧 IMPROVED: Better worklets integration for performance
  • 🔧 IMPROVED: Updated peer dependencies to latest versions
  • 🔧 IMPROVED: Enhanced TypeScript definitions

License

ISC

Author

Solarin Johnson


Made with ❤️ for the React Native community