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

table-minimap

v2.0.0

Published

Tired of explaining to your boss why having too many columns in a table isn't a good idea? Give him Table-Minimap so he'll leave you alone.

Readme

Table Minimap

A framework-agnostic minimap component for navigating large HTML tables. Inspired by VS Code's minimap, this library provides a visual overview of wide tables and enables drag-to-scroll navigation.

npm version bundle size license

Changelog

Features

  • Framework Agnostic - Works with vanilla JS, React, Vue, Angular, or any framework
  • Zero Dependencies - No external runtime dependencies
  • Tree Shakable - ESM + CommonJS outputs
  • Two Render Modes - Simple columns or VS Code-like canvas preview
  • Touch Support - Full mobile support with pinch-to-zoom and double-tap gestures
  • Auto Updates - Responds to resize, scroll, and DOM mutations
  • Accessible - ARIA attributes and keyboard navigation support
  • Themeable - CSS custom properties for easy customization
  • shadcn/ui Ready - Dedicated theme using shadcn CSS variables
  • TypeScript - Fully typed with declaration files

Installation

npm install table-minimap
yarn add table-minimap
pnpm add table-minimap

Quick Start

import { TableMinimap } from 'table-minimap';

// Using a CSS selector
const minimap = new TableMinimap('#my-table');

// Or using a DOM element
const table = document.querySelector('table');
const minimap = new TableMinimap(table);

// Don't forget to cleanup when done
minimap.destroy();

Usage

Basic Usage

<div class="table-container" style="overflow-x: auto;">
  <table id="my-table">
    <thead>
      <tr>
        <th>Column 1</th>
        <th>Column 2</th>
        <!-- ... more columns ... -->
      </tr>
    </thead>
    <tbody>
      <!-- ... rows ... -->
    </tbody>
  </table>
</div>
<!-- Minimap will be inserted after the container -->
import { TableMinimap } from 'table-minimap';

const minimap = new TableMinimap('#my-table');

With Configuration

const minimap = new TableMinimap('#my-table', {
  mode: 'canvas', // 'columns' | 'canvas'
  height: 50, // Height in pixels
  position: 'bottom', // 'top' | 'bottom' | 'fixed'
  draggable: true, // Enable drag navigation
  showViewport: true, // Show viewport indicator
  canvasClipboard: true, // Right-click canvas column to copy
  canvasClipboardLabel: 'Spalte kopieren', // i18n label
  canvasColumnMarking: true, // Right-click canvas column to mark/unmark
  markedColumns: [2, 5], // Optional initial marks
  canvasColumnHiding: true, // Right-click canvas column to collapse/expand
  hiddenColumns: [1], // Optional initial collapsed columns
});

Compact Fixed Mode

For a smaller, hideable floating minimap, enable compact together with position: 'fixed':

const minimap = new TableMinimap('#my-table', {
  position: 'fixed',
  fixedWidth: 260,
  fixedPosition: 'bottom-right',
  compact: true,
  height: 44,
});
  • Collapses into a small translucent dot in the bottom-right corner
  • Expands on click or keyboard focus
  • Remains clickable when collapsed
  • Uses a short ease-in-out transition for width/height/opacity
  • Double-click fixed minimaps to move them to the next corner

Canvas Mode (VS Code-like)

Canvas mode renders a compressed pixel preview of the entire table:

const minimap = new TableMinimap('#data-table', {
  mode: 'canvas',
  height: 60,
  canvasClipboard: true,
});
  • Darker pixels indicate cells with more content
  • Empty cells appear lighter
  • Provides a visual "density map" of your data
  • Optional: right-click a column and use Copy column to clipboard
  • Optional: right-click a column and use Mark/Unmark column (bookmark icon in header)
  • Optional: right-click and use Unmark all columns to clear all bookmarks at once
  • Optional: right-click a column and use Collapse/Expand column (dims column with dark overlay and narrows table cell)
  • Optional: right-click and use Expand all columns to reveal all collapsed columns

Canvas Mode with Zoom

Enable zoom functionality in canvas mode for detailed inspection:

const minimap = new TableMinimap('#data-table', {
  mode: 'canvas',
  height: 80,
  zoomable: true,
  canvasClipboard: true,
  minZoom: 1,
  maxZoom: 10,
  zoomSpeed: 0.1,
});

// Programmatic zoom control
minimap.setZoom(3, 0.5); // Zoom to 3x at center
minimap.zoomToColumns(5, 15); // Zoom to columns 5-15
minimap.resetZoom(); // Reset to full view

Zoom Controls:

  • Scroll wheel on minimap to zoom in/out
  • Drag the canvas when zoomed to pan
  • A position indicator appears at the bottom when zoomed

Mobile Support (Canvas Mode)

Canvas mode provides full mobile/touch support:

const minimap = new TableMinimap('#my-table', {
  mode: 'canvas',
  zoomable: true, // Enable pinch-to-zoom
  canvasColumnMarking: true, // Enable double-tap context menu
  canvasColumnHiding: true,
});

Touch Gestures:

  • Pinch-to-zoom - Two-finger zoom like Google Maps
  • Double-tap - Opens the context menu for marking/collapsing columns
  • Single-tap - Navigates to the tapped column
  • Pan/drag - When zoomed, drag to pan the view

Accessibility:

  • Context menu actions have 48px minimum touch targets on touch devices
  • Compact desktop styling with standard sizing on mouse devices
  • Touch-friendly padding and spacing (via @media (pointer: coarse))

Position Options

// Minimap above the table
const minimap = new TableMinimap('#my-table', {
  position: 'top',
});

// Minimap below the table (default)
const minimap = new TableMinimap('#my-table', {
  position: 'bottom',
});

Configuration

Options Interface

interface TableMinimapOptions {
  /**
   * Rendering mode
   * - "columns": Simple column segments (default)
   * - "canvas": VS Code-like pixel preview
   */
  mode?: 'columns' | 'canvas';

  /**
   * Height of the minimap in pixels
   * @default 40
   */
  height?: number;

  /**
   * Position relative to the table
   * @default "bottom"
   */
  position?: 'top' | 'bottom' | 'fixed';

  /**
   * Corner position when using position: 'fixed'
   * Double-click fixed minimaps to cycle to the next corner.
   * @default "bottom-right"
   */
  fixedPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';

  /**
   * Enable compact floating mode for fixed minimaps
   * @default false
   */
  compact?: boolean;

  /**
   * Enable drag navigation
   * @default true
   */
  draggable?: boolean;

  /**
   * Show viewport indicator
   * @default true
   */
  showViewport?: boolean;

  /**
   * Enable zoom in canvas mode (scroll wheel)
   * @default false
   */
  zoomable?: boolean;

  /**
   * Minimum zoom level
   * @default 1
   */
  minZoom?: number;

  /**
   * Maximum zoom level
   * @default 10
   */
  maxZoom?: number;

  /**
   * Zoom speed multiplier
   * @default 0.1
   */
  zoomSpeed?: number;

  /**
   * Enable right-click context menu in canvas mode to copy a column
   * @default false
   */
  canvasClipboard?: boolean;

  /**
   * Label text used for the canvas clipboard action (useful for i18n)
   * @default "Copy column to clipboard"
   */
  canvasClipboardLabel?: string;

  /**
   * Enable right-click context menu action to mark/unmark canvas columns
   * @default false
   */
  canvasColumnMarking?: boolean;

  /**
   * Label text used for the mark action in the canvas context menu
   * @default "Mark column"
   */
  canvasMarkColumnLabel?: string;

  /**
   * Label text used for the unmark action in the canvas context menu
   * @default "Unmark column"
   */
  canvasUnmarkColumnLabel?: string;

  /**
   * Label text used for the unmark-all action in the canvas context menu
   * @default "Unmark all columns"
   */
  canvasUnmarkAllColumnsLabel?: string;

  /**
   * Enable right-click context menu action to collapse/expand table columns
   * @default false
   */
  canvasColumnHiding?: boolean;

  /**
   * Label text used for the collapse action in the canvas context menu
   * @default "Collapse column"
   */
  canvasHideColumnLabel?: string;

  /**
   * Label text used for the expand action in the canvas context menu
   * @default "Expand column"
   */
  canvasShowColumnLabel?: string;

  /**
   * Label text used for the expand-all action in the canvas context menu
   * @default "Expand all columns"
   */
  canvasShowAllColumnsLabel?: string;

  /**
   * Width in pixels for collapsed table columns
   * @default 10
   */
  collapsedColumnWidth?: number;

  /**
   * Initially marked canvas column indices
   * @default []
   */
  markedColumns?: number[];

  /**
   * Called whenever marked canvas columns change
   */
  onMarkedColumnsChange?: (details: {
    markedColumns: number[];
    changedColumnIndex: number | null;
    isMarked: boolean | null;
    headers: string[];
    table: HTMLTableElement;
  }) => void;

  /**
   * Initially collapsed canvas column indices
   * @default []
   */
  hiddenColumns?: number[];

  /**
   * Called whenever collapsed canvas columns change
   */
  onHiddenColumnsChange?: (details: {
    hiddenColumns: number[];
    changedColumnIndex: number | null;
    isHidden: boolean | null;
    headers: string[];
    table: HTMLTableElement;
  }) => void;
}

Default Values

| Option | Default | | ---------------------- | ---------------------------- | | mode | 'columns' | | height | 40 | | position | 'bottom' | | fixedPosition | 'bottom-right' | | compact | false | | draggable | true | | showViewport | true | | zoomable | false | | canvasClipboard | false | | canvasClipboardLabel | 'Copy column to clipboard' | | canvasColumnMarking | false | | canvasMarkColumnLabel| 'Mark column' | | canvasUnmarkColumnLabel | 'Unmark column' | | canvasUnmarkAllColumnsLabel | 'Unmark all columns' | | canvasColumnHiding | false | | canvasHideColumnLabel | 'Collapse column' | | canvasShowColumnLabel | 'Expand column' | | canvasShowAllColumnsLabel | 'Expand all columns' | | collapsedColumnWidth | 10 | | markedColumns | [] | | hiddenColumns | [] | | minZoom | 1 | | maxZoom | 10 | | zoomSpeed | 0.1 |

API Reference

Constructor

new TableMinimap(selector: string | HTMLTableElement, options?: TableMinimapOptions)

Parameters:

  • selector - CSS selector string or HTMLTableElement
  • options - Optional configuration object

Throws:

  • Error if element is not found
  • Error if element is not a <table>

Methods

destroy(): void

Removes the minimap and cleans up all event listeners, observers, and DOM elements. Always call this when the table is removed from the DOM.

minimap.destroy();

refresh(): void

Forces a refresh of the minimap. Useful after programmatic table modifications.

// After dynamically updating table content
minimap.refresh();

getScrollState(): ScrollState

Returns the current scroll state.

const state = minimap.getScrollState();
console.log(state);
// {
//   scrollLeft: 200,
//   scrollWidth: 2000,
//   clientWidth: 500,
//   viewportRatio: 0.25,
//   positionRatio: 0.13
// }

getColumns(): ColumnInfo[]

Returns information about detected columns.

const columns = minimap.getColumns();
console.log(columns);
// [
//   { index: 0, width: 120, widthPercent: 6 },
//   { index: 1, width: 80, widthPercent: 4 },
//   ...
// ]

scrollToColumn(columnIndex: number, smooth?: boolean): void

Scrolls the table to bring a specific column into view.

// Scroll to column 10 with smooth animation
minimap.scrollToColumn(10, true);

// Instant scroll
minimap.scrollToColumn(10, false);

getZoomState(): ZoomState

Returns the current zoom state (canvas mode only).

const zoom = minimap.getZoomState();
console.log(zoom);
// {
//   level: 2.5,
//   panX: 0.3,
//   isMinZoom: false,
//   isMaxZoom: false
// }

setZoom(level: number, panX?: number): void

Sets the zoom level programmatically (canvas mode only).

// Zoom to 3x at current position
minimap.setZoom(3);

// Zoom to 5x centered at 50% of table width
minimap.setZoom(5, 0.5);

resetZoom(): void

Resets zoom to show the full table overview.

minimap.resetZoom();

zoomToColumns(startCol: number, endCol: number): void

Zooms to show a specific column range (canvas mode only).

// Zoom to show columns 10-20
minimap.zoomToColumns(10, 20);

getMarkedColumns(): number[]

Returns currently marked canvas column indices.

const marked = minimap.getMarkedColumns();
console.log(marked); // e.g. [1, 4, 7]

setMarkedColumns(columnIndices: number[]): void

Replaces marked canvas columns programmatically.

minimap.setMarkedColumns([0, 3, 8]);

clearMarkedColumns(): void

Clears all marked canvas columns programmatically.

minimap.clearMarkedColumns();

getHiddenColumns(): number[]

Returns currently collapsed canvas column indices.

const hidden = minimap.getHiddenColumns();
console.log(hidden); // e.g. [0, 3, 9]

setHiddenColumns(columnIndices: number[]): void

Replaces collapsed canvas columns programmatically. Collapsed columns appear with a dark overlay in the canvas and are narrowed in the real table.

minimap.setHiddenColumns([2, 4, 6]);

clearHiddenColumns(): void

Expands all collapsed columns programmatically.

minimap.clearHiddenColumns();

Persisting Marked Columns

const saved = JSON.parse(localStorage.getItem('table-marks') ?? '[]') as number[];

const minimap = new TableMinimap('#data-table', {
  mode: 'canvas',
  canvasColumnMarking: true,
  markedColumns: saved,
  onMarkedColumnsChange: ({ markedColumns }) => {
    localStorage.setItem('table-marks', JSON.stringify(markedColumns));
  },
});

Persisting Collapsed Columns

const savedHidden = JSON.parse(localStorage.getItem('table-hidden') ?? '[]') as number[];

const minimap = new TableMinimap('#data-table', {
  mode: 'canvas',
  canvasColumnHiding: true,
  hiddenColumns: savedHidden,
  onHiddenColumnsChange: ({ hiddenColumns }) => {
    localStorage.setItem('table-hidden', JSON.stringify(hiddenColumns));
  },
});

Styling

CSS Custom Properties

Override these CSS variables to customize the minimap appearance:

:root {
  --tm-background: #f5f5f5;
  --tm-border: #e0e0e0;
  --tm-viewport-color: rgba(0, 120, 212, 0.3);
  --tm-viewport-border: rgba(0, 120, 212, 0.8);
  --tm-height: 40px;
  --tm-column-color: #d0d0d0;
  --tm-column-gap: 1px;
  --tm-border-radius: 4px;
  --tm-canvas-empty: #f0f0f0;
  --tm-canvas-filled: #606060;
  --tm-context-menu-bg: #ffffff;
  --tm-context-menu-border: #d4dce7;
  --tm-context-menu-text: #0f172a;
  --tm-context-menu-hover-text: #0369a1;
  --tm-context-menu-status: #475569;
  --tm-context-menu-shadow: 0 8px 24px rgba(15, 23, 42, 0.22);
}

Dark Mode

The library automatically supports prefers-color-scheme: dark:

@media (prefers-color-scheme: dark) {
  :root {
    --tm-background: #2d2d2d;
    --tm-border: #404040;
    --tm-viewport-color: rgba(100, 180, 255, 0.25);
    --tm-viewport-border: rgba(100, 180, 255, 0.7);
    --tm-column-color: #505050;
    --tm-canvas-empty: #3a3a3a;
    --tm-canvas-filled: #a0a0a0;
  }
}

CSS Classes

| Class | Description | | ------------------------ | --------------------------------- | | .tm-minimap | Main container | | .tm-minimap--top | Applied when position is 'top' | | .tm-minimap--bottom | Applied when position is 'bottom' | | .tm-columns | Columns container (columns mode) | | .tm-column | Individual column segment | | .tm-canvas | Canvas element (canvas mode) | | .tm-viewport | Viewport indicator | | .tm-viewport--dragging | Applied during drag | | .tm-viewport--disabled | Applied when draggable is false |

Custom Theme Example

/* Blue theme */
.tm-minimap {
  --tm-background: #e3f2fd;
  --tm-border: #bbdefb;
  --tm-viewport-color: rgba(25, 118, 210, 0.3);
  --tm-viewport-border: #1976d2;
  --tm-column-color: #90caf9;
}

shadcn/ui Integration

For projects using shadcn/ui, import the dedicated stylesheet that uses shadcn's CSS variables:

import { TableMinimap } from 'table-minimap';
import 'table-minimap/shadcn.css'; // Use shadcn theme instead of style.css

const minimap = new TableMinimap('#my-table');

The shadcn theme automatically uses your project's color scheme:

| Minimap Variable | shadcn Variable | | ---------------------- | -------------------------- | | --tm-background | --muted | | --tm-border | --border | | --tm-viewport-color | --primary (with opacity) | | --tm-viewport-border | --primary | | --tm-column-color | --muted-foreground | | --tm-border-radius | --radius |

This ensures the minimap matches your shadcn/ui theme including dark mode support.

shadcn/ui + React Example

'use client';

import { useEffect, useRef } from 'react';
import { TableMinimap } from 'table-minimap';
import 'table-minimap/shadcn.css';

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';

export function DataTableWithMinimap({ data }: { data: any[] }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const minimapRef = useRef<TableMinimap | null>(null);

  useEffect(() => {
    const table = containerRef.current?.querySelector('table');
    if (table) {
      minimapRef.current = new TableMinimap(table as HTMLTableElement, {
        mode: 'columns',
        height: 36,
      });
    }

    return () => {
      minimapRef.current?.destroy();
    };
  }, []);

  return (
    <div ref={containerRef} className="overflow-x-auto rounded-md border">
      <Table>
        <TableHeader>
          <TableRow>{/* Your headers */}</TableRow>
        </TableHeader>
        <TableBody>{/* Your rows */}</TableBody>
      </Table>
    </div>
  );
}

Framework Integration

React

import { useEffect, useRef } from 'react';
import { TableMinimap } from 'table-minimap';

function DataTable() {
  const tableRef = useRef<HTMLTableElement>(null);
  const minimapRef = useRef<TableMinimap | null>(null);

  useEffect(() => {
    if (tableRef.current) {
      minimapRef.current = new TableMinimap(tableRef.current);
    }

    return () => {
      minimapRef.current?.destroy();
    };
  }, []);

  return (
    <div style={{ overflowX: 'auto' }}>
      <table ref={tableRef}>{/* ... */}</table>
    </div>
  );
}

Vue 3

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { TableMinimap } from 'table-minimap';

const tableRef = ref<HTMLTableElement | null>(null);
let minimap: TableMinimap | null = null;

onMounted(() => {
  if (tableRef.value) {
    minimap = new TableMinimap(tableRef.value);
  }
});

onUnmounted(() => {
  minimap?.destroy();
});
</script>

<template>
  <div style="overflow-x: auto;">
    <table ref="tableRef">
      <!-- ... -->
    </table>
  </div>
</template>

Angular

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

@Component({
  selector: 'app-data-table',
  template: `
    <div style="overflow-x: auto;">
      <table #tableElement>
        <!-- ... -->
      </table>
    </div>
  `,
})
export class DataTableComponent implements AfterViewInit, OnDestroy {
  @ViewChild('tableElement') tableRef!: ElementRef<HTMLTableElement>;
  private minimap: TableMinimap | null = null;

  ngAfterViewInit() {
    this.minimap = new TableMinimap(this.tableRef.nativeElement);
  }

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

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Requires:

  • ResizeObserver
  • MutationObserver
  • PointerEvents

License

MIT © Kevin Imig

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

  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