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" />
<simple-table[columns]="columns"[rows]="rows"height="400px"initialSortColumn="revenue"initialSortDirection="desc"></simple-table>
<SimpleTable:columns="columns":rows="rows"height="400px"initial-sort-column="revenue"initial-sort-direction="desc"/>
<SimpleTable {columns} {rows} height="400px" initialSortColumn="revenue" initialSortDirection="desc" />
<SimpleTable columns={columns} rows={rows} height="400px" initialSortColumn="revenue" initialSortDirection="desc" />
const table = new SimpleTableVanilla(container, {columns,rows,height: "400px",initialSortColumn: "revenue",initialSortDirection: "desc",});table.mount();
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}}/>
<simple-table[externalSortHandling]="true"[columns]="columns"[rows]="sortedRows"(sortChange)="handleSortChange($event)"></simple-table>
<SimpleTable:external-sort-handling="true":columns="columns":rows="sortedRows":on-sort-change="handleSortChange"/>
<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";2import type { Theme } from "@simple-table/react";3import { columnSortingConfig } from "./column-sorting.demo-data";4import "@simple-table/react/styles.css";56const ColumnSortingDemo = ({7 height = "400px",8 theme9}: {10 height?: string | number;11 theme?: Theme;12}) => {13 return (14 <SimpleTable15 columns={columnSortingConfig.headers}16 rows={columnSortingConfig.rows}17 height={height}18 theme={theme}19 getRowId={({ row }) => row.id}20 initialSortColumn="age"21 initialSortDirection="desc"22 />23 );24};2526export default ColumnSortingDemo;
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, GetRowIdParams, Theme } from "@simple-table/angular";3import { columnSortingConfig } from "./column-sorting.demo-data";4import "@simple-table/angular/styles.css";5import type { FacultyMember } from "./column-sorting.demo-data";67@Component({8 selector: "column-sorting-demo",9 standalone: true,10 imports: [SimpleTableComponent],11 template: `12 <simple-table13 [getRowId]="getRowId"14 [rows]="rows"15 [columns]="headers"16 [height]="height"17 [theme]="theme"18 initialSortColumn="age"19 initialSortDirection="desc"20 ></simple-table>21 `,22})23export class ColumnSortingDemoComponent {24 @Input() height: string | number = "400px";25 @Input() theme?: Theme;2627 readonly rows: FacultyMember[] = columnSortingConfig.rows;28 readonly headers: AngularColumnDef<FacultyMember>[] = columnSortingConfig.headers;2930 getRowId = ({ row }: GetRowIdParams<FacultyMember>) => row.id;31}323334// column-sorting.demo-data.ts35// Self-contained demo table setup for this example.36import type { AngularColumnDef, ValueFormatterProps } from "@simple-table/angular";3738export interface FacultyMember {39 id: number;40 name: string;41 age: number;42 role: string;43 department: string;44 startDate: string;45}4647export const COLUMN_SORTING_DATA: FacultyMember[] = [48 {49 id: 1,50 name: "Dr. Elena Vasquez",51 age: 42,52 role: "Computer Science Professor",53 department: "Computer Science",54 startDate: "2015-08-15",55 },56 {57 id: 2,58 name: "Prof. Michael Chang",59 age: 38,60 role: "Mathematics Professor",61 department: "Mathematics",62 startDate: "2017-01-10",63 },64 {65 id: 3,66 name: "Dr. Sarah Mitchell",67 age: 45,68 role: "Dean of Engineering",69 department: "Administration",70 startDate: "2012-09-01",71 },72 {73 id: 4,74 name: "Alex Parker",75 age: 22,76 role: "Graduate Student",77 department: "Computer Science",78 startDate: "2023-09-01",79 },80 {81 id: 5,82 name: "Dr. James Wilson",83 age: 51,84 role: "Physics Professor",85 department: "Physics",86 startDate: "2008-03-15",87 },88 {89 id: 6,90 name: "Maria Santos",91 age: 24,92 role: "Research Assistant",93 department: "Biology",94 startDate: "2022-06-01",95 },96 {97 id: 7,98 name: "Prof. David Kumar",99 age: 39,100 role: "Biology Professor",101 department: "Biology",102 startDate: "2018-02-14",103 },104 {105 id: 8,106 name: "Rachel Green",107 age: 28,108 role: "Lab Coordinator",109 department: "Chemistry",110 startDate: "2020-11-05",111 },112 {113 id: 9,114 name: "Dr. Lisa Chen",115 age: 47,116 role: "Psychology Professor",117 department: "Psychology",118 startDate: "2014-08-20",119 },120 {121 id: 10,122 name: "Ben Taylor",123 age: 23,124 role: "Teaching Assistant",125 department: "Mathematics",126 startDate: "2023-01-15",127 },128 {129 id: 11,130 name: "Dr. Anna Rodriguez",131 age: 35,132 role: "Chemistry Professor",133 department: "Chemistry",134 startDate: "2019-07-01",135 },136 {137 id: 12,138 name: "Prof. Robert Kim",139 age: 44,140 role: "Department Head",141 department: "Engineering",142 startDate: "2011-04-12",143 },144];145146147export const columnSortingHeaders: AngularColumnDef<FacultyMember, any>[] = [148 { accessor: "id", label: "ID", width: 80, sortable: true, type: "number" },149 { accessor: "name", label: "Name", width: 180, sortable: true, type: "string" },150 { accessor: "age", label: "Age", width: 80, sortable: true, type: "number" },151 { accessor: "role", label: "Role", width: 200, sortable: true, type: "string" },152 {153 accessor: "department",154 label: "Department",155 width: 180,156 sortable: true,157 type: "string",158 valueFormatter: ({ value }: ValueFormatterProps<FacultyMember, string>) => {159 return value.charAt(0).toUpperCase() + value.slice(1);160 },161 },162 {163 accessor: "startDate",164 label: "Start Date",165 width: 140,166 sortable: true,167 type: "date",168 valueFormatter: ({ value }: ValueFormatterProps<FacultyMember>) => {169 if (typeof value === "string") {170 return new Date(value).toLocaleDateString("en-US", {171 year: "numeric",172 month: "short",173 day: "numeric",174 });175 }176 return String(value);177 },178 },179];180181export const columnSortingConfig = {182 headers: columnSortingHeaders,183 rows: COLUMN_SORTING_DATA,184};185186
1<template>2 <SimpleTable3 :columns="columnSortingConfig.headers"4 :rows="columnSortingConfig.rows"5 :get-row-id="getRowId"6 :height="height"7 :theme="theme"8 initial-sort-column="age"9 initial-sort-direction="desc"10 />11</template>1213<script setup lang="ts">14import { SimpleTable } from "@simple-table/vue";15import type { Theme, GetRowIdParams } from "@simple-table/vue";16import { columnSortingConfig } from "./column-sorting.demo-data";17import type { FacultyMember } from "./column-sorting.demo-data";18import "@simple-table/vue/styles.css";1920withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {21 height: "400px",22});2324const getRowId = ({ row }: GetRowIdParams<FacultyMember>) => row.id;25</script>
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, GetRowIdParams } from "@simple-table/svelte";4 import { columnSortingConfig } from "./column-sorting.demo-data";5 import type { FacultyMember } from "./column-sorting.demo-data";6 import "@simple-table/svelte/styles.css";78 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();910 const getRowId = ({ row }: GetRowIdParams<FacultyMember>) => row.id;11</script>1213<SimpleTable14 columns={columnSortingConfig.headers}15 rows={columnSortingConfig.rows}16 getRowId={getRowId}17 {height}18 {theme}19 initialSortColumn="age"20 initialSortDirection="desc"21/>
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 getRowId={({ row }) => row.id}13 rows={columnSortingConfig.rows}14 height={props.height ?? "400px"}15 theme={props.theme}16 initialSortColumn="age"17 initialSortDirection="desc"18 />19 );20}
1import { SimpleTableVanilla } from "simple-table-core";2import type { FacultyMember } from "./column-sorting.demo-data";3import type { Theme, GetRowIdParams } from "simple-table-core";4import { columnSortingConfig } from "./column-sorting.demo-data";5import "simple-table-core/styles.css";678const getRowId = ({ row }: GetRowIdParams<FacultyMember>) => row.id;9export function renderColumnSortingDemo(10 container: HTMLElement,11 options?: { height?: string | number; theme?: Theme }12): SimpleTableVanilla<FacultyMember> {13 const table = new SimpleTableVanilla(container, {14 getRowId,15 columns: columnSortingConfig.headers,16 rows: columnSortingConfig.rows,17 height: options?.height ?? "400px",18 theme: options?.theme,19 initialSortColumn: "age",20 initialSortDirection: "desc",21 });22 return table;23}242526// column-sorting.demo-data.ts27// Self-contained demo table setup for this example.28import type { ColumnDef } from "simple-table-core";2930export interface FacultyMember {31 id: number;32 name: string;33 age: number;34 role: string;35 department: string;36 startDate: string;37}3839export const COLUMN_SORTING_DATA: FacultyMember[] = [40 {41 id: 1,42 name: "Dr. Elena Vasquez",43 age: 42,44 role: "Computer Science Professor",45 department: "Computer Science",46 startDate: "2015-08-15",47 },48 {49 id: 2,50 name: "Prof. Michael Chang",51 age: 38,52 role: "Mathematics Professor",53 department: "Mathematics",54 startDate: "2017-01-10",55 },56 {57 id: 3,58 name: "Dr. Sarah Mitchell",59 age: 45,60 role: "Dean of Engineering",61 department: "Administration",62 startDate: "2012-09-01",63 },64 {65 id: 4,66 name: "Alex Parker",67 age: 22,68 role: "Graduate Student",69 department: "Computer Science",70 startDate: "2023-09-01",71 },72 {73 id: 5,74 name: "Dr. James Wilson",75 age: 51,76 role: "Physics Professor",77 department: "Physics",78 startDate: "2008-03-15",79 },80 {81 id: 6,82 name: "Maria Santos",83 age: 24,84 role: "Research Assistant",85 department: "Biology",86 startDate: "2022-06-01",87 },88 {89 id: 7,90 name: "Prof. David Kumar",91 age: 39,92 role: "Biology Professor",93 department: "Biology",94 startDate: "2018-02-14",95 },96 {97 id: 8,98 name: "Rachel Green",99 age: 28,100 role: "Lab Coordinator",101 department: "Chemistry",102 startDate: "2020-11-05",103 },104 {105 id: 9,106 name: "Dr. Lisa Chen",107 age: 47,108 role: "Psychology Professor",109 department: "Psychology",110 startDate: "2014-08-20",111 },112 {113 id: 10,114 name: "Ben Taylor",115 age: 23,116 role: "Teaching Assistant",117 department: "Mathematics",118 startDate: "2023-01-15",119 },120 {121 id: 11,122 name: "Dr. Anna Rodriguez",123 age: 35,124 role: "Chemistry Professor",125 department: "Chemistry",126 startDate: "2019-07-01",127 },128 {129 id: 12,130 name: "Prof. Robert Kim",131 age: 44,132 role: "Department Head",133 department: "Engineering",134 startDate: "2011-04-12",135 },136];137138139export const columnSortingHeaders: ColumnDef<FacultyMember>[] = [140 { accessor: "id", label: "ID", width: 80, sortable: true, type: "number" },141 { accessor: "name", label: "Name", width: 180, sortable: true, type: "string" },142 { accessor: "age", label: "Age", width: 80, sortable: true, type: "number" },143 { accessor: "role", label: "Role", width: 200, sortable: true, type: "string" },144 {145 accessor: "department",146 label: "Department",147 width: 180,148 sortable: true,149 type: "string",150 valueFormatter: ({ value }) => {151 const department = String(value);152 return department.charAt(0).toUpperCase() + department.slice(1);153 },154 },155 {156 accessor: "startDate",157 label: "Start Date",158 width: 140,159 sortable: true,160 type: "date",161 valueFormatter: ({ value }) => {162 if (typeof value === "string") {163 return new Date(value).toLocaleDateString("en-US", {164 year: "numeric",165 month: "short",166 day: "numeric",167 });168 }169 return String(value);170 },171 },172];173174export const columnSortingConfig = {175 headers: columnSortingHeaders,176 rows: COLUMN_SORTING_DATA,177};178179
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";3import type { Theme, SortColumn } from "@simple-table/react";4import { externalSortConfig, type SortableEmployee } from "./external-sort.demo-data";5import "@simple-table/react/styles.css";67const ExternalSortDemo = ({8 height = "400px",9 theme10}: {11 height?: string | number;12 theme?: Theme;13}) => {14 const [sortConfig, setSortConfig] = useState<SortColumn | null>(null);1516 const sortedData = useMemo(() => {17 if (!sortConfig) return externalSortConfig.rows;18 const sorted = [...externalSortConfig.rows].sort((a, b) => {19 const key = sortConfig.key.accessor as keyof SortableEmployee;20 const aVal = a[key];21 const bVal = b[key];22 if (aVal === bVal) return 0;23 const cmp =24 sortConfig.key.type === "number"25 ? (aVal as number) - (bVal as number)26 : String(aVal).localeCompare(String(bVal));27 return sortConfig.direction === "asc" ? cmp : -cmp;28 });29 return sorted;30 }, [sortConfig]);3132 return (33 <SimpleTable34 columns={externalSortConfig.headers}35 rows={sortedData}36 onSortChange={setSortConfig}37 externalSortHandling38 columnResizing39 height={height}40 theme={theme}41 getRowId={({ row }) => row.id}42 />43 );44};4546export default ExternalSortDemo;
1import { Component, Input } from "@angular/core";2import { SimpleTableImports } from "@simple-table/angular";3import type { AngularColumnDef, ColumnType, GetRowIdParams, SortColumn, Theme } from "@simple-table/angular";4import { externalSortConfig } from "./external-sort.demo-data";5import "@simple-table/angular/styles.css";6import type { SortableEmployee } from "./external-sort.demo-data";78type SortableKey = keyof SortableEmployee;910function isSortableKey(accessor: string): accessor is SortableKey {11 return (12 accessor === "id" ||13 accessor === "name" ||14 accessor === "age" ||15 accessor === "email" ||16 accessor === "salary" ||17 accessor === "department"18 );19}2021type ActiveSort = {22 accessor: SortableKey;23 direction: "asc" | "desc";24 type?: ColumnType;25};2627@Component({28 selector: "external-sort-demo",29 standalone: true,30 imports: [SimpleTableImports],31 template: `32 <div style="margin-bottom: 8px; font-size: 13px; color: #334155">33 <strong>Sort:</strong> {{ sortLabel }}34 </div>35 <simple-table36 [getRowId]="getRowId"37 [rows]="displayRows"38 [columns]="headers"39 [height]="height"40 [theme]="theme"41 [externalSortHandling]="true"42 [columnResizing]="true"43 (sortChange)="handleSortChange($event)"44 ></simple-table>45 `,46})47export class ExternalSortDemoComponent {48 @Input() height: string | number = "400px";49 @Input() theme?: Theme;5051 readonly headers: AngularColumnDef<SortableEmployee>[] = externalSortConfig.headers;52 sortLabel = "None";53 displayRows: SortableEmployee[] = [...externalSortConfig.rows];54 private activeSort: ActiveSort | null = null;5556 handleSortChange = (sort: SortColumn | null): void => {57 if (!sort || !isSortableKey(sort.key.accessor)) {58 this.activeSort = null;59 this.sortLabel = "None";60 } else {61 this.activeSort = {62 accessor: sort.key.accessor,63 direction: sort.direction,64 type: sort.key.type,65 };66 this.sortLabel = `${sort.key.accessor} ${sort.direction}`;67 }68 this.displayRows = this.sortRows();69 };7071 private sortRows(): SortableEmployee[] {72 const rows = [...externalSortConfig.rows];73 if (!this.activeSort) return rows;74 const { accessor, type, direction } = this.activeSort;75 return rows.sort((a, b) => {76 const aVal = a[accessor];77 const bVal = b[accessor];78 if (aVal === bVal) return 0;79 const cmp =80 type === "number"81 ? (Number(aVal) || 0) - (Number(bVal) || 0)82 : String(aVal).localeCompare(String(bVal));83 return direction === "asc" ? cmp : -cmp;84 });85 }8687 getRowId = ({ row }: GetRowIdParams<SortableEmployee>) => row.id;88}899091// external-sort.demo-data.ts92// Self-contained demo table setup for this example.93import type { AngularColumnDef, ValueFormatterProps } from "@simple-table/angular";9495export interface SortableEmployee {96 id: number;97 name: string;98 age: number;99 email: string;100 salary: number;101 department: string;102}103104export const externalSortData: SortableEmployee[] = [105 { id: 1, name: "Dr. Elena Vasquez", age: 42, email: "elena.vasquez@techcorp.com", salary: 145000, department: "AI Research" },106 { id: 2, name: "Kai Tanaka", age: 29, email: "k.tanaka@techcorp.com", salary: 95000, department: "UX Design" },107 { id: 3, name: "Amara Okafor", age: 35, email: "amara.okafor@techcorp.com", salary: 125000, department: "DevOps" },108 { id: 4, name: "Santiago Rodriguez", age: 27, email: "s.rodriguez@techcorp.com", salary: 82000, department: "Marketing" },109 { id: 5, name: "Priya Chakraborty", age: 33, email: "priya.c@techcorp.com", salary: 118000, department: "Engineering" },110 { id: 6, name: "Magnus Eriksson", age: 38, email: "magnus.erik@techcorp.com", salary: 110000, department: "Product" },111 { id: 7, name: "Zara Al-Rashid", age: 31, email: "zara.alrashid@techcorp.com", salary: 98000, department: "Sales" },112 { id: 8, name: "Luca Rossi", age: 26, email: "luca.rossi@techcorp.com", salary: 75000, department: "Marketing" },113 { id: 9, name: "Dr. Sarah Kim", age: 45, email: "sarah.kim@techcorp.com", salary: 165000, department: "AI Research" },114 { id: 10, name: "Olumide Adebayo", age: 30, email: "olumide.a@techcorp.com", salary: 105000, department: "Engineering" },115 { id: 11, name: "Isabella Chen", age: 24, email: "isabella.chen@techcorp.com", salary: 68000, department: "UX Design" },116 { id: 12, name: "Dmitri Volkov", age: 39, email: "dmitri.volkov@techcorp.com", salary: 135000, department: "DevOps" },117];118119export const externalSortHeaders: AngularColumnDef<SortableEmployee, any>[] = [120 { accessor: "name", label: "Name", width: "1fr", minWidth: 120, sortable: true, type: "string" },121 { accessor: "age", label: "Age", width: 120, sortable: true, type: "number" },122 { accessor: "department", label: "Department", width: 150, sortable: true, type: "string" },123 { accessor: "email", label: "Email", width: 200, sortable: true, type: "string" },124 {125 accessor: "salary",126 label: "Salary",127 width: 120,128 sortable: true,129 type: "number",130 align: "right",131 valueFormatter: ({ value }: ValueFormatterProps<SortableEmployee, number>) => `$${value.toLocaleString()}`,132 },133];134135export const externalSortConfig = {136 headers: externalSortHeaders,137 rows: externalSortData,138 tableProps: { externalSortHandling: true, columnResizing: true },139};140
1<template>2 <SimpleTable3 :columns="externalSortConfig.headers"4 :rows="sortedRows"5 :get-row-id="getRowId"6 :external-sort-handling="true"7 :column-resizing="externalSortConfig.tableProps.columnResizing"8 :height="height"9 :theme="theme"10 @sort-change="handleSortChange"11 />12</template>1314<script setup lang="ts">15import { ref, computed } from "vue";16import { SimpleTable } from "@simple-table/vue";17import type { Theme, SortColumn, GetRowIdParams } from "@simple-table/vue";18import { externalSortConfig } from "./external-sort.demo-data";19import type { SortableEmployee } from "./external-sort.demo-data";20import "@simple-table/vue/styles.css";2122withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {23 height: "400px",24});2526const sortConfig = ref<SortColumn | null>(null);2728const getRowId = ({ row }: GetRowIdParams<SortableEmployee>) => row.id;2930const sortedRows = computed(() => {31 const data = [...externalSortConfig.rows];32 const sort = sortConfig.value;33 if (!sort) return data;3435 const accessor = sort.key.accessor as keyof SortableEmployee;36 const dir = sort.direction === "asc" ? 1 : -1;3738 return data.sort((a, b) => {39 const aVal = a[accessor];40 const bVal = b[accessor];41 if (aVal == null && bVal == null) return 0;42 if (aVal == null) return dir;43 if (bVal == null) return -dir;44 if (typeof aVal === "string" && typeof bVal === "string") return dir * aVal.localeCompare(bVal);45 return dir * (Number(aVal) - Number(bVal));46 });47});4849function handleSortChange(sort: SortColumn | null) {50 sortConfig.value = sort;51}52</script>
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, SortColumn, GetRowIdParams } from "@simple-table/svelte";4 import { externalSortConfig } from "./external-sort.demo-data";5 import type { SortableEmployee } from "./external-sort.demo-data";6 import "@simple-table/svelte/styles.css";78 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();910 let sortConfig = $state<SortColumn | null>(null);1112 const getRowId = ({ row }: GetRowIdParams<SortableEmployee>) => row.id;1314 let sortedRows = $derived.by(() => {15 const data = [...externalSortConfig.rows];16 if (!sortConfig) return data;1718 const accessor = sortConfig.key.accessor as keyof SortableEmployee;19 const dir = sortConfig.direction === "asc" ? 1 : -1;2021 return data.sort((a, b) => {22 const aVal = a[accessor];23 const bVal = b[accessor];24 if (aVal == null && bVal == null) return 0;25 if (aVal == null) return dir;26 if (bVal == null) return -dir;27 if (typeof aVal === "string" && typeof bVal === "string") return dir * aVal.localeCompare(bVal);28 return dir * (Number(aVal) - Number(bVal));29 });30 });3132 function handleSortChange(sort: SortColumn | null) {33 sortConfig = sort;34 }35</script>3637<SimpleTable38 columns={externalSortConfig.headers}39 rows={sortedRows}40 {getRowId}41 externalSortHandling={true}42 columnResizing={externalSortConfig.tableProps.columnResizing}43 onSortChange={handleSortChange}44 {height}45 {theme}46/>
1import { createSignal, createMemo } from "solid-js";2import { SimpleTable } from "@simple-table/solid";3import type { Theme, SortColumn } from "@simple-table/solid";4import { externalSortConfig, type SortableEmployee } from "./external-sort.demo-data";5import "@simple-table/solid/styles.css";67export default function ExternalSortDemo(props: {8 height?: string | number;9 theme?: Theme;10}) {11 const [sortConfig, setSortConfig] = createSignal<SortColumn | null>(null);1213 const sortedData = createMemo(() => {14 const sort = sortConfig();15 if (!sort) return externalSortConfig.rows;16 return [...externalSortConfig.rows].sort((a, b) => {17 const key = sort.key.accessor as keyof SortableEmployee;18 const aVal = a[key];19 const bVal = b[key];20 if (aVal === bVal) return 0;21 const cmp =22 sort.key.type === "number"23 ? (aVal as number) - (bVal as number)24 : String(aVal).localeCompare(String(bVal));25 return sort.direction === "asc" ? cmp : -cmp;26 });27 });2829 return (30 <SimpleTable31 columns={externalSortConfig.headers}32 getRowId={({ row }) => row.id}33 rows={sortedData()}34 onSortChange={setSortConfig}35 externalSortHandling36 columnResizing37 height={props.height ?? "400px"}38 theme={props.theme}39 />40 );41}
1import { SimpleTableVanilla } from "simple-table-core";2import type { SortableEmployee } from "./external-sort.demo-data";3import type { Theme, SortColumn, GetRowIdParams, ColumnType } from "simple-table-core";4import { externalSortConfig } from "./external-sort.demo-data";5import "simple-table-core/styles.css";67const getRowId = ({ row }: GetRowIdParams<SortableEmployee>) => row.id;89type SortableKey = keyof SortableEmployee;1011function isSortableKey(accessor: string): accessor is SortableKey {12 return (13 accessor === "id" ||14 accessor === "name" ||15 accessor === "age" ||16 accessor === "email" ||17 accessor === "salary" ||18 accessor === "department"19 );20}2122type ActiveSort = {23 accessor: SortableKey;24 direction: "asc" | "desc";25 type?: ColumnType;26};2728export function renderExternalSortDemo(29 container: HTMLElement,30 options?: { height?: string | number; theme?: Theme },31): SimpleTableVanilla<SortableEmployee> {32 let currentSort: ActiveSort | null = null;3334 function getSortedRows(): SortableEmployee[] {35 const rows = [...externalSortConfig.rows];36 if (!currentSort) return rows;37 const { accessor, type, direction } = currentSort;38 return rows.sort((a, b) => {39 const aVal = a[accessor];40 const bVal = b[accessor];41 if (aVal === bVal) return 0;42 const cmp =43 type === "number"44 ? (Number(aVal) || 0) - (Number(bVal) || 0)45 : String(aVal).localeCompare(String(bVal));46 return direction === "asc" ? cmp : -cmp;47 });48 }4950 const table = new SimpleTableVanilla(container, {51 getRowId,52 columns: externalSortConfig.headers,53 rows: externalSortConfig.rows,54 height: options?.height ?? "400px",55 theme: options?.theme,56 externalSortHandling: true,57 columnResizing: true,58 onSortChange: (sort: SortColumn | null) => {59 if (!sort || !isSortableKey(sort.key.accessor)) {60 currentSort = null;61 } else {62 currentSort = {63 accessor: sort.key.accessor,64 direction: sort.direction,65 type: sort.key.type,66 };67 }68 table.update({ rows: getSortedRows() });69 },70 });71 return table;72}737475// external-sort.demo-data.ts76// Self-contained demo table setup for this example.77import type { ColumnDef } from "simple-table-core";7879export interface SortableEmployee {80 id: number;81 name: string;82 age: number;83 email: string;84 salary: number;85 department: string;86}8788export const externalSortData: SortableEmployee[] = [89 { id: 1, name: "Dr. Elena Vasquez", age: 42, email: "elena.vasquez@techcorp.com", salary: 145000, department: "AI Research" },90 { id: 2, name: "Kai Tanaka", age: 29, email: "k.tanaka@techcorp.com", salary: 95000, department: "UX Design" },91 { id: 3, name: "Amara Okafor", age: 35, email: "amara.okafor@techcorp.com", salary: 125000, department: "DevOps" },92 { id: 4, name: "Santiago Rodriguez", age: 27, email: "s.rodriguez@techcorp.com", salary: 82000, department: "Marketing" },93 { id: 5, name: "Priya Chakraborty", age: 33, email: "priya.c@techcorp.com", salary: 118000, department: "Engineering" },94 { id: 6, name: "Magnus Eriksson", age: 38, email: "magnus.erik@techcorp.com", salary: 110000, department: "Product" },95 { id: 7, name: "Zara Al-Rashid", age: 31, email: "zara.alrashid@techcorp.com", salary: 98000, department: "Sales" },96 { id: 8, name: "Luca Rossi", age: 26, email: "luca.rossi@techcorp.com", salary: 75000, department: "Marketing" },97 { id: 9, name: "Dr. Sarah Kim", age: 45, email: "sarah.kim@techcorp.com", salary: 165000, department: "AI Research" },98 { id: 10, name: "Olumide Adebayo", age: 30, email: "olumide.a@techcorp.com", salary: 105000, department: "Engineering" },99 { id: 11, name: "Isabella Chen", age: 24, email: "isabella.chen@techcorp.com", salary: 68000, department: "UX Design" },100 { id: 12, name: "Dmitri Volkov", age: 39, email: "dmitri.volkov@techcorp.com", salary: 135000, department: "DevOps" },101];102103export const externalSortHeaders: ColumnDef<SortableEmployee>[] = [104 { accessor: "name", label: "Name", width: "1fr", minWidth: 120, sortable: true, type: "string" },105 { accessor: "age", label: "Age", width: 120, sortable: true, type: "number" },106 { accessor: "department", label: "Department", width: 150, sortable: true, type: "string" },107 { accessor: "email", label: "Email", width: 200, sortable: true, type: "string" },108 {109 accessor: "salary",110 label: "Salary",111 width: 120,112 sortable: true,113 type: "number",114 align: "right",115 valueFormatter: ({ value }) => `$${Number(value).toLocaleString()}`,116 },117];118119export const externalSortConfig = {120 headers: externalSortHeaders,121 rows: externalSortData,122 tableProps: { externalSortHandling: true, columnResizing: true },123};124
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). |