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

@pavan_brahmaji/dragboard

v1.0.0

Published

Zero-dependency drag-and-drop dashboard grid with live resize, TypeScript types, and no build step.

Downloads

16

Readme

@pavanbrahmaji0/dragboard

A zero-dependency, framework-agnostic drag-and-drop dashboard library with full TypeScript support.

Works in vanilla JS, React, Angular, Vue — no build step required.

Features

  • Drag & drop cards from a sidebar onto a responsive grid
  • Resize cards in 8 directions (se, sw, ne, nw, n, s, e, w) — configurable per card
  • 4 built-in widgets: KPI metric, Analytics chart (Canvas), Data table, Notes
  • 6 grid background patterns: cubes, dots, lines, columns, dashed, cross
  • 5 sidebar positions: left, right, top, bottom, float
  • Lock mode: disables all interactions, hides close buttons, fades the grid
  • Full TypeScript types — ambient .d.ts declarations, zero build step
  • 30+ CSS custom properties for scoped theming
  • Custom icons — any HTML string, SVG, or icon-font tag
  • Per-card constraints: minW/maxW/minH/maxH, noMove, noResize, locked
  • Events: card:added, card:removed, card:moved, card:resized, dropped, change, and more
  • Zero dependencies — no React, no Angular, no D3, no build tools needed

Installation

npm install @pavanbrahmaji0/dragboard

Quick Start

<link rel="stylesheet" href="node_modules/@pavanbrahmaji0/dragboard/src/dashboard.css">

<div id="root">
  <div id="sidebar"></div>
</div>

<script type="module">
  import { Dashboard } from '@pavanbrahmaji0/dragboard';

  const dash = await Dashboard.create({
    container: '#root',
    sidebar:   '#sidebar',
    columns:   12,
    rowHeight: 80,
    gap:       8,
  });
</script>

Options

const dash = await Dashboard.create({
  container:          '#root',          // required
  sidebar:            '#sidebar',       // optional
  configPath:         './widget-config.json', // JSON defaults (optional)

  columns:            12,
  rowHeight:          80,
  gap:                8,

  sidebarPosition:    'left',           // 'left'|'right'|'top'|'bottom'|'float'
  gridPattern:        'cubes',          // 'cubes'|'dots'|'lines'|'columns'|'dashed'|'cross'|'none'
  gridBackground:     '#ffffff',
  gridPatternColor:   '#e2e8f0',

  disappearOnDrop:    true,             // sidebar item disappears after first drop
  draggable:          true,
  cardConfigurable:   false,            // show ⚙ gear button on cards
  resizeHandles:      'se',             // 'none'|'se'|'2side'|'4corners'|'all'|'n,s,e,w,...'
  showGridInEditMode: true,

  theme: {
    '--db-card-radius':      '4px',
    '--db-card-header-bg':   '#0f172a',
  },

  cssClasses: {
    card:        'my-card',
    cardHeader:  'my-header',
    cardBody:    'my-body',
    sidebarItem: 'my-item',
  },

  icons: {
    drag:   '<i class="fa fa-grip-vertical"></i>',
    close:  '<i class="fa fa-times"></i>',
    config: '<i class="fa fa-cog"></i>',
  },

  initialCards: [
    { widgetType: 'kpi',   x: 0, y: 0, w: 2, h: 2, config: { title: 'Revenue', value: '84,200' } },
    { widgetType: 'chart', x: 2, y: 0, w: 4, h: 3 },
  ],
});

API

// Add / remove cards
dash.addCard({ widgetType, x, y, w, h, config });
dash.removeCard(cardId);

// Layout
dash.getLayout();          // → serialisable array
dash.loadLayout(array);
dash.compact();            // pack cards upward

// Lock / unlock
dash.disable();            // view mode — no drag/resize/drop
dash.enable();

// Batch (no intermediate redraws)
dash.batchUpdate(() => {
  dash.addCard({ ... });
  dash.addCard({ ... });
});

// Query
dash.isAreaEmpty(x, y, w, h, skipId?); // → boolean

// Events
dash.on('card:added',   ({ card }) => { });
dash.on('card:removed', ({ card }) => { });
dash.on('card:moved',   ({ card }) => { });
dash.on('card:resized', ({ card }) => { });
dash.on('dropped',      ({ card, x, y }) => { });
dash.on('change',       (layout) => { });

// Teardown
dash.destroy();

Custom Widget

const MyWidget = {
  type:          'my-widget',
  label:         'My Widget',
  icon:          '🚀',
  description:   'Does something cool',
  defaultConfig: { w: 3, h: 2, title: 'My Widget' },
  configFields:  [
    { name: 'color', label: 'Color', type: 'color' },
  ],
  render(container, config) {
    container.innerHTML = `<div style="color:${config.color}">Hello!</div>`;
  },
  update(container, config) {
    container.querySelector('div').style.color = config.color;
  },
  destroy(container) { /* cleanup observers, timers, etc. */ },
};

const dash = await Dashboard.create({
  widgets: [MyWidget],
  // ...
});

Angular Integration

import { Component, AfterViewInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';

@Component({
  template: `
    <div #root style="display:flex;flex:1;min-height:0">
      <div #sidebar></div>
    </div>
  `
})
export class DashboardComponent implements AfterViewInit, OnDestroy {
  @ViewChild('root')    rootRef!: ElementRef;
  @ViewChild('sidebar') sidebarRef!: ElementRef;
  private dash: any;

  async ngAfterViewInit() {
    const { Dashboard } = await import('@pavanbrahmaji0/dragboard');
    this.dash = await Dashboard.create({
      container: this.rootRef.nativeElement,
      sidebar:   this.sidebarRef.nativeElement,
    });
  }

  ngOnDestroy() { this.dash?.destroy(); }
}

CSS Custom Properties

| Variable | Default | Controls | |---|---|---| | --db-card-bg | #ffffff | Card background | | --db-card-border | #e2e8f0 | Card border | | --db-card-radius | 10px | Card border radius | | --db-card-shadow | 0 2px 12px … | Card shadow | | --db-card-header-bg | #1e293b | Header background | | --db-card-header-color | #ffffff | Header text | | --db-card-header-height | 40px | Header height | | --db-card-btn-size | 26px | Button size | | --db-sidebar-bg | #ffffff | Sidebar background | | --db-sidebar-border | #e2e8f0 | Sidebar border | | --db-grid-line-color | #e2e8f0 | Grid pattern color | | --db-grid-line-width | 1px | Grid line thickness |

License

MIT