Documentation
Column Sorting
Let users sort columns ascending or descending — or drive sort from your server.
Enable sorting
Set sortable: true on a column. Users click the header to cycle sort. Nested accessors like albums[0].title work too.
{accessor: "name",label: "Full Name",width: "1fr",sortable: true,}
{accessor: "name",label: "Full Name",width: "1fr",sortable: true,}
{accessor: "name",label: "Full Name",width: "1fr",sortable: true,}
{accessor: "name",label: "Full Name",width: "1fr",sortable: true,}
{accessor: "name",label: "Full Name",width: "1fr",sortable: true,}
{accessor: "name",label: "Full Name",width: "1fr",sortable: true,}
Custom sort cycle
Use sortingOrder to change the click cycle. Default is ["asc", "desc", null]. Prefer desc-first for numbers and dates.
{accessor: "revenue",label: "Revenue",width: 120,type: "number",sortable: true,sortingOrder: ["desc", "asc", null],}
{accessor: "revenue",label: "Revenue",width: 120,type: "number",sortable: true,sortingOrder: ["desc", "asc", null],}
{accessor: "revenue",label: "Revenue",width: 120,type: "number",sortable: true,sortingOrder: ["desc", "asc", null],}
{accessor: "revenue",label: "Revenue",width: 120,type: "number",sortable: true,sortingOrder: ["desc", "asc", null],}
{accessor: "revenue",label: "Revenue",width: 120,type: "number",sortable: true,sortingOrder: ["desc", "asc", null],}
{accessor: "revenue",label: "Revenue",width: 120,type: "number",sortable: true,sortingOrder: ["desc", "asc", null],}
Initial sort
Set initialSortColumn and initialSortDirection so the table loads already sorted.
<SimpleTable columns={columns} rows={rows} height="400px" initialSortColumn="revenue" initialSortDirection="desc" />
<SimpleTable:columns="columns":rows="rows"height="400px"initial-sort-column="revenue"initial-sort-direction="desc"/>
<simple-table[columns]="columns"[rows]="rows"height="400px"initialSortColumn="revenue"initialSortDirection="desc"></simple-table>
<SimpleTable {columns} {rows} height="400px" initialSortColumn="revenue" initialSortDirection="desc" />
<SimpleTable columns={columns} rows={rows} height="400px" initialSortColumn="revenue" initialSortDirection="desc" />
new SimpleTableVanilla(container, {columns,rows,height: "400px",initialSortColumn: "revenue",initialSortDirection: "desc",});
Custom comparator
Use comparator when you need multi-field or domain-specific sort logic with both rows.
{accessor: "priority",label: "Priority",sortable: true,comparator: ({ rowA, rowB, direction }) => {if (rowA.priority !== rowB.priority) {return direction === "asc"? Number(rowA.priority) - Number(rowB.priority): Number(rowB.priority) - Number(rowA.priority);}return Number(rowB.score) - Number(rowA.score);},}
{accessor: "priority",label: "Priority",sortable: true,comparator: ({ rowA, rowB, direction }) => {if (rowA.priority !== rowB.priority) {return direction === "asc"? Number(rowA.priority) - Number(rowB.priority): Number(rowB.priority) - Number(rowA.priority);}return Number(rowB.score) - Number(rowA.score);},}
{accessor: "priority",label: "Priority",sortable: true,comparator: ({ rowA, rowB, direction }) => {if (rowA.priority !== rowB.priority) {return direction === "asc"? Number(rowA.priority) - Number(rowB.priority): Number(rowB.priority) - Number(rowA.priority);}return Number(rowB.score) - Number(rowA.score);},}
{accessor: "priority",label: "Priority",sortable: true,comparator: ({ rowA, rowB, direction }) => {if (rowA.priority !== rowB.priority) {return direction === "asc"? Number(rowA.priority) - Number(rowB.priority): Number(rowB.priority) - Number(rowA.priority);}return Number(rowB.score) - Number(rowA.score);},}
{accessor: "priority",label: "Priority",sortable: true,comparator: ({ rowA, rowB, direction }) => {if (rowA.priority !== rowB.priority) {return direction === "asc"? Number(rowA.priority) - Number(rowB.priority): Number(rowB.priority) - Number(rowA.priority);}return Number(rowB.score) - Number(rowA.score);},}
{accessor: "priority",label: "Priority",sortable: true,comparator: ({ rowA, rowB, direction }) => {if (rowA.priority !== rowB.priority) {return direction === "asc"? Number(rowA.priority) - Number(rowB.priority): Number(rowB.priority) - Number(rowA.priority);}return Number(rowB.score) - Number(rowA.score);},}
Sort with valueGetter
Use valueGetter when the sort value is nested or computed (instead of the raw accessor field).
{accessor: "seniority",label: "Seniority",sortable: true,valueGetter: ({ row }) => row.metadata?.seniorityLevel ?? 0,}
{accessor: "seniority",label: "Seniority",sortable: true,valueGetter: ({ row }) => row.metadata?.seniorityLevel ?? 0,}
{accessor: "seniority",label: "Seniority",sortable: true,valueGetter: ({ row }) => row.metadata?.seniorityLevel ?? 0,}
{accessor: "seniority",label: "Seniority",sortable: true,valueGetter: ({ row }) => row.metadata?.seniorityLevel ?? 0,}
{accessor: "seniority",label: "Seniority",sortable: true,valueGetter: ({ row }) => row.metadata?.seniorityLevel ?? 0,}
{accessor: "seniority",label: "Seniority",sortable: true,valueGetter: ({ row }) => row.metadata?.seniorityLevel ?? 0,}
External / server sorting
Set externalSortHandling and handle onSortChange — the table keeps sort UI while you supply pre-sorted rows.
<SimpleTableexternalSortHandlingcolumns={columns}rows={sortedRows}onSortChange={(sort) => {// fetch or sort sortedRows from sort.key / sort.direction}}/>
<SimpleTable:external-sort-handling="true":columns="columns":rows="sortedRows":on-sort-change="handleSortChange"/>
<simple-table[externalSortHandling]="true"[columns]="columns"[rows]="sortedRows"[onSortChange]="handleSortChange"></simple-table>
<SimpleTableexternalSortHandling={true}{columns}rows={sortedRows}onSortChange={handleSortChange}/>
<SimpleTableexternalSortHandlingcolumns={columns}rows={sortedRows()}onSortChange={(sort) => {// fetch or sort sortedRows from sort.key / sort.direction}}/>
new SimpleTableVanilla(container, {columns,rows: sortedRows,externalSortHandling: true,onSortChange: (sort) => {// fetch or sort sortedRows from sort.key / sort.direction},});
Example
1import {SimpleTable} from "@simple-table/react";import type { Theme } from "@simple-table/react";2import { columnSortingConfig } from "./column-sorting.demo-data";3import "@simple-table/react/styles.css";45const ColumnSortingDemo = ({6 height = "400px",7 theme,8}: {9 height?: string | number;10 theme?: Theme;11}) => {12 return (13 <SimpleTable14 columns={columnSortingConfig.headers}15 rows={columnSortingConfig.rows}16 height={height}17 theme={theme}18 initialSortColumn={columnSortingConfig.tableProps.initialSortColumn}19 initialSortDirection={columnSortingConfig.tableProps.initialSortDirection}20 />21 );22};2324export default ColumnSortingDemo;
1<template>2 <SimpleTable3 :columns="columnSortingConfig.headers"4 :rows="columnSortingConfig.rows"5 :height="height"6 :theme="theme"7 :initial-sort-column="columnSortingConfig.tableProps.initialSortColumn"8 :initial-sort-direction="columnSortingConfig.tableProps.initialSortDirection"9 />10</template>1112<script setup lang="ts">13import {SimpleTable} from "@simple-table/vue";import type { Theme } from "@simple-table/vue";14import { columnSortingConfig } from "./column-sorting.demo-data";15import "@simple-table/vue/styles.css";1617withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {18 height: "400px",19});20</script>
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, Row, Theme } from "@simple-table/angular";3import { columnSortingConfig } from "./column-sorting.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "column-sorting-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <simple-table12 [rows]="rows"13 [columns]="headers"14 [height]="height"15 [theme]="theme"16 [initialSortColumn]="initialSortColumn"17 [initialSortDirection]="initialSortDirection"18 ></simple-table>19 `,20})21export class ColumnSortingDemoComponent {22 @Input() height: string | number = "400px";23 @Input() theme?: Theme;2425 readonly rows: Row[] = columnSortingConfig.rows;26 readonly headers: AngularColumnDef[] = columnSortingConfig.headers;27 readonly initialSortColumn = columnSortingConfig.tableProps.initialSortColumn;28 readonly initialSortDirection = columnSortingConfig.tableProps.initialSortDirection;29}303132// column-sorting.demo-data.ts33// Self-contained demo table setup for this example.34import type { Row } from "@simple-table/angular";35import type { AngularColumnDef } from "@simple-table/angular";363738export const COLUMN_SORTING_DATA: Row[] = [39 {40 id: 1,41 name: "Dr. Elena Vasquez",42 age: 42,43 role: "Computer Science Professor",44 department: "Computer Science",45 startDate: "2015-08-15",46 },47 {48 id: 2,49 name: "Prof. Michael Chang",50 age: 38,51 role: "Mathematics Professor",52 department: "Mathematics",53 startDate: "2017-01-10",54 },55 {56 id: 3,57 name: "Dr. Sarah Mitchell",58 age: 45,59 role: "Dean of Engineering",60 department: "Administration",61 startDate: "2012-09-01",62 },63 {64 id: 4,65 name: "Alex Parker",66 age: 22,67 role: "Graduate Student",68 department: "Computer Science",69 startDate: "2023-09-01",70 },71 {72 id: 5,73 name: "Dr. James Wilson",74 age: 51,75 role: "Physics Professor",76 department: "Physics",77 startDate: "2008-03-15",78 },79 {80 id: 6,81 name: "Maria Santos",82 age: 24,83 role: "Research Assistant",84 department: "Biology",85 startDate: "2022-06-01",86 },87 {88 id: 7,89 name: "Prof. David Kumar",90 age: 39,91 role: "Biology Professor",92 department: "Biology",93 startDate: "2018-02-14",94 },95 {96 id: 8,97 name: "Rachel Green",98 age: 28,99 role: "Lab Coordinator",100 department: "Chemistry",101 startDate: "2020-11-05",102 },103 {104 id: 9,105 name: "Dr. Lisa Chen",106 age: 47,107 role: "Psychology Professor",108 department: "Psychology",109 startDate: "2014-08-20",110 },111 {112 id: 10,113 name: "Ben Taylor",114 age: 23,115 role: "Teaching Assistant",116 department: "Mathematics",117 startDate: "2023-01-15",118 },119 {120 id: 11,121 name: "Dr. Anna Rodriguez",122 age: 35,123 role: "Chemistry Professor",124 department: "Chemistry",125 startDate: "2019-07-01",126 },127 {128 id: 12,129 name: "Prof. Robert Kim",130 age: 44,131 role: "Department Head",132 department: "Engineering",133 startDate: "2011-04-12",134 },135];136137138export const columnSortingHeaders: AngularColumnDef[] = [139 { accessor: "id", label: "ID", width: 80, sortable: true, type: "number" },140 { accessor: "name", label: "Name", width: 180, sortable: true, type: "string" },141 { accessor: "age", label: "Age", width: 80, sortable: true, type: "number" },142 { accessor: "role", label: "Role", width: 200, sortable: true, type: "string" },143 {144 accessor: "department",145 label: "Department",146 width: 180,147 sortable: true,148 type: "string",149 valueFormatter: ({ value }) => {150 return (value as string).charAt(0).toUpperCase() + (value as string).slice(1);151 },152 },153 {154 accessor: "startDate",155 label: "Start Date",156 width: 140,157 sortable: true,158 type: "date",159 valueFormatter: ({ value }) => {160 if (typeof value === "string") {161 return new Date(value).toLocaleDateString("en-US", {162 year: "numeric",163 month: "short",164 day: "numeric",165 });166 }167 return String(value);168 },169 },170];171172export const columnSortingConfig = {173 headers: columnSortingHeaders,174 rows: COLUMN_SORTING_DATA,175 tableProps: {176 initialSortColumn: "age",177 initialSortDirection: "desc" as const,178 },179} as const;180
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme } from "@simple-table/svelte";3 import { columnSortingConfig } from "./column-sorting.demo-data";4 import "@simple-table/svelte/styles.css";56 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();7</script>89<SimpleTable10 columns={columnSortingConfig.headers}11 rows={columnSortingConfig.rows}12 {height}13 {theme}14 initialSortColumn={columnSortingConfig.tableProps.initialSortColumn}15 initialSortDirection={columnSortingConfig.tableProps.initialSortDirection}16/>
1import {SimpleTable} from "@simple-table/solid";import type { Theme } from "@simple-table/solid";2import { columnSortingConfig } from "./column-sorting.demo-data";3import "@simple-table/solid/styles.css";45export default function ColumnSortingDemo(props: {6 height?: string | number;7 theme?: Theme;8}) {9 return (10 <SimpleTable11 columns={columnSortingConfig.headers}12 rows={columnSortingConfig.rows}13 height={props.height ?? "400px"}14 theme={props.theme}15 initialSortColumn={columnSortingConfig.tableProps.initialSortColumn}16 initialSortDirection={columnSortingConfig.tableProps.initialSortDirection}17 />18 );19}
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme } from "simple-table-core";3import { columnSortingConfig } from "./column-sorting.demo-data";4import "simple-table-core/styles.css";56export function renderColumnSortingDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): SimpleTableVanilla {10 const table = new SimpleTableVanilla(container, {11 columns: columnSortingConfig.headers,12 rows: columnSortingConfig.rows,13 height: options?.height ?? "400px",14 theme: options?.theme,15 initialSortColumn: columnSortingConfig.tableProps.initialSortColumn,16 initialSortDirection: columnSortingConfig.tableProps.initialSortDirection,17 });18 return table;19}202122// column-sorting.demo-data.ts23// Self-contained demo table setup for this example.24import type { Row } from "simple-table-core";25import type { ColumnDef } from "simple-table-core";262728export const COLUMN_SORTING_DATA: Row[] = [29 {30 id: 1,31 name: "Dr. Elena Vasquez",32 age: 42,33 role: "Computer Science Professor",34 department: "Computer Science",35 startDate: "2015-08-15",36 },37 {38 id: 2,39 name: "Prof. Michael Chang",40 age: 38,41 role: "Mathematics Professor",42 department: "Mathematics",43 startDate: "2017-01-10",44 },45 {46 id: 3,47 name: "Dr. Sarah Mitchell",48 age: 45,49 role: "Dean of Engineering",50 department: "Administration",51 startDate: "2012-09-01",52 },53 {54 id: 4,55 name: "Alex Parker",56 age: 22,57 role: "Graduate Student",58 department: "Computer Science",59 startDate: "2023-09-01",60 },61 {62 id: 5,63 name: "Dr. James Wilson",64 age: 51,65 role: "Physics Professor",66 department: "Physics",67 startDate: "2008-03-15",68 },69 {70 id: 6,71 name: "Maria Santos",72 age: 24,73 role: "Research Assistant",74 department: "Biology",75 startDate: "2022-06-01",76 },77 {78 id: 7,79 name: "Prof. David Kumar",80 age: 39,81 role: "Biology Professor",82 department: "Biology",83 startDate: "2018-02-14",84 },85 {86 id: 8,87 name: "Rachel Green",88 age: 28,89 role: "Lab Coordinator",90 department: "Chemistry",91 startDate: "2020-11-05",92 },93 {94 id: 9,95 name: "Dr. Lisa Chen",96 age: 47,97 role: "Psychology Professor",98 department: "Psychology",99 startDate: "2014-08-20",100 },101 {102 id: 10,103 name: "Ben Taylor",104 age: 23,105 role: "Teaching Assistant",106 department: "Mathematics",107 startDate: "2023-01-15",108 },109 {110 id: 11,111 name: "Dr. Anna Rodriguez",112 age: 35,113 role: "Chemistry Professor",114 department: "Chemistry",115 startDate: "2019-07-01",116 },117 {118 id: 12,119 name: "Prof. Robert Kim",120 age: 44,121 role: "Department Head",122 department: "Engineering",123 startDate: "2011-04-12",124 },125];126127128export const columnSortingHeaders: ColumnDef[] = [129 { accessor: "id", label: "ID", width: 80, sortable: true, type: "number" },130 { accessor: "name", label: "Name", width: 180, sortable: true, type: "string" },131 { accessor: "age", label: "Age", width: 80, sortable: true, type: "number" },132 { accessor: "role", label: "Role", width: 200, sortable: true, type: "string" },133 {134 accessor: "department",135 label: "Department",136 width: 180,137 sortable: true,138 type: "string",139 valueFormatter: ({ value }) => {140 return (value as string).charAt(0).toUpperCase() + (value as string).slice(1);141 },142 },143 {144 accessor: "startDate",145 label: "Start Date",146 width: 140,147 sortable: true,148 type: "date",149 valueFormatter: ({ value }) => {150 if (typeof value === "string") {151 return new Date(value).toLocaleDateString("en-US", {152 year: "numeric",153 month: "short",154 day: "numeric",155 });156 }157 return String(value);158 },159 },160];161162export const columnSortingConfig = {163 headers: columnSortingHeaders,164 rows: COLUMN_SORTING_DATA,165 tableProps: {166 initialSortColumn: "age",167 initialSortDirection: "desc" as const,168 },169} as const;170
External sorting example
Sort is handled outside the table; header indicators still update.
External Sort Status: No sorting applied
1import { useState, useMemo } from "react";2import {SimpleTable} from "@simple-table/react";import type { Theme, SortColumn } from "@simple-table/react";3import { externalSortConfig } from "./external-sort.demo-data";4import "@simple-table/react/styles.css";56const ExternalSortDemo = ({7 height = "400px",8 theme,9}: {10 height?: string | number;11 theme?: Theme;12}) => {13 const [sortConfig, setSortConfig] = useState<SortColumn | null>(null);1415 const sortedData = useMemo(() => {16 if (!sortConfig) return externalSortConfig.rows;17 const sorted = [...externalSortConfig.rows].sort((a, b) => {18 const key = sortConfig.key.accessor;19 const aVal = a[key as keyof typeof a];20 const bVal = b[key as keyof typeof b];21 if (aVal === bVal) return 0;22 const cmp =23 sortConfig.key.type === "number"24 ? (aVal as number) - (bVal as number)25 : String(aVal).localeCompare(String(bVal));26 return sortConfig.direction === "asc" ? cmp : -cmp;27 });28 return sorted;29 }, [sortConfig]);3031 return (32 <SimpleTable33 columns={externalSortConfig.headers}34 rows={sortedData}35 onSortChange={setSortConfig}36 externalSortHandling37 columnResizing38 height={height}39 theme={theme}40 />41 );42};4344export default ExternalSortDemo;
1<template>2 <SimpleTable3 :columns="externalSortConfig.headers"4 :rows="sortedRows"5 :external-sort-handling="true"6 :column-resizing="externalSortConfig.tableProps.columnResizing"7 :height="height"8 :theme="theme"9 @sort-change="handleSortChange"10 />11</template>1213<script setup lang="ts">14import { ref, computed } from "vue";15import {SimpleTable} from "@simple-table/vue";import type { Theme, SortColumn } from "@simple-table/vue";16import { externalSortConfig } from "./external-sort.demo-data";17import "@simple-table/vue/styles.css";1819withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {20 height: "400px",21});2223const sortConfig = ref<SortColumn | null>(null);2425const sortedRows = computed(() => {26 const data = [...externalSortConfig.rows];27 const sort = sortConfig.value;28 if (!sort) return data;2930 const accessor = sort.key.accessor as string;31 const dir = sort.direction === "asc" ? 1 : -1;3233 return data.sort((a, b) => {34 const aVal = a[accessor];35 const bVal = b[accessor];36 if (aVal == null && bVal == null) return 0;37 if (aVal == null) return dir;38 if (bVal == null) return -dir;39 if (typeof aVal === "string" && typeof bVal === "string") return dir * aVal.localeCompare(bVal);40 return dir * (Number(aVal) - Number(bVal));41 });42});4344function handleSortChange(sort: SortColumn | null) {45 sortConfig.value = sort;46}47</script>
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent, asRows} from "@simple-table/angular";import type { AngularColumnDef, Row, SortColumn, Theme } from "@simple-table/angular";3import { externalSortConfig } from "./external-sort.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "external-sort-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <simple-table12 [rows]="sortedRows"13 [columns]="headers"14 [height]="height"15 [theme]="theme"16 [externalSortHandling]="true"17 [columnResizing]="true"18 [onSortChange]="handleSortChange"19 ></simple-table>20 `,21})22export class ExternalSortDemoComponent {23 @Input() height: string | number = "400px";24 @Input() theme?: Theme;2526 readonly headers: AngularColumnDef[] = externalSortConfig.headers;27 private sortState: SortColumn | null = null;2829 handleSortChange = (sort: SortColumn | null): void => {30 this.sortState = sort;31 };3233 get sortedRows(): Row[] {34 const rows = [...asRows(externalSortConfig.rows)];35 if (!this.sortState) return rows;36 const accessor = this.sortState.key.accessor as string;37 const type = this.sortState.key.type;38 const dir = this.sortState.direction;39 return rows.sort((a, b) => {40 const aVal = a[accessor];41 const bVal = b[accessor];42 if (aVal === bVal) return 0;43 const cmp = type === "number"44 ? (aVal as number) - (bVal as number)45 : String(aVal).localeCompare(String(bVal));46 return dir === "asc" ? cmp : -cmp;47 });48 }49}505152// external-sort.demo-data.ts53// Self-contained demo table setup for this example.54import type { AngularColumnDef } from "@simple-table/angular";555657export const externalSortData = [58 { id: 1, name: "Dr. Elena Vasquez", age: 42, email: "elena.vasquez@techcorp.com", salary: 145000, department: "AI Research" },59 { id: 2, name: "Kai Tanaka", age: 29, email: "k.tanaka@techcorp.com", salary: 95000, department: "UX Design" },60 { id: 3, name: "Amara Okafor", age: 35, email: "amara.okafor@techcorp.com", salary: 125000, department: "DevOps" },61 { id: 4, name: "Santiago Rodriguez", age: 27, email: "s.rodriguez@techcorp.com", salary: 82000, department: "Marketing" },62 { id: 5, name: "Priya Chakraborty", age: 33, email: "priya.c@techcorp.com", salary: 118000, department: "Engineering" },63 { id: 6, name: "Magnus Eriksson", age: 38, email: "magnus.erik@techcorp.com", salary: 110000, department: "Product" },64 { id: 7, name: "Zara Al-Rashid", age: 31, email: "zara.alrashid@techcorp.com", salary: 98000, department: "Sales" },65 { id: 8, name: "Luca Rossi", age: 26, email: "luca.rossi@techcorp.com", salary: 75000, department: "Marketing" },66 { id: 9, name: "Dr. Sarah Kim", age: 45, email: "sarah.kim@techcorp.com", salary: 165000, department: "AI Research" },67 { id: 10, name: "Olumide Adebayo", age: 30, email: "olumide.a@techcorp.com", salary: 105000, department: "Engineering" },68 { id: 11, name: "Isabella Chen", age: 24, email: "isabella.chen@techcorp.com", salary: 68000, department: "UX Design" },69 { id: 12, name: "Dmitri Volkov", age: 39, email: "dmitri.volkov@techcorp.com", salary: 135000, department: "DevOps" },70];7172export const externalSortHeaders: AngularColumnDef[] = [73 { accessor: "name", label: "Name", width: "1fr", minWidth: 120, sortable: true, type: "string" },74 { accessor: "age", label: "Age", width: 120, sortable: true, type: "number" },75 { accessor: "department", label: "Department", width: 150, sortable: true, type: "string" },76 { accessor: "email", label: "Email", width: 200, sortable: true, type: "string" },77 {78 accessor: "salary",79 label: "Salary",80 width: 120,81 sortable: true,82 type: "number",83 align: "right",84 valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,85 },86];8788export const externalSortConfig = {89 headers: externalSortHeaders,90 rows: externalSortData,91 tableProps: { externalSortHandling: true, columnResizing: true },92} as const;93
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme, SortColumn } from "@simple-table/svelte";3 import { externalSortConfig } from "./external-sort.demo-data";4 import "@simple-table/svelte/styles.css";56 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();78 let sortConfig = $state<SortColumn | null>(null);910 let sortedRows = $derived.by(() => {11 const data = [...externalSortConfig.rows];12 if (!sortConfig) return data;1314 const accessor = sortConfig.key.accessor as string;15 const dir = sortConfig.direction === "asc" ? 1 : -1;1617 return data.sort((a, b) => {18 const aVal = a[accessor];19 const bVal = b[accessor];20 if (aVal == null && bVal == null) return 0;21 if (aVal == null) return dir;22 if (bVal == null) return -dir;23 if (typeof aVal === "string" && typeof bVal === "string") return dir * aVal.localeCompare(bVal);24 return dir * (Number(aVal) - Number(bVal));25 });26 });2728 function handleSortChange(sort: SortColumn | null) {29 sortConfig = sort;30 }31</script>3233<SimpleTable34 columns={externalSortConfig.headers}35 rows={sortedRows}36 externalSortHandling={true}37 columnResizing={externalSortConfig.tableProps.columnResizing}38 onSortChange={handleSortChange}39 {height}40 {theme}41/>
1import {SimpleTable, asRows} from "@simple-table/solid";import type { Theme, SortColumn, Row } from "@simple-table/solid";2import { externalSortConfig } from "./external-sort.demo-data";3import { createSignal, createMemo } from "solid-js";4import "@simple-table/solid/styles.css";56export default function ExternalSortDemo(props: {7 height?: string | number;8 theme?: Theme;9}) {10 const [sortState, setSortState] = createSignal<SortColumn | null>(null);1112 const sortedRows = createMemo((): Row[] => {13 const sort = sortState();14 const rows = [...asRows(externalSortConfig.rows)];15 if (!sort) return rows;16 const accessor = sort.key.accessor as string;17 const type = sort.key.type;18 const dir = sort.direction;19 return rows.sort((a, b) => {20 const aVal = a[accessor];21 const bVal = b[accessor];22 if (aVal === bVal) return 0;23 const cmp =24 type === "number"25 ? (Number(aVal) || 0) - (Number(bVal) || 0)26 : String(aVal).localeCompare(String(bVal));27 return dir === "asc" ? cmp : -cmp;28 });29 });3031 return (32 <SimpleTable33 columns={externalSortConfig.headers}34 rows={sortedRows()}35 height={props.height ?? "400px"}36 theme={props.theme}37 externalSortHandling={true}38 columnResizing={true}39 onSortChange={(sort) => setSortState(sort)}40 />41 );42}
1import { SimpleTableVanilla, asRows } from "simple-table-core";2import type { Theme, SortColumn, Row } from "simple-table-core";3import { externalSortConfig } from "./external-sort.demo-data";4import "simple-table-core/styles.css";56export function renderExternalSortDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): SimpleTableVanilla {10 let currentSort: SortColumn | null = null;1112 function getSortedRows(): Row[] {13 const rows = [...asRows(externalSortConfig.rows)];14 if (!currentSort) return rows;15 const accessor = currentSort.key.accessor as string;16 const type = currentSort.key.type;17 const dir = currentSort.direction;18 return rows.sort((a, b) => {19 const aVal = a[accessor];20 const bVal = b[accessor];21 if (aVal === bVal) return 0;22 const cmp =23 type === "number"24 ? (Number(aVal) || 0) - (Number(bVal) || 0)25 : String(aVal).localeCompare(String(bVal));26 return dir === "asc" ? cmp : -cmp;27 });28 }2930 const table = new SimpleTableVanilla(container, {31 columns: externalSortConfig.headers,32 rows: asRows(externalSortConfig.rows),33 height: options?.height ?? "400px",34 theme: options?.theme,35 externalSortHandling: true,36 columnResizing: true,37 onSortChange: (sort) => {38 currentSort = sort;39 table.update({ rows: getSortedRows() });40 },41 });42 return table;43}444546// external-sort.demo-data.ts47// Self-contained demo table setup for this example.48import type { ColumnDef } from "simple-table-core";495051export const externalSortData = [52 { id: 1, name: "Dr. Elena Vasquez", age: 42, email: "elena.vasquez@techcorp.com", salary: 145000, department: "AI Research" },53 { id: 2, name: "Kai Tanaka", age: 29, email: "k.tanaka@techcorp.com", salary: 95000, department: "UX Design" },54 { id: 3, name: "Amara Okafor", age: 35, email: "amara.okafor@techcorp.com", salary: 125000, department: "DevOps" },55 { id: 4, name: "Santiago Rodriguez", age: 27, email: "s.rodriguez@techcorp.com", salary: 82000, department: "Marketing" },56 { id: 5, name: "Priya Chakraborty", age: 33, email: "priya.c@techcorp.com", salary: 118000, department: "Engineering" },57 { id: 6, name: "Magnus Eriksson", age: 38, email: "magnus.erik@techcorp.com", salary: 110000, department: "Product" },58 { id: 7, name: "Zara Al-Rashid", age: 31, email: "zara.alrashid@techcorp.com", salary: 98000, department: "Sales" },59 { id: 8, name: "Luca Rossi", age: 26, email: "luca.rossi@techcorp.com", salary: 75000, department: "Marketing" },60 { id: 9, name: "Dr. Sarah Kim", age: 45, email: "sarah.kim@techcorp.com", salary: 165000, department: "AI Research" },61 { id: 10, name: "Olumide Adebayo", age: 30, email: "olumide.a@techcorp.com", salary: 105000, department: "Engineering" },62 { id: 11, name: "Isabella Chen", age: 24, email: "isabella.chen@techcorp.com", salary: 68000, department: "UX Design" },63 { id: 12, name: "Dmitri Volkov", age: 39, email: "dmitri.volkov@techcorp.com", salary: 135000, department: "DevOps" },64];6566export const externalSortHeaders: ColumnDef[] = [67 { accessor: "name", label: "Name", width: "1fr", minWidth: 120, sortable: true, type: "string" },68 { accessor: "age", label: "Age", width: 120, sortable: true, type: "number" },69 { accessor: "department", label: "Department", width: 150, sortable: true, type: "string" },70 { accessor: "email", label: "Email", width: 200, sortable: true, type: "string" },71 {72 accessor: "salary",73 label: "Salary",74 width: 120,75 sortable: true,76 type: "number",77 align: "right",78 valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,79 },80];8182export const externalSortConfig = {83 headers: externalSortHeaders,84 rows: externalSortData,85 tableProps: { externalSortHandling: true, columnResizing: true },86} as const;87
Props
Column Sorting Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
ColumnDef.sortableboolean | Optional | Enables header click sorting for the column. | |
ColumnDef.sortingOrderArray<'asc' | 'desc' | null> | Optional | Sort state cycle on header click. Default: ['asc', 'desc', null]. Omit null to keep a sort always on. | |
ColumnDef.comparator | Optional | Custom compare using both rows and direction. | |
ColumnDef.valueGetter | Optional | Extract or compute the value used for sorting (and often display). | |
initialSortColumnstring | Optional | Accessor to sort by on first load. | |
initialSortDirection"asc" | "desc" | Optional | Direction for the initial sort. Defaults to 'asc'. | |
onSortChange | Optional | Fires when sort config changes (or null when cleared). | |
externalSortHandlingboolean | Optional | Disables internal sorting. Provide already-sorted rows (e.g. from your API). |