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'
function App() {
const tableProps = createTableState([
{ A: 1, B: 2, C: 3 },
{ A: 4, B: 5, C: 6 },
{ A: 7, B: 8, C: 9 },
])
return (
<div style={{ width: '100%', padding: '20px' }}>
<Table {...tableProps} columnsResizeable />
</div>
)
}
export default AppThis 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'
function App() {
const [activeRange, setActiveRange] = createSignal<ActiveRange>()
const columns = ['A', 'B', 'C']
const [data, setData] = createSignal([
['A1', 'B1', 'C1'],
['A2', 'B2', 'C2'],
['A3', 'B3', 'C3'],
])
return (
<div style={{ width: '100%', padding: '20px' }}>
<Table
columns={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.006 264.531);
--solid-tabular-text-color: black;
/* Cells */
--solid-tabular-cell-color: white;
--solid-tabular-cell-hover-color: oklch(96.7% 0.003 264.542);
--solid-tabular-border-color: oklch(87.2% 0.01 258.338);
--solid-tabular-placeholder-color: oklch(70.7% 0.022 261.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.24 277.023);
--solid-tabular-shade-color: rgba(0, 0, 0, 0.12);
}