Documentation
Themes
Built-in themes and flags for table appearance.
Apply a theme
Pass theme with light, dark, neutral, modern-light, modern-dark, or modern-black. Use custom with Custom Theme for your own tokens.
React TSX
Copy
<SimpleTabletheme="modern-black"columns={columns}rows={rows}/>
Angular
Copy
<simple-tabletheme="modern-black"[columns]="columns"[rows]="rows"></simple-table>
Vue SFC
Copy
<SimpleTabletheme="modern-black":columns="columns":rows="rows"/>
Svelte
Copy
<SimpleTabletheme="modern-black"{columns}{rows}/>
Solid TSX
Copy
<SimpleTabletheme="modern-black"columns={columns}rows={rows()}/>
TypeScript
Copy
new SimpleTableVanilla(container, {theme: "modern-black",columns,rows,});
Styling flags
Toggle row hover, zebra striping, and borders. hoverRowBackground and oddEvenRowBackground default to on; columnBorders and oddColumnBackground default to off.
React TSX
Copy
<SimpleTabletheme="light"hoverRowBackgroundoddEvenRowBackgroundcolumnBordersoddColumnBackgroundcolumns={columns}rows={rows}/>
Angular
Copy
<simple-tabletheme="light"[hoverRowBackground]="true"[oddEvenRowBackground]="true"[columnBorders]="true"[oddColumnBackground]="true"[columns]="columns"[rows]="rows"></simple-table>
Vue SFC
Copy
<SimpleTabletheme="light":hover-row-background="true":odd-even-row-background="true":column-borders="true":odd-column-background="true":columns="columns":rows="rows"/>
Svelte
Copy
<SimpleTabletheme="light"hoverRowBackground={true}oddEvenRowBackground={true}columnBorders={true}oddColumnBackground={true}{columns}{rows}/>
Solid TSX
Copy
<SimpleTabletheme="light"hoverRowBackgroundoddEvenRowBackgroundcolumnBordersoddColumnBackgroundcolumns={columns}rows={rows()}/>
TypeScript
Copy
new SimpleTableVanilla(container, {theme: "light",hoverRowBackground: true,oddEvenRowBackground: true,columnBorders: true,oddColumnBackground: true,columns,rows,});
Column cell classes
Set cellClass on a ColumnDef to add a CSS class to every body cell in that column. Space-separated names work. Style with selectors like .amount-col.
React TSX
Copy
{accessor: "amount",label: "Amount",width: 120,type: "number",cellClass: "amount-col",}/* CSS */.amount-col { font-variant-numeric: tabular-nums; }
Angular
Copy
{accessor: "amount",label: "Amount",width: 120,type: "number",cellClass: "amount-col",}/* CSS */.amount-col { font-variant-numeric: tabular-nums; }
Vue SFC
Copy
{accessor: "amount",label: "Amount",width: 120,type: "number",cellClass: "amount-col",}/* CSS */.amount-col { font-variant-numeric: tabular-nums; }
Svelte
Copy
{accessor: "amount",label: "Amount",width: 120,type: "number",cellClass: "amount-col",}/* CSS */.amount-col { font-variant-numeric: tabular-nums; }
Solid TSX
Copy
{accessor: "amount",label: "Amount",width: 120,type: "number",cellClass: "amount-col",}/* CSS */.amount-col { font-variant-numeric: tabular-nums; }
TypeScript
Copy
{accessor: "amount",label: "Amount",width: 120,type: "number",cellClass: "amount-col",}/* CSS */.amount-col { font-variant-numeric: tabular-nums; }
Conditional row classes
Use getRowClass to highlight rows by data (e.g. a search jump target). Classes apply to each body .st-cell — style with selectors like .jump-row.
React TSX
Copy
const [jumpId, setJumpId] = useState(null);<SimpleTablecolumns={columns}rows={rows}getRowClass={({ row }) =>jumpId && row.id === jumpId ? "jump-row" : undefined}/>/* CSS */.jump-row { background-color: #fef3c7; }
Angular
Copy
<simple-table[columns]="columns"[rows]="rows"[getRowClass]="getRowClass"></simple-table>/* getRowClass = ({ row }) => jumpId && row.id === jumpId ? 'jump-row' : undefined *//* CSS */.jump-row { background-color: #fef3c7; }
Vue SFC
Copy
<SimpleTable:columns="columns":rows="rows":get-row-class="({ row }) =>jumpId && row.id === jumpId ? 'jump-row' : undefined"/>/* CSS */.jump-row { background-color: #fef3c7; }
Svelte
Copy
<SimpleTable{columns}{rows}getRowClass={({ row }) =>jumpId && row.id === jumpId ? "jump-row" : undefined}/>/* CSS */.jump-row { background-color: #fef3c7; }
Solid TSX
Copy
<SimpleTablecolumns={columns}rows={rows()}getRowClass={({ row }) =>jumpId() && row.id === jumpId() ? "jump-row" : undefined}/>/* CSS */.jump-row { background-color: #fef3c7; }
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows,getRowClass: ({ row }) =>jumpId && row.id === jumpId ? "jump-row" : undefined,});/* CSS */.jump-row { background-color: #fef3c7; }
Example
Switch themes below. Code or StackBlitz has the full example.
Light
React TSX
Copy
1import { useState } from "react";2import {SimpleTable} from "@simple-table/react";import type { Theme } from "@simple-table/react";3import { themesConfig, AVAILABLE_THEMES } from "./themes.demo-data";4import "@simple-table/react/styles.css";56const ThemesDemo = ({7 height = "400px",8 theme9}: {10 height?: string | number;11 theme?: Theme;12}) => {13 const [selectedTheme, setSelectedTheme] = useState<Theme>(theme ?? "light");1415 return (16 <div>17 <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 12 }}>18 {AVAILABLE_THEMES.map((t) => (19 <button20 key={t.value}21 onClick={() => setSelectedTheme(t.value)}22 style={{23 padding: "6px 14px",24 borderRadius: 6,25 border: selectedTheme === t.value ? "2px solid #3b82f6" : "1px solid #d1d5db",26 background: selectedTheme === t.value ? "#eff6ff" : "#fff",27 color: selectedTheme === t.value ? "#1d4ed8" : "#374151",28 fontWeight: selectedTheme === t.value ? 600 : 400,29 cursor: "pointer",30 fontSize: 1331 }}32 >33 {t.label}34 </button>35 ))}36 </div>37 <SimpleTable38 columns={themesConfig.headers}39 rows={themesConfig.rows}40 height={height}41 theme={selectedTheme}42 getRowId={({ row }) => row.id}43 />44 </div>45 );46};4748export default ThemesDemo;
Angularthemes-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import { NgFor } from "@angular/common";3import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, GetRowIdParams, Theme } from "@simple-table/angular";4import { themesConfig, AVAILABLE_THEMES } from "./themes.demo-data";5import "@simple-table/angular/styles.css";6import type { ThemeDemoStaff } from "./themes.demo-data";78@Component({9 selector: "themes-demo",10 standalone: true,11 imports: [SimpleTableComponent, NgFor],12 template: `13 <div>14 <div style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px">15 <button16 *ngFor="let t of themes"17 (click)="selectedTheme = t.value"18 [style.padding]="'6px 14px'"19 [style.borderRadius]="'6px'"20 [style.border]="selectedTheme === t.value ? '2px solid #3b82f6' : '1px solid #d1d5db'"21 [style.background]="selectedTheme === t.value ? '#eff6ff' : '#fff'"22 [style.color]="selectedTheme === t.value ? '#1d4ed8' : '#374151'"23 [style.fontWeight]="selectedTheme === t.value ? 600 : 400"24 [style.cursor]="'pointer'"25 [style.fontSize]="'13px'"26 >27 {{ t.label }}28 </button>29 </div>30 <simple-table31 [getRowId]="getRowId"32 [rows]="rows"33 [columns]="headers"34 [height]="height"35 [theme]="selectedTheme"36 ></simple-table>37 </div>38 `,39})40export class ThemesDemoComponent {41 @Input() height: string | number = "400px";42 @Input() theme?: Theme;4344 readonly rows: ThemeDemoStaff[] = themesConfig.rows;45 readonly headers: AngularColumnDef<ThemeDemoStaff>[] = themesConfig.headers;46 readonly themes = AVAILABLE_THEMES;47 selectedTheme: Theme = "light";4849 ngOnInit() {50 if (this.theme) this.selectedTheme = this.theme;51 }5253 getRowId = ({ row }: GetRowIdParams<ThemeDemoStaff>) => row.id;54}555657// themes.demo-data.ts58// Self-contained demo table setup for this example.59import type { AngularColumnDef, Theme } from "@simple-table/angular";606162export const AVAILABLE_THEMES: { value: Theme; label: string }[] = [63 { value: "light", label: "Light" },64 { value: "dark", label: "Dark" },65 { value: "modern-light", label: "Modern Light" },66 { value: "modern-dark", label: "Modern Dark" },67 { value: "modern-black", label: "Modern Black" },68 { value: "neutral", label: "Neutral" },69];7071export interface ThemeDemoStaff {72 id: number;73 name: string;74 email: string;75 department: string;76 status: string;77}7879export const themesHeaders: AngularColumnDef<ThemeDemoStaff>[] = [80 { accessor: "id", label: "ID", width: 80, type: "number" },81 { accessor: "name", label: "Name", minWidth: 100, width: "1fr", type: "string" },82 { accessor: "email", label: "Email", width: 220, type: "string" },83 { accessor: "department", label: "Department", width: 150, type: "string" },84 { accessor: "status", label: "Status", width: 120, type: "string" },85];8687export const themesData: ThemeDemoStaff[] = [88 { id: 1, name: "Dr. Sarah Mitchell", email: "s.mitchell@cityhospital.org", department: "Cardiology", status: "Active" },89 { id: 2, name: "Nurse Emily Chen", email: "e.chen@cityhospital.org", department: "Emergency", status: "Active" },90 { id: 3, name: "Dr. Marcus Williams", email: "m.williams@cityhospital.org", department: "Neurology", status: "Active" },91 { id: 4, name: "Therapist Ana Rodriguez", email: "a.rodriguez@cityhospital.org", department: "Physical Therapy", status: "Active" },92 { id: 5, name: "Dr. Yuki Tanaka", email: "y.tanaka@cityhospital.org", department: "Pediatrics", status: "On Call" },93 { id: 6, name: "Technician Omar Hassan", email: "o.hassan@cityhospital.org", department: "Radiology", status: "Active" },94 { id: 7, name: "Dr. Priya Patel", email: "p.patel@cityhospital.org", department: "Oncology", status: "Active" },95 { id: 8, name: "Coordinator Lisa Kim", email: "l.kim@cityhospital.org", department: "Patient Care", status: "Active" },96 { id: 9, name: "Dr. Giovanni Rossi", email: "g.rossi@cityhospital.org", department: "Surgery", status: "Active" },97 { id: 10, name: "Pharmacist David Lee", email: "d.lee@cityhospital.org", department: "Pharmacy", status: "Active" },98 { id: 11, name: "Nurse Zara Singh", email: "z.singh@cityhospital.org", department: "ICU", status: "Active" },99 { id: 12, name: "Dr. Felix Martinez", email: "f.martinez@cityhospital.org", department: "Orthopedics", status: "Active" },100];101102export const themesConfig = {103 headers: themesHeaders,104 rows: themesData,105};106
Vue SFC
Copy
1<template>2 <div>3 <div style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px">4 <button5 v-for="t in AVAILABLE_THEMES"6 :key="t.value"7 @click="selectedTheme = t.value"8 :style="{9 padding: '6px 14px',10 borderRadius: '6px',11 border: selectedTheme === t.value ? '2px solid #3b82f6' : '1px solid #d1d5db',12 background: selectedTheme === t.value ? '#eff6ff' : '#fff',13 color: selectedTheme === t.value ? '#1d4ed8' : '#374151',14 fontWeight: selectedTheme === t.value ? 600 : 400,15 cursor: 'pointer',16 fontSize: '13px',17 }"18 >19 {{ t.label }}20 </button>21 </div>22 <SimpleTable23 :columns="themesConfig.headers"24 :rows="themesConfig.rows"25 :get-row-id="getRowId"26 :height="height"27 :theme="selectedTheme"28 />29 </div>30</template>3132<script setup lang="ts">33import { ref } from "vue";34import { SimpleTable } from "@simple-table/vue";35import type { Theme, GetRowIdParams } from "@simple-table/vue";36import { themesConfig, AVAILABLE_THEMES } from "./themes.demo-data";37import type { ThemeDemoStaff } from "./themes.demo-data";38import "@simple-table/vue/styles.css";3940const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {41 height: "400px",42});4344const getRowId = ({ row }: GetRowIdParams<ThemeDemoStaff>) => row.id;4546const selectedTheme = ref<Theme>(props.theme ?? "light");47</script>
Svelte
Copy
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, GetRowIdParams } from "@simple-table/svelte";4 import { themesConfig, AVAILABLE_THEMES } from "./themes.demo-data";5 import type { ThemeDemoStaff } from "./themes.demo-data";6 import "@simple-table/svelte/styles.css";78 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();9 let userThemePick = $state<Theme | undefined>(undefined);10 const selectedTheme: Theme = $derived(userThemePick ?? theme ?? "light");1112 const getRowId = ({ row }: GetRowIdParams<ThemeDemoStaff>) => row.id;13</script>1415<div>16 <div style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px">17 {#each AVAILABLE_THEMES as t}18 <button19 onclick={() => (userThemePick = t.value)}20 style="padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; border: {selectedTheme === t.value ? '2px solid #3b82f6' : '1px solid #d1d5db'}; background: {selectedTheme === t.value ? '#eff6ff' : '#fff'}; color: {selectedTheme === t.value ? '#1d4ed8' : '#374151'}; font-weight: {selectedTheme === t.value ? 600 : 400}"21 >22 {t.label}23 </button>24 {/each}25 </div>26 <SimpleTable27 columns={themesConfig.headers}28 rows={themesConfig.rows}29 getRowId={getRowId}30 {height}31 theme={selectedTheme}32 />33</div>
Solid TSX
Copy
1import { createSignal, For } from "solid-js";2import {SimpleTable} from "@simple-table/solid";import type { Theme } from "@simple-table/solid";3import { themesConfig, AVAILABLE_THEMES } from "./themes.demo-data";4import "@simple-table/solid/styles.css";56export default function ThemesDemo(props: { height?: string | number; theme?: Theme }) {7 const [selectedTheme, setSelectedTheme] = createSignal<Theme>(props.theme ?? "light");89 return (10 <div>11 <div style={{ display: "flex", "flex-wrap": "wrap", gap: "8px", "margin-bottom": "12px" }}>12 <For each={AVAILABLE_THEMES}>13 {(t) => (14 <button15 onClick={() => setSelectedTheme(t.value)}16 style={{17 padding: "6px 14px",18 "border-radius": "6px",19 border: selectedTheme() === t.value ? "2px solid #3b82f6" : "1px solid #d1d5db",20 background: selectedTheme() === t.value ? "#eff6ff" : "#fff",21 color: selectedTheme() === t.value ? "#1d4ed8" : "#374151",22 "font-weight": selectedTheme() === t.value ? 600 : 400,23 cursor: "pointer",24 "font-size": "13px",25 }}26 >27 {t.label}28 </button>29 )}30 </For>31 </div>32 <SimpleTable33 columns={themesConfig.headers}34 getRowId={({ row }) => row.id}35 rows={themesConfig.rows}36 height={props.height ?? "400px"}37 theme={selectedTheme()}38 />39 </div>40 );41}
TypeScriptThemesDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { ThemeDemoStaff } from "./themes.demo-data";3import type { Theme, GetRowIdParams } from "simple-table-core";4import { themesConfig, AVAILABLE_THEMES } from "./themes.demo-data";5import "simple-table-core/styles.css";678const getRowId = ({ row }: GetRowIdParams<ThemeDemoStaff>) => row.id;9export function renderThemesDemo(10 container: HTMLElement,11 options?: { height?: string | number; theme?: Theme }12): SimpleTableVanilla<ThemeDemoStaff> {13 let selectedTheme: Theme = options?.theme ?? "light";1415 const wrapper = document.createElement("div");1617 const buttonRow = document.createElement("div");18 buttonRow.style.cssText = "display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px";1920 const tableContainer = document.createElement("div");21 wrapper.appendChild(buttonRow);22 wrapper.appendChild(tableContainer);23 container.appendChild(wrapper);2425 const table = new SimpleTableVanilla(tableContainer, {26 getRowId,27 columns: themesConfig.headers,28 rows: themesConfig.rows,29 height: options?.height ?? "400px",30 theme: selectedTheme,31 });3233 function renderButtons() {34 buttonRow.innerHTML = "";35 for (const t of AVAILABLE_THEMES) {36 const btn = document.createElement("button");37 btn.textContent = t.label;38 const active = selectedTheme === t.value;39 btn.style.cssText = `padding:6px 14px;border-radius:6px;cursor:pointer;font-size:13px;border:${active ? "2px solid #3b82f6" : "1px solid #d1d5db"};background:${active ? "#eff6ff" : "#fff"};color:${active ? "#1d4ed8" : "#374151"};font-weight:${active ? 600 : 400}`;40 btn.addEventListener("click", () => {41 selectedTheme = t.value;42 table.update({ theme: selectedTheme });43 renderButtons();44 });45 buttonRow.appendChild(btn);46 }47 }4849 renderButtons();50 return table;51}525354// themes.demo-data.ts55// Self-contained demo table setup for this example.56import type { ColumnDef, Theme } from "simple-table-core";575859export const AVAILABLE_THEMES: { value: Theme; label: string }[] = [60 { value: "light", label: "Light" },61 { value: "dark", label: "Dark" },62 { value: "modern-light", label: "Modern Light" },63 { value: "modern-dark", label: "Modern Dark" },64 { value: "modern-black", label: "Modern Black" },65 { value: "neutral", label: "Neutral" },66];6768export interface ThemeDemoStaff {69 id: number;70 name: string;71 email: string;72 department: string;73 status: string;74}7576export const themesHeaders: ColumnDef<ThemeDemoStaff>[] = [77 { accessor: "id", label: "ID", width: 80, type: "number" },78 { accessor: "name", label: "Name", minWidth: 100, width: "1fr", type: "string" },79 { accessor: "email", label: "Email", width: 220, type: "string" },80 { accessor: "department", label: "Department", width: 150, type: "string" },81 { accessor: "status", label: "Status", width: 120, type: "string" },82];8384export const themesData: ThemeDemoStaff[] = [85 { id: 1, name: "Dr. Sarah Mitchell", email: "s.mitchell@cityhospital.org", department: "Cardiology", status: "Active" },86 { id: 2, name: "Nurse Emily Chen", email: "e.chen@cityhospital.org", department: "Emergency", status: "Active" },87 { id: 3, name: "Dr. Marcus Williams", email: "m.williams@cityhospital.org", department: "Neurology", status: "Active" },88 { id: 4, name: "Therapist Ana Rodriguez", email: "a.rodriguez@cityhospital.org", department: "Physical Therapy", status: "Active" },89 { id: 5, name: "Dr. Yuki Tanaka", email: "y.tanaka@cityhospital.org", department: "Pediatrics", status: "On Call" },90 { id: 6, name: "Technician Omar Hassan", email: "o.hassan@cityhospital.org", department: "Radiology", status: "Active" },91 { id: 7, name: "Dr. Priya Patel", email: "p.patel@cityhospital.org", department: "Oncology", status: "Active" },92 { id: 8, name: "Coordinator Lisa Kim", email: "l.kim@cityhospital.org", department: "Patient Care", status: "Active" },93 { id: 9, name: "Dr. Giovanni Rossi", email: "g.rossi@cityhospital.org", department: "Surgery", status: "Active" },94 { id: 10, name: "Pharmacist David Lee", email: "d.lee@cityhospital.org", department: "Pharmacy", status: "Active" },95 { id: 11, name: "Nurse Zara Singh", email: "z.singh@cityhospital.org", department: "ICU", status: "Active" },96 { id: 12, name: "Dr. Felix Martinez", email: "f.martinez@cityhospital.org", department: "Orthopedics", status: "Active" },97];9899export const themesConfig = {100 headers: themesHeaders,101 rows: themesData,102};103
Props
Theme Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
theme | Optional | Built-in theme for the table. Options: light dark neutral modern-light modern-dark modern-black custom | |
hoverRowBackgroundboolean | Optional | Highlight the row under the pointer. Defaults to true. | |
oddEvenRowBackgroundboolean | Optional | Alternate row background colors. Defaults to true. | |
| Optional | Return CSS class name(s) for a row. Applied to each body cell — style with `.st-cell.yourClass`. | ||
oddColumnBackgroundboolean | Optional | Alternate column background colors. Defaults to false. | |
columnBordersboolean | Optional | Show vertical borders between columns. Defaults to false. |