Spreadsheet-like table UI for SolidJS.
Demo:https://alexanderrafferty.com/projects/solid-tabular/
- 🚀 Virtualization: Efficiently renders large datasets using
@tanstack/solid-virtual. - 🖱️ Selection: Excel-like cell and range selection.
- ✏️ Editing: In-place cell editing.
- 📏 Resizing: Draggable column resizing.
- 📋 Clipboard: Copy and paste support.
- ⌨️ Keyboard Navigation: Arrow keys, Tab, Enter, etc.
- 🎨 Customizable: CSS variables for theming.
npm install solid-tabular
# or
pnpm add solid-tabular
# or
yarn add solid-tabularThe example below shows how to display a static data table using the createTableState utility function.
import{Table,createTableState}from'solid-tabular'import'solid-tabular/styles.css'functionApp(){consttableProps=createTableState([{A: 1,B: 2,C: 3},{A: 4,B: 5,C: 6},{A: 7,B: 8,C: 9},])return(<divstyle={{width: '100%',padding: '20px'}}><Table{...tableProps}columnsResizeable/></div>)}exportdefaultAppThis example controls the table data explicitly for more control.
import{createSignal}from'solid-js'import{Table,ActiveRange}from'solid-tabular'import'solid-tabular/dist/index.css'functionApp(){const[activeRange,setActiveRange]=createSignal<ActiveRange>()constcolumns=['A','B','C']const[data,setData]=createSignal([['A1','B1','C1'],['A2','B2','C2'],['A3','B3','C3'],])return(<divstyle={{width: '100%',padding: '20px'}}><Tablecolumns={columns}numRows={data().length}getCellValue={(row,col)=>data()[row][columns.indexOf(col)]}activeRange={activeRange()}setActiveRange={setActiveRange}/></div>)}The Table component is the main entry point, and has two generic type parameters Column and Value.
| Prop | Type | Description |
|---|---|---|
columns | Column[] | Array of column objects. |
numRows | number | Total number of rows. |
getCellValue | (row: number, column: Column) => Value | Function to get the value for a cell. |
setCellValue | (row: number, column: Column, value: Value) => void | Function to update the value of a cell. |
activeRange | ActiveRange | The current selection state. |
setActiveRange | (range: ActiveRange) => void | Callback to update the selection state. |
columnsResizeable | boolean | Enables column resizing. |
columnsRenameable | boolean | Enables column renaming. |
addColumnButton | boolean | Whether to render an "add column" button. |
addRowButton | boolean | Whether to render an "add row" button. |
cellHeight | number | Height of cells in pixels (default: 29). |
getCellContent | (column: Column) => Component<CellContentProps<Value>> | Component used to render the content of cells for a particular column. |
getColumnIcon | (column: Column) => Component | Gets the icon for a particular column. |
getColumnSize | (column: Column) => number | Get column width. |
setColumnSize | (column: Column, width: number) => void | Set column width. |
resetColumnSize | (column: Column) => void | Reset column width. |
getColumnName | (column: Column) => string | Get column name. |
setColumnName | (column: Column, name: string) => void | Set column name. |
onInsertColumns | (index: number, count: number) => void | Insert columns. |
onInsertRows | (index: number, count: number) => void | Insert rows. |
onViewportChanged | (start: number, end: number) => void | Called whenever the range of rows visible in the viewport changes. |
onCellContextMenu | (ev: MouseEvent, row: number, column: Column) => void | Fired when a cell context menu is opened. |
onCopy | (min: CellIndex, max: CellIndex) => void | Called when cells are copied. |
onPaste | (min: CellIndex, max: CellIndex) => void | Called when cells are pasted. |
onClear | (min: CellIndex, max: CellIndex) => void | Called when cells are cleared (delete key). |
initialScrollPosition | { left: number; top: number } | Initial scroll position to restore. |
onScrollPositionChange | (scrollLeft: number, scrollTop: number) => void | Called when the table scroll position changes. |
You can customize the appearance using CSS variables:
.my-table {
/* General */--solid-tabular-font:0.875rem'Segoe UI','Helvetica Neue', Arial, sans-serif;
--solid-tabular-bg-color:oklch(92.8%0.006264.531);
--solid-tabular-text-color: black;
/* Cells */--solid-tabular-cell-color: white;
--solid-tabular-cell-hover-color:oklch(96.7%0.003264.542);
--solid-tabular-border-color:oklch(87.2%0.01258.338);
--solid-tabular-placeholder-color:oklch(70.7%0.022261.325);
/* Headers */--solid-tabular-header-color:var(--solid-tabular-cell-color);
--solid-tabular-rownum-color: inherit;
--solid-tabular-font-size-row-num:1em;
/* Selection & Accents */--solid-tabular-accent-color:oklch(45.7%0.24277.023);
--solid-tabular-shade-color:rgba(0,0,0,0.12);
}