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

dragstack

v0.0.10

Published

A lightweight, performant React library for native drag, drop, and resize functionality

Readme

🎨 Dragstack - Lightweight Draggable Library

npm version Test Coverage TypeScript License: MIT Live Demo

⚠️ Important: This library requires React 18.x and Tailwind CSS. React 19 is not yet supported.

Create beautiful, interactive canvas experiences with drag-and-drop simplicity! A lightweight, performant React library that makes building canvas editors as easy as drawing on paper.


🎮 Live Demo

▶️ Try the Interactive Demo

Experience Dragstack in action! Add shapes, drag them around, resize, and see how smooth and intuitive the library is.


✨ Why Dragstack?

🚀 Lightning Fast - Built with native browser APIs for buttery-smooth 60fps interactions

🎯 All-in-One Solution - Canvas, grid, drag, drop, and resize in one powerful package

🎨 Beautiful by Default - Smooth shadows, animations, and visual feedback out of the box

🔧 Developer Friendly - Simple API with TypeScript support and comprehensive docs

Accessible - Full ARIA support and keyboard navigation

📱 Responsive - Works perfectly on desktop, tablet, and mobile devices


🚀 Quick Start

Prerequisites

⚠️ Important Requirements:

  • React 18.x - This library is built for React 18. React 19 is not yet supported due to breaking changes in hooks API
  • Tailwind CSS - Required for styling components. Make sure Tailwind is configured in your project.

Installation

# Install React 18 (if not already installed)
npm install react@^18.0.0 react-dom@^18.0.0

# Install Tailwind CSS (if not already installed)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

# Install Dragstack
npm install dragstack
# or
yarn add dragstack
# or
pnpm add dragstack

Your First Canvas in 30 Seconds 🎯

import React from 'react';
import { Canvas, useDraggableState } from 'dragstack';

const MyFirstCanvas = () => {
  const {
    elements,
    selectedElement,
    addElement,
    removeElement,
    updateElementPosition,
    updateElementSize,
    selectElement
  } = useDraggableState({
    initialElements: [
      {
        id: 'my-first-shape',
        type: 'rectangle',
        position: { x: 100, y: 100 },
        size: { width: 200, height: 150 },
        zIndex: 1,
        properties: {
          fill: '#3b82f6',
          opacity: 1
        }
      }
    ]
  });

  return (
    <Canvas
      elements={elements}
      selectedElement={selectedElement}
      onUpdateElementPosition={updateElementPosition}
      onUpdateElementSize={updateElementSize}
      onSelectElement={selectElement}
      width="100%"
      height={600}
      enableGrid={true}
      showGrid={true}
      gridSize={20}
      constrainToCanvas={true}
      draggableProps={{
        enableDragShadow: true,
        enableResizeShadow: true,
        dragScale: 1.02,
        resizeScale: 1.01
      }}
    >
      {(element, isSelected) => (
        <div
          style={{
            width: '100%',
            height: '100%',
            backgroundColor: element.properties?.fill,
            border: isSelected ? '3px solid #1f2937' : '2px solid #e5e7eb',
            borderRadius: '12px',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            color: 'white',
            fontSize: '14px',
            fontWeight: 'bold',
            transition: 'all 0.2s ease',
            cursor: 'move'
          }}
        >
          Drag me! 🎯
        </div>
      )}
    </Canvas>
  );
};

export default MyFirstCanvas;

That's it! You now have a fully functional draggable canvas with:

  • ✅ Drag and drop functionality
  • ✅ Resize handles on selection
  • ✅ Grid snapping
  • ✅ Visual feedback with shadows
  • ✅ Boundary constraints
  • ✅ Smooth animations

🎮 Interactive Playground

Want to see it in action? Here's a complete interactive example:

import React, { useState } from 'react';
import { Canvas, useDraggableState } from 'dragstack';

const InteractiveCanvas = () => {
  const [shapeType, setShapeType] = useState('rectangle');
  const [shapeColor, setShapeColor] = useState('#3b82f6');

  const canvasState = useDraggableState({
    initialElements: []
  });

  const addShape = () => {
    const newShape = {
      id: `${shapeType}-${Date.now()}`,
      type: shapeType,
      position: {
        x: Math.random() * 400 + 50,
        y: Math.random() * 300 + 50
      },
      size: {
        width: shapeType === 'circle' ? 120 : 150,
        height: shapeType === 'circle' ? 120 : 100
      },
      zIndex: canvasState.elements.length + 1,
      properties: {
        fill: shapeColor,
        opacity: 0.9
      }
    };

    canvasState.addElement(newShape);
  };

  const clearCanvas = () => {
    canvasState.elements.forEach(element => {
      canvasState.removeElement(element.id);
    });
  };

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
      {/* Control Panel */}
      <div style={{
        marginBottom: '20px',
        padding: '15px',
        backgroundColor: '#f8fafc',
        borderRadius: '10px',
        display: 'flex',
        gap: '15px',
        alignItems: 'center',
        flexWrap: 'wrap'
      }}>
        <div>
          <label style={{ marginRight: '10px', fontWeight: 'bold' }}>Shape:</label>
          <select
            value={shapeType}
            onChange={(e) => setShapeType(e.target.value)}
            style={{ padding: '8px', borderRadius: '5px', border: '1px solid #d1d5db' }}
          >
            <option value="rectangle">Rectangle</option>
            <option value="circle">Circle</option>
            <option value="square">Square</option>
          </select>
        </div>

        <div>
          <label style={{ marginRight: '10px', fontWeight: 'bold' }}>Color:</label>
          <input
            type="color"
            value={shapeColor}
            onChange={(e) => setShapeColor(e.target.value)}
            style={{ width: '50px', height: '35px', borderRadius: '5px', border: 'none' }}
          />
        </div>

        <button
          onClick={addShape}
          style={{
            padding: '10px 20px',
            backgroundColor: '#3b82f6',
            color: 'white',
            border: 'none',
            borderRadius: '8px',
            fontWeight: 'bold',
            cursor: 'pointer'
          }}
        >
          ➕ Add Shape
        </button>

        <button
          onClick={clearCanvas}
          style={{
            padding: '10px 20px',
            backgroundColor: '#ef4444',
            color: 'white',
            border: 'none',
            borderRadius: '8px',
            fontWeight: 'bold',
            cursor: 'pointer'
          }}
        >
          🗑️ Clear Canvas
        </button>

        <span style={{ fontSize: '14px', color: '#6b7280' }}>
          Elements: {canvasState.elements.length} |
          {canvasState.selectedElement && ` Selected: ${canvasState.selectedElement.type}`}
        </span>
      </div>

      {/* Canvas */}
      <Canvas
        elements={canvasState.elements}
        selectedElement={canvasState.selectedElement}
        onUpdateElementPosition={canvasState.updateElementPosition}
        onUpdateElementSize={canvasState.updateElementSize}
        onSelectElement={(elementId) => {
          canvasState.selectElement(elementId);
          canvasState.bringToFront(elementId);
        }}
        width="100%"
        height={600}
        enableGrid={true}
        showGrid={true}
        gridSize={20}
        constrainToCanvas={true}
        draggableProps={{
          enableDragShadow: true,
          enableResizeShadow: true,
          dragScale: 1.05,
          resizeScale: 1.02
        }}
      >
        {(element, isSelected) => {
          const baseStyle = {
            width: '100%',
            height: '100%',
            backgroundColor: element.properties?.fill,
            border: isSelected ? '3px solid #1f2937' : '2px solid #e5e7eb',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            color: 'white',
            fontSize: '12px',
            fontWeight: 'bold',
            cursor: 'move',
            userSelect: 'none',
            transition: 'all 0.2s ease'
          };

          if (element.type === 'circle') {
            return (
              <div style={{ ...baseStyle, borderRadius: '50%' }}>
                {isSelected ? '✨ Selected' : 'Circle'}
              </div>
            );
          }

          if (element.type === 'square') {
            return (
              <div style={{ ...baseStyle, borderRadius: '8px' }}>
                {isSelected ? '✨ Selected' : 'Square'}
              </div>
            );
          }

          return (
            <div style={{ ...baseStyle, borderRadius: '12px' }}>
              {isSelected ? '✨ Selected' : 'Rectangle'}
            </div>
          );
        }}
      </Canvas>

      {/* Instructions */}
      <div style={{
        marginTop: '20px',
        padding: '15px',
        backgroundColor: '#f0f9ff',
        borderRadius: '10px',
        border: '1px solid #bfdbfe'
      }}>
        <h3 style={{ margin: '0 0 10px 0', color: '#1e40af' }}>🎮 How to Use:</h3>
        <ul style={{ margin: 0, paddingLeft: '20px', color: '#1e40af' }}>
          <li>Click "Add Shape" to create new elements</li>
          <li>Drag elements to move them around</li>
          <li>Select an element to see resize handles</li>
          <li>Drag the handles to resize</li>
          <li>Elements automatically snap to the grid</li>
          <li>Elements stay within canvas boundaries</li>
        </ul>
      </div>
    </div>
  );
};

export default InteractiveCanvas;

🎯 Key Features & Use Cases

🎨 Design Tools

Build tools like Figma, Canva, or Sketch with intuitive drag-and-drop interfaces.

// Perfect for building:
- ✨ Diagram editors
- 🎨 Design systems
- 📊 Chart builders
- 🗺️ Map editors
- 📝 Form builders

📊 Dashboard Builders

Create interactive dashboards with draggable widgets and charts.

// Dashboard components:
- 📈 Charts & graphs
- 📋 Cards & widgets
- 🔧 Settings panels
- 📱 Layout editors

🎮 Educational Tools

Build interactive learning environments and simulations.

// Educational applications:
- 🧩 Puzzle games
- 📐 Geometry tools
- 🎯 Quiz builders
- 🗺️ Interactive maps

🔧 Component API

🎨 Canvas Component

The Canvas component is your all-in-one solution for creating beautiful drag-and-drop interfaces. Think of it as a digital workspace where users can intuitively move, resize, and organize elements.

<Canvas
  // Canvas properties
  width="100%"
  height={600}
  className="my-canvas"
  disabled={false}

  // Grid settings
  enableGrid={true}
  showGrid={true}
  gridSize={20}
  gridColor="#e5e7eb"

  // Element management
  elements={elements}
  selectedElement={selectedElement}
  onUpdateElementPosition={updatePosition}
  onUpdateElementSize={updateSize}
  onSelectElement={selectElement}

  // Constraints
  constrainToCanvas={true}

  // Visual effects
  draggableProps={{
    enableDragShadow: true,
    enableResizeShadow: true,
    dragScale: 1.02,
    resizeScale: 1.01
  }}
>
  {(element, isSelected) => (
    // Your custom component here
    <div>Your content</div>
  )}
</Canvas>

📋 Canvas Props Reference

| Prop Name | Type | What It Does | Default Value | |-----------|------|--------------|---------------| | Canvas Properties | | | | | width | string \| number | Sets the canvas width. Use "100%" for responsive layouts | "100%" | | height | string \| number | Sets the canvas height. Numbers are treated as pixels | 600 | | className | string | Custom CSS class names for the canvas container | undefined | | style | React.CSSProperties | Inline styles for the canvas container | {} | | disabled | boolean | Disables all interactions when true | false | | Grid Settings | | | | | enableGrid | boolean | Enables grid snapping for precise element positioning | false | | showGrid | boolean | Shows or hides the grid background pattern | false | | gridSize | number | Size of each grid cell in pixels | 20 | | gridColor | string | Color of the grid lines (any valid CSS color) | "#e5e7eb" | | gridOpacity | number | Transparency of grid lines (0-1) | 1 | | Element Management | | | | | elements | DraggableElement[] | Array of elements to display on the canvas | [] | | selectedElement | DraggableElement \| null | Currently selected element | null | | onUpdateElementPosition | (id: string, position: {x: number, y: number}) => void | Called when element is moved | undefined | | onUpdateElementSize | (id: string, size: {width: number, height: number}, position?: {x: number, y: number}) => void | Called when element is resized | undefined | | onSelectElement | (id: string \| null) => void | Called when element is selected or deselected | undefined | | Interaction | | | | | onCanvasClick | (e: React.MouseEvent) => void | Called when clicking empty canvas space | undefined | | constrainToCanvas | boolean | Prevents elements from leaving canvas boundaries | true | | Visual Effects | | | | | draggableProps.enableDragShadow | boolean | Shows shadow while dragging elements | false | | draggableProps.enableResizeShadow | boolean | Shows shadow while resizing elements | false | | draggableProps.dragScale | number | Scale factor when dragging (1.0 = no scale) | 1.02 | | draggableProps.resizeScale | number | Scale factor when resizing (1.0 = no scale) | 1.01 | | draggableProps.dragShadow | string | Custom shadow CSS for dragging | undefined | | draggableProps.resizeShadow | string | Custom shadow CSS for resizing | undefined |


🎯 useDraggableState Hook

This hook is your state management powerhouse! It handles all the complex logic so you can focus on building amazing user experiences.

const {
  elements,              // Current elements
  selectedElement,       // Selected element
  updateElementPosition, // Update element position
  updateElementSize,     // Update element size
  addElement,            // Add new element
  removeElement,         // Remove element
  selectElement,         // Select element
  bringToFront,          // Bring to front
  sendToBack,            // Send to back
  duplicateElement,      // Duplicate element
  clearSelection         // Clear selection
} = useDraggableState({
  initialElements: [...],
  onElementsChange: (elements) => console.log('Elements changed:', elements)
});

📋 useDraggableState Reference

| Return Value | Type | What It Does | How to Use | |--------------|------|--------------|------------| | State Values | | | | | elements | DraggableElement[] | Current array of all elements | elements.map(el => ...) | | selectedElement | DraggableElement \| null | Currently selected element | selectedElement?.id | | Element Actions | | | | | addElement | (element: DraggableElement) => void | Adds a new element to the canvas | addElement({...}) | | removeElement | (id: string) => void | Removes an element by ID | removeElement('box-1') | | updateElementPosition | (id: string, position: {x: number, y: number}) => void | Changes element position | updateElementPosition('box-1', {x: 100, y: 200}) | | updateElementSize | (id: string, size: {width: number, height: number}, position?: {x: number, y: number}) => void | Changes element size | updateElementSize('box-1', {width: 150, height: 100}) | | Selection Management | | | | | selectElement | (id: string \| null) => void | Selects an element or clears selection | selectElement('box-1') or selectElement(null) | | clearSelection | () => void | Deselects current element | clearSelection() | | Layer Management | | | | | bringToFront | (id: string) => void | Moves element to top layer | bringToFront('box-1') | | sendToBack | (id: string) => void | Moves element to bottom layer | sendToBack('box-1') | | duplicateElement | (id: string) => void | Creates a copy of an element | duplicateElement('box-1') |

📋 Hook Configuration Options

| Option | Type | What It Does | Default Value | |--------|------|--------------|---------------| | initialElements | DraggableElement[] | Elements to show when canvas first loads | [] | | onElementsChange | (elements: DraggableElement[]) => void | Callback when any element changes | undefined |

📋 DraggableElement Type

| Property | Type | What It Is | Example | |----------|------|------------|---------| | id | string | Unique identifier for the element | 'box-1' | | type | string | Element type for custom rendering | 'rectangle' | | position | {x: number, y: number} | Element position in pixels | {x: 100, y: 50} | | size | {width: number, height: number} | Element dimensions in pixels | {width: 200, height: 150} | | zIndex | number | Layer order (higher = on top) | 1 | | properties | Record<string, any> | Custom properties for your rendering | {fill: '#3b82f6', opacity: 0.8} | | constraints | ElementConstraints | Size and behavior restrictions | {minSize: {width: 50, height: 50}} |


🎨 Children Render Function

The children prop is where your creativity shines! It's a function that renders each element on your canvas.

<Canvas {...props}>
  {(element, isSelected) => {
    // element: The element data
    // isSelected: Boolean indicating if this element is selected
    return <YourCustomComponent element={element} isSelected={isSelected} />
  }}
</Canvas>

📋 Render Function Parameters

| Parameter | Type | What It Gives You | Usage Example | |-----------|------|-------------------|---------------| | element | DraggableElement | All data about the current element | element.properties?.fill | | isSelected | boolean | Whether this element is currently selected | isSelected && 'ring-2 ring-blue-500' |


🎨 Customization Guide

Custom Resize Handles

import { ResizeHandle } from 'dragstack';

const CustomHandle = (props) => (
  <ResizeHandle
    {...props}
    size={12}
    color="#3b82f6"
    style={{
      backgroundColor: '#3b82f6',
      border: '2px solid white',
      borderRadius: '50%'
    }}
  />
);

Grid Customization

<Canvas
  enableGrid={true}
  showGrid={true}
  gridSize={30}          // Grid cell size
  gridColor="#cbd5e1"    // Grid color
  gridOpacity={0.3}      // Grid transparency
  constrainToCanvas={true}
>
  {children}
</Canvas>

Custom Shadows and Effects

<Canvas
  draggableProps={{
    enableDragShadow: true,
    enableResizeShadow: true,
    dragShadow: "0 25px 50px rgba(0, 0, 0, 0.25)",
    resizeShadow: "0 10px 25px rgba(0, 0, 0, 0.15)",
    dragScale: 1.05,
    resizeScale: 1.02
  }}
>
  {children}
</Canvas>

📱 Browser Support

| Browser | Version | Status | |---------|---------|--------| | Chrome | 90+ | ✅ Full Support | | Firefox | 88+ | ✅ Full Support | | Safari | 14+ | ✅ Full Support | | Edge | 90+ | ✅ Full Support |

⚙️ Requirements & Compatibility

React Version Support

  • React 18.x - Fully supported and recommended
  • React 19.x - Not yet supported (breaking changes in hooks API)
  • React < 18 - Not supported

Required Dependencies

  • React 18.x - Core dependency
  • React DOM 18.x - Core dependency
  • Tailwind CSS - Required for component styling
  • TypeScript 5.x - Recommended for type safety

Setup for New Projects

If you're starting a new project, here's the complete setup:

# Create React app with Vite
npm create vite@latest my-app -- --template react-ts
cd my-app

# Ensure React 18
npm install react@^18.0.0 react-dom@^18.0.0

# Install and configure Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

# Configure tailwind.config.js
# Add: content: ["./src/**/*.{js,ts,jsx,tsx}"]

# Add Tailwind directives to your CSS
# @tailwind base; @tailwind components; @tailwind utilities;

# Install Dragstack
npm install dragstack

# Start development
npm run dev

🚀 Performance

Canvas Editor is optimized for performance:

  • GPU Accelerated animations using CSS transforms
  • 🎯 Smart Re-rendering with React optimization
  • 📦 Tiny Bundle Size - tree-shakable and minimal dependencies
  • 🔄 60 FPS smooth interactions even with 100+ elements
  • 💾 Memory Efficient - no memory leaks or excessive allocations

Performance Tips

  1. Use React.memo for custom child components
  2. Enable grid snapping for smoother movements
  3. Use constraints to prevent unnecessary calculations
  4. Avoid inline functions in render props

🤝 Contributing

We love contributions! 🎉

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Setup

git clone https://github.com/your-username/canvas-editor.git
cd canvas-editor
npm install
npm run dev
npm test

📄 License

MIT © Canvas Editor Team


🙏 Acknowledgments

Built with ❤️ by the Canvas Editor team, inspired by modern design tools and the amazing React community.


📚 Need Help?


⭐ Star this repo if you find it helpful! It helps us know we're making a difference.