@mistersee/global-table
v0.1.5
Published
A lightweight, Tailwind-first React data table for product UIs that need sticky columns, pagination, column visibility, drag-and-drop reordering, and flexible styling — without a heavy grid framework.
Readme
@mistersee/global-table
A lightweight, Tailwind-first React data table for product UIs that need sticky columns, pagination, column visibility, drag-and-drop reordering, and flexible styling — without a heavy grid framework.

Features
- Sticky columns — pin any fields left or right while scrolling horizontally
- Hybrid pagination — controlled or uncontrolled (
limit/skip) - Column visibility — toggle which fields are shown
- Column reorder — optional drag-and-drop header reordering
- Custom actions — inject any React nodes into the actions column
- Row index — optional leading
#column (pagination-aware) - Field styling — map Tailwind classes to fields from outside (e.g. price → green)
- Design slots — style structure via
classNameswithout rewriting markup - Sensible defaults — column min-width
120px, max-width310px - Tailwind CSS only — no CSS-in-JS runtime for styles
Install
npm install @mistersee/global-table
# or
bun add @mistersee/global-table
# or
pnpm add @mistersee/global-tablePeer dependencies
npm install react react-dom framer-motion tailwindcss| Package | Version |
| --- | --- |
| react | >=18 |
| react-dom | >=18 |
| framer-motion | >=11 |
| tailwindcss | >=4 |
Tailwind v4 setup
@mistersee/global-table ships utility class names inside its ESM build. Your app must scan them:
@import 'tailwindcss';
@source "../node_modules/@mistersee/global-table/dist";Adjust the @source path to match your project layout.
Quick start
import { MisterSeeGlobalTable } from '@mistersee/global-table'
import type { ColumnDef } from '@mistersee/global-table'
type Customer = {
id: string
code: string
firstName: string
lastName: string
company: string
status: 'active' | 'inactive' | 'pending'
orders: number
spent: number
}
const columns: ColumnDef<Customer>[] = [
{ key: 'code', header: 'Code' },
{ key: 'firstName', header: 'First name' },
{ key: 'lastName', header: 'Last name' },
{ key: 'company', header: 'Company' },
{ key: 'status', header: 'Status' },
{ key: 'orders', header: 'Orders' },
{
key: 'spent',
header: 'Spent',
cell: (row) => `$${row.spent.toFixed(2)}`,
},
{ key: 'actions', header: 'Actions' },
]
export function CustomersTable({ rows }: { rows: Customer[] }) {
return (
<MisterSeeGlobalTable
columns={columns}
data={rows}
getRowId={(row) => row.id}
showIndex
stickyRight={['actions']}
fieldClassNames={{
orders: 'text-blue-500',
spent: 'text-green-500 font-medium',
}}
renderActions={(row) => (
<button type="button" onClick={() => console.log(row.id)}>
View
</button>
)}
classNames={{
root: 'rounded-xl bg-white p-3 shadow-sm',
th: 'bg-zinc-100 font-medium text-zinc-700',
}}
/>
)
}UI overview
The screenshot above shows a typical configuration:
| Area | Behavior |
| --- | --- |
| Header | Drag handles when reordering is enabled |
| # | Optional index column |
| Orders / Spent | Colored via fieldClassNames |
| Actions | Sticky right + custom renderActions |
| Footer | Column toggle, page size select, pagination |
Column definition
type ColumnDef<T> = {
key: string
header: React.ReactNode
width?: number | 'auto' // omit = default min 120 / max 310
sticky?: 'left' | 'right' // optional; prefer stickyLeft / stickyRight props
defaultVisible?: boolean
cell?: (row: T, rowIndex: number) => React.ReactNode
className?: string
}Width defaults (when width is omitted):
min-width: 120pxmax-width: 310px
Use width: 'auto' for content-sized columns such as action button groups.
MisterSeeGlobalTable props
Data & columns
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| columns | ColumnDef<T>[] | — | Column definitions |
| data | T[] | — | Row data for the current view (or full list in uncontrolled pagination) |
| getRowId | (row, index) => string | row index | Stable row key |
| defaultVisibleColumns | string[] | all visible | Initial visible column keys |
| loading | boolean | false | Shows skeleton rows |
Sticky columns
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| stickyLeft | string[] | — | Column keys pinned to the left |
| stickyRight | string[] | — | Column keys pinned to the right |
<MisterSeeGlobalTable
stickyLeft={['firstName']}
stickyRight={['actions']}
{...props}
/>When stickyLeft / stickyRight are provided, they become the source of truth for sticky sides.
Pagination (hybrid)
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| limit | number | internal 10 | Page size |
| skip | number | internal 0 | Offset |
| total | number | data.length | Total row count (useful for server paging) |
| onPaginationChange | ({ limit, skip }) => void | — | Enables controlled mode when used with limit + skip |
| limitOptions | number[] | [10, 20, 50] | Page-size options |
Uncontrolled (client-side): pass the full data array; the table slices pages internally.
Controlled (API / server): pass the current page data, plus limit, skip, total, and onPaginationChange.
Column visibility toggle
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| showColumnToggle | boolean | true | Show/hide the Columns button |
| columnsLabel | ReactNode | "Columns" | Button label |
<MisterSeeGlobalTable showColumnToggle={false} {...props} />Column reorder (drag & drop)
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| columnOrder | string[] | column definition order | Controlled key order |
| onColumnOrderChange | (next) => void | — | Called after a successful drop |
| enableColumnReorder | boolean | true if onColumnOrderChange is set | Force enable/disable |
const [columnOrder, setColumnOrder] = useState(columns.map((c) => c.key))
<MisterSeeGlobalTable
columnOrder={columnOrder}
onColumnOrderChange={setColumnOrder}
enableColumnReorder
{...props}
/>To disable reordering entirely:
<MisterSeeGlobalTable enableColumnReorder={false} {...props} />Actions column
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| renderActions | (row, rowIndex) => ReactNode | — | Custom action UI per row |
| actionsKey | string | "actions" | Target column key |
| actionsHeader | ReactNode | "Actions" | Header if column is auto-created |
<MisterSeeGlobalTable
renderActions={(row) => (
<div className="flex gap-1.5">
<button type="button">View</button>
<button type="button">Edit</button>
</div>
)}
stickyRight={['actions']}
{...props}
/>Row index
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| showIndex | boolean | false | Show leading index column |
| indexHeader | ReactNode | "#" | Index header label |
| indexWidth | number | 56 | Index column width (px) |
| renderIndex | (row, rowIndex, index) => ReactNode | index | Custom index cell |
index is pagination-aware: skip + rowIndex + 1.
Field colors / classes
| Prop | Type | Description |
| --- | --- | --- |
| fieldClassNames | Record<string, string \| ((row, rowIndex) => string \| undefined)> | Per-field Tailwind classes |
<MisterSeeGlobalTable
fieldClassNames={{
spent: 'text-green-500 font-medium',
orders: 'text-blue-500',
status: (row) =>
row.status === 'active'
? 'text-emerald-600'
: row.status === 'pending'
? 'text-amber-600'
: 'text-zinc-400',
}}
{...props}
/>Design slots (classNames)
| Slot | Applies to |
| --- | --- |
| root | Outer wrapper |
| tableWrapper | Scroll container |
| table | <table> |
| thead / th | Header |
| tbody / tr / td | Body |
| skeleton | Loading rows |
| columnToggle | Columns button |
| pagination | Footer controls |
<MisterSeeGlobalTable
classNames={{
root: 'rounded-xl bg-white p-3 shadow-sm',
th: 'bg-zinc-100 font-medium text-zinc-700',
td: 'text-zinc-600',
}}
{...props}
/>Sticky headers/cells respect classNames.th / classNames.td (including custom backgrounds such as bg-orange-500).
Loading state
<MisterSeeGlobalTable columns={columns} data={rows} loading />When loading is true, skeleton rows are rendered to match the current page size and visible column count.
Exports
import {
MisterSeeGlobalTable,
INDEX_COLUMN_KEY,
DEFAULT_COL_MIN_WIDTH, // 120
DEFAULT_COL_MAX_WIDTH, // 310
} from '@mistersee/global-table'
import type {
ColumnDef,
MisterSeeGlobalTableProps,
MisterSeeGlobalTableClassNames,
StickySide,
} from '@mistersee/global-table'Local development
bun install
bun run dev # demo app
bun run build # library → dist/
bun run build:demo # demo production buildDesign principles
- Composable, not opinionated — bring your own buttons, badges, and Tailwind tokens
- Opt-in complexity — drag-and-drop, index, and column toggle can be turned off
- Predictable layout — default column bounds keep dense tables readable
- Peer-first styling — consumers own Tailwind; the library stays thin
Developed by
- MisterSee VANG
- [email protected]
- Senior Software Engineer
