Documentation
Column Filtering
Let users filter columns by type — or drive filtering from your server.
Enable filtering
Set filterable: true on a column. Operators follow the column type (string, number, date, boolean, or enum).
TypeScript
Copy
{accessor: "name",label: "Full Name",width: "1fr",type: "string",filterable: true,}
TypeScript
Copy
{accessor: "name",label: "Full Name",width: "1fr",type: "string",filterable: true,}
TypeScript
Copy
{accessor: "name",label: "Full Name",width: "1fr",type: "string",filterable: true,}
TypeScript
Copy
{accessor: "name",label: "Full Name",width: "1fr",type: "string",filterable: true,}
TypeScript
Copy
{accessor: "name",label: "Full Name",width: "1fr",type: "string",filterable: true,}
TypeScript
Copy
{accessor: "name",label: "Full Name",width: "1fr",type: "string",filterable: true,}
Limit filter operators
Use filterOperators to show only the comparisons you want, in that order. Invalid operators for the column type are ignored. Has no effect on enum columns.
TypeScript
Copy
{accessor: "name",label: "Full Name",type: "string",filterable: true,filterOperators: ["contains", "equals"],}
TypeScript
Copy
{accessor: "name",label: "Full Name",type: "string",filterable: true,filterOperators: ["contains", "equals"],}
TypeScript
Copy
{accessor: "name",label: "Full Name",type: "string",filterable: true,filterOperators: ["contains", "equals"],}
TypeScript
Copy
{accessor: "name",label: "Full Name",type: "string",filterable: true,filterOperators: ["contains", "equals"],}
TypeScript
Copy
{accessor: "name",label: "Full Name",type: "string",filterable: true,filterOperators: ["contains", "equals"],}
TypeScript
Copy
{accessor: "name",label: "Full Name",type: "string",filterable: true,filterOperators: ["contains", "equals"],}
Enum filters
Enum columns use a checkbox picker from enumOptions. With more than 10 options, a search input appears automatically.
TypeScript
Copy
{accessor: "status",label: "Status",type: "enum",filterable: true,enumOptions: [{ label: "Active", value: "active" },{ label: "Inactive", value: "inactive" },{ label: "Pending", value: "pending" },],}
TypeScript
Copy
{accessor: "status",label: "Status",type: "enum",filterable: true,enumOptions: [{ label: "Active", value: "active" },{ label: "Inactive", value: "inactive" },{ label: "Pending", value: "pending" },],}
TypeScript
Copy
{accessor: "status",label: "Status",type: "enum",filterable: true,enumOptions: [{ label: "Active", value: "active" },{ label: "Inactive", value: "inactive" },{ label: "Pending", value: "pending" },],}
TypeScript
Copy
{accessor: "status",label: "Status",type: "enum",filterable: true,enumOptions: [{ label: "Active", value: "active" },{ label: "Inactive", value: "inactive" },{ label: "Pending", value: "pending" },],}
TypeScript
Copy
{accessor: "status",label: "Status",type: "enum",filterable: true,enumOptions: [{ label: "Active", value: "active" },{ label: "Inactive", value: "inactive" },{ label: "Pending", value: "pending" },],}
TypeScript
Copy
{accessor: "status",label: "Status",type: "enum",filterable: true,enumOptions: [{ label: "Active", value: "active" },{ label: "Inactive", value: "inactive" },{ label: "Pending", value: "pending" },],}
External / server filtering
Set externalFilterHandling and handle onFilterChange — the table keeps filter UI while you supply pre-filtered rows.
React TSX
Copy
<SimpleTableexternalFilterHandlingcolumns={columns}rows={filteredRows}onFilterChange={(filters) => {// fetch or filter filteredRows from filters}}/>
Vue SFC
Copy
<SimpleTable:external-filter-handling="true":columns="columns":rows="filteredRows":on-filter-change="handleFilterChange"/>
Angular
Copy
<simple-table[externalFilterHandling]="true"[columns]="columns"[rows]="filteredRows"[onFilterChange]="handleFilterChange"></simple-table>
Svelte
Copy
<SimpleTableexternalFilterHandling={true}{columns}rows={filteredRows}onFilterChange={handleFilterChange}/>
Solid TSX
Copy
<SimpleTableexternalFilterHandlingcolumns={columns}rows={filteredRows()}onFilterChange={(filters) => {// fetch or filter filteredRows from filters}}/>
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows: filteredRows,externalFilterHandling: true,onFilterChange: (filters) => {// fetch or filter filteredRows from filters},});
Example
React TSX
Copy
1import {SimpleTable} from "@simple-table/react";import type { Theme } from "@simple-table/react";2import { columnFilteringConfig } from "./column-filtering.demo-data";3import "@simple-table/react/styles.css";45const ColumnFilteringDemo = ({6 height = "400px",7 theme,8}: {9 height?: string | number;10 theme?: Theme;11}) => {12 return (13 <SimpleTable14 columns={columnFilteringConfig.headers}15 rows={columnFilteringConfig.rows}16 height={height}17 theme={theme}18 />19 );20};2122export default ColumnFilteringDemo;
Vue SFC
Copy
1<template>2 <SimpleTable3 :columns="columnFilteringConfig.headers"4 :rows="columnFilteringConfig.rows"5 :height="height"6 :theme="theme"7 />8</template>910<script setup lang="ts">11import {SimpleTable} from "@simple-table/vue";import type { Theme } from "@simple-table/vue";12import { columnFilteringConfig } from "./column-filtering.demo-data";13import "@simple-table/vue/styles.css";1415withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {16 height: "400px",17});18</script>
Angularcolumn-filtering-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, Row, Theme } from "@simple-table/angular";3import { columnFilteringConfig } from "./column-filtering.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "column-filtering-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <simple-table12 [rows]="rows"13 [columns]="headers"14 [height]="height"15 [theme]="theme"16 ></simple-table>17 `,18})19export class ColumnFilteringDemoComponent {20 @Input() height: string | number = "400px";21 @Input() theme?: Theme;2223 readonly rows: Row[] = columnFilteringConfig.rows;24 readonly headers: AngularColumnDef[] = columnFilteringConfig.headers;25}262728// column-filtering.demo-data.ts29// Self-contained demo table setup for this example.30import type { Row, AngularColumnDef } from "@simple-table/angular";313233export const COLUMN_FILTERING_DATA: Row[] = [34 {35 id: 1,36 name: "Bianca Rossi",37 department: "Editorial",38 role: "Senior Editor",39 salary: 82000,40 startDate: "2020-02-14",41 isActive: true,42 },43 {44 id: 2,45 name: "Axel Chen",46 department: "Production",47 role: "Art Director",48 salary: 75000,49 startDate: "2021-06-18",50 isActive: true,51 },52 {53 id: 3,54 name: "Emilia Nakamura",55 department: "Editorial",56 role: "Managing Editor",57 salary: 95000,58 startDate: "2019-04-22",59 isActive: true,60 },61 {62 id: 4,63 name: "Luca Martinez",64 department: "Marketing",65 role: "Content Strategist",66 salary: 68000,67 startDate: "2022-01-12",68 isActive: false,69 },70 {71 id: 5,72 name: "Delia Kumar",73 department: "Production",74 role: "Layout Designer",75 salary: 72000,76 startDate: "2020-09-07",77 isActive: true,78 },79 {80 id: 6,81 name: "Cian O'Sullivan",82 department: "Sales",83 role: "Sales Representative",84 salary: 65000,85 startDate: "2021-11-03",86 isActive: true,87 },88 {89 id: 7,90 name: "Amara Okafor",91 department: "Human Resources",92 role: "HR Manager",93 salary: 78000,94 startDate: "2019-08-15",95 isActive: true,96 },97 {98 id: 8,99 name: "Rowan Thompson",100 department: "Production",101 role: "Cover Designer",102 salary: 69000,103 startDate: "2021-12-20",104 isActive: false,105 },106 {107 id: 9,108 name: "Celeste Petrov",109 department: "Marketing",110 role: "PR Specialist",111 salary: 63000,112 startDate: "2022-03-08",113 isActive: true,114 },115 {116 id: 10,117 name: "Quinn Hassan",118 department: "Sales",119 role: "Sales Manager",120 salary: 89000,121 startDate: "2020-05-11",122 isActive: true,123 },124 {125 id: 11,126 name: "Isla Williams",127 department: "Editorial",128 role: "Copy Editor",129 salary: 58000,130 startDate: "2021-10-25",131 isActive: true,132 },133 {134 id: 12,135 name: "Dax Silva",136 department: "Finance",137 role: "Financial Analyst",138 salary: 64000,139 startDate: "2022-07-14",140 isActive: false,141 },142 {143 id: 13,144 name: "Maya Patel",145 department: "IT Support",146 role: "Systems Administrator",147 salary: 71000,148 startDate: "2021-03-15",149 isActive: true,150 },151 {152 id: 14,153 name: "Jordan Lee",154 department: "Quality Assurance",155 role: "QA Engineer",156 salary: 67000,157 startDate: "2020-11-08",158 isActive: true,159 },160];161162163export const DEPARTMENT_OPTIONS = [164 { label: "Editorial", value: "Editorial" },165 { label: "Production", value: "Production" },166 { label: "Marketing", value: "Marketing" },167 { label: "Sales", value: "Sales" },168 { label: "Operations", value: "Operations" },169 { label: "Human Resources", value: "Human Resources" },170 { label: "Finance", value: "Finance" },171 { label: "Legal", value: "Legal" },172 { label: "IT Support", value: "IT Support" },173 { label: "Customer Service", value: "Customer Service" },174 { label: "Research & Development", value: "Research & Development" },175 { label: "Quality Assurance", value: "Quality Assurance" },176];177178export const columnFilteringHeaders: AngularColumnDef[] = [179 {180 accessor: "id",181 label: "ID",182 width: 80,183 type: "number",184 sortable: true,185 filterable: true,186 },187 {188 accessor: "name",189 label: "Employee Name",190 width: "1fr",191 minWidth: 150,192 type: "string",193 sortable: true,194 filterable: true,195 },196 {197 accessor: "department",198 label: "Department",199 width: "1fr",200 minWidth: 120,201 type: "enum",202 sortable: true,203 filterable: true,204 enumOptions: DEPARTMENT_OPTIONS,205 },206 {207 accessor: "role",208 label: "Role",209 width: 140,210 type: "string",211 sortable: true,212 filterable: true,213 },214 {215 accessor: "salary",216 label: "Salary",217 width: 120,218 align: "right",219 type: "number",220 sortable: true,221 filterable: true,222 cellRenderer: ({ row }) => {223 const salary = row.salary as number;224 return `$${salary.toLocaleString()}`;225 },226 },227 {228 accessor: "startDate",229 label: "Start Date",230 width: 130,231 type: "date",232 sortable: true,233 filterable: true,234 },235 {236 accessor: "isActive",237 label: "Active",238 width: 100,239 align: "center",240 type: "boolean",241 sortable: true,242 filterable: true,243 },244];245246export const columnFilteringConfig = {247 headers: columnFilteringHeaders,248 rows: COLUMN_FILTERING_DATA,249} as const;250
Svelte
Copy
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme } from "@simple-table/svelte";3 import { columnFilteringConfig } from "./column-filtering.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={columnFilteringConfig.headers}11 rows={columnFilteringConfig.rows}12 {height}13 {theme}14/>
Solid TSX
Copy
1import {SimpleTable} from "@simple-table/solid";import type { Theme } from "@simple-table/solid";2import { columnFilteringConfig } from "./column-filtering.demo-data";3import "@simple-table/solid/styles.css";45export default function ColumnFilteringDemo(props: {6 height?: string | number;7 theme?: Theme;8}) {9 return (10 <SimpleTable11 columns={columnFilteringConfig.headers}12 rows={columnFilteringConfig.rows}13 height={props.height ?? "400px"}14 theme={props.theme}15 />16 );17}
TypeScriptColumnFilteringDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme } from "simple-table-core";3import { columnFilteringConfig } from "./column-filtering.demo-data";4import "simple-table-core/styles.css";56export function renderColumnFilteringDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): SimpleTableVanilla {10 const table = new SimpleTableVanilla(container, {11 columns: columnFilteringConfig.headers,12 rows: columnFilteringConfig.rows,13 height: options?.height ?? "400px",14 theme: options?.theme,15 });16 return table;17}181920// column-filtering.demo-data.ts21// Self-contained demo table setup for this example.22import type { Row } from "simple-table-core";23import type { ColumnDef } from "simple-table-core";242526export const COLUMN_FILTERING_DATA: Row[] = [27 {28 id: 1,29 name: "Bianca Rossi",30 department: "Editorial",31 role: "Senior Editor",32 salary: 82000,33 startDate: "2020-02-14",34 isActive: true,35 },36 {37 id: 2,38 name: "Axel Chen",39 department: "Production",40 role: "Art Director",41 salary: 75000,42 startDate: "2021-06-18",43 isActive: true,44 },45 {46 id: 3,47 name: "Emilia Nakamura",48 department: "Editorial",49 role: "Managing Editor",50 salary: 95000,51 startDate: "2019-04-22",52 isActive: true,53 },54 {55 id: 4,56 name: "Luca Martinez",57 department: "Marketing",58 role: "Content Strategist",59 salary: 68000,60 startDate: "2022-01-12",61 isActive: false,62 },63 {64 id: 5,65 name: "Delia Kumar",66 department: "Production",67 role: "Layout Designer",68 salary: 72000,69 startDate: "2020-09-07",70 isActive: true,71 },72 {73 id: 6,74 name: "Cian O'Sullivan",75 department: "Sales",76 role: "Sales Representative",77 salary: 65000,78 startDate: "2021-11-03",79 isActive: true,80 },81 {82 id: 7,83 name: "Amara Okafor",84 department: "Human Resources",85 role: "HR Manager",86 salary: 78000,87 startDate: "2019-08-15",88 isActive: true,89 },90 {91 id: 8,92 name: "Rowan Thompson",93 department: "Production",94 role: "Cover Designer",95 salary: 69000,96 startDate: "2021-12-20",97 isActive: false,98 },99 {100 id: 9,101 name: "Celeste Petrov",102 department: "Marketing",103 role: "PR Specialist",104 salary: 63000,105 startDate: "2022-03-08",106 isActive: true,107 },108 {109 id: 10,110 name: "Quinn Hassan",111 department: "Sales",112 role: "Sales Manager",113 salary: 89000,114 startDate: "2020-05-11",115 isActive: true,116 },117 {118 id: 11,119 name: "Isla Williams",120 department: "Editorial",121 role: "Copy Editor",122 salary: 58000,123 startDate: "2021-10-25",124 isActive: true,125 },126 {127 id: 12,128 name: "Dax Silva",129 department: "Finance",130 role: "Financial Analyst",131 salary: 64000,132 startDate: "2022-07-14",133 isActive: false,134 },135 {136 id: 13,137 name: "Maya Patel",138 department: "IT Support",139 role: "Systems Administrator",140 salary: 71000,141 startDate: "2021-03-15",142 isActive: true,143 },144 {145 id: 14,146 name: "Jordan Lee",147 department: "Quality Assurance",148 role: "QA Engineer",149 salary: 67000,150 startDate: "2020-11-08",151 isActive: true,152 },153];154155156export const DEPARTMENT_OPTIONS = [157 { label: "Editorial", value: "Editorial" },158 { label: "Production", value: "Production" },159 { label: "Marketing", value: "Marketing" },160 { label: "Sales", value: "Sales" },161 { label: "Operations", value: "Operations" },162 { label: "Human Resources", value: "Human Resources" },163 { label: "Finance", value: "Finance" },164 { label: "Legal", value: "Legal" },165 { label: "IT Support", value: "IT Support" },166 { label: "Customer Service", value: "Customer Service" },167 { label: "Research & Development", value: "Research & Development" },168 { label: "Quality Assurance", value: "Quality Assurance" },169];170171export const columnFilteringHeaders: ColumnDef[] = [172 {173 accessor: "id",174 label: "ID",175 width: 80,176 type: "number",177 sortable: true,178 filterable: true,179 },180 {181 accessor: "name",182 label: "Employee Name",183 width: "1fr",184 minWidth: 150,185 type: "string",186 sortable: true,187 filterable: true,188 },189 {190 accessor: "department",191 label: "Department",192 width: "1fr",193 minWidth: 120,194 type: "enum",195 sortable: true,196 filterable: true,197 enumOptions: DEPARTMENT_OPTIONS,198 },199 {200 accessor: "role",201 label: "Role",202 width: 140,203 type: "string",204 sortable: true,205 filterable: true,206 },207 {208 accessor: "salary",209 label: "Salary",210 width: 120,211 align: "right",212 type: "number",213 sortable: true,214 filterable: true,215 cellRenderer: ({ row }) => {216 const salary = row.salary as number;217 return `$${salary.toLocaleString()}`;218 },219 },220 {221 accessor: "startDate",222 label: "Start Date",223 width: 130,224 type: "date",225 sortable: true,226 filterable: true,227 },228 {229 accessor: "isActive",230 label: "Active",231 width: 100,232 align: "center",233 type: "boolean",234 sortable: true,235 filterable: true,236 },237];238239export const columnFilteringConfig = {240 headers: columnFilteringHeaders,241 rows: COLUMN_FILTERING_DATA,242} as const;243
External filtering example
Filtering is handled outside the table; filter controls still update.
External Filter Status: No filters applied
React TSX
Copy
1import { useState, useMemo } from "react";2import {SimpleTable} from "@simple-table/react";import type { Theme, TableFilterState } from "@simple-table/react";3import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4import "@simple-table/react/styles.css";56const ExternalFilterDemo = ({7 height = "400px",8 theme,9}: {10 height?: string | number;11 theme?: Theme;12}) => {13 const [filters, setFilters] = useState<TableFilterState>({});1415 const filteredData = useMemo(() => {16 const filterEntries = Object.entries(filters);17 if (filterEntries.length === 0) return externalFilterConfig.rows;1819 return externalFilterConfig.rows.filter((row) =>20 filterEntries.every(([accessor, filter]) =>21 matchesFilter(row[accessor as keyof typeof row] as any, filter)22 )23 );24 }, [filters]);2526 return (27 <SimpleTable28 columns={externalFilterConfig.headers}29 rows={filteredData}30 onFilterChange={setFilters}31 externalFilterHandling32 columnResizing33 height={height}34 theme={theme}35 />36 );37};3839export default ExternalFilterDemo;
Vue SFC
Copy
1<script setup lang="ts">2import { ref, computed } from "vue";3import {SimpleTable} from "@simple-table/vue";import type { Theme, TableFilterState } from "@simple-table/vue";4import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";5import "@simple-table/vue/styles.css";67const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {8 height: "400px",9});1011const filters = ref<TableFilterState>({});1213const filteredRows = computed(() => {14 const entries = Object.entries(filters.value);15 if (entries.length === 0) return externalFilterConfig.rows;1617 return externalFilterConfig.rows.filter((row) =>18 entries.every(([accessor, filter]) =>19 matchesFilter(row[accessor as keyof typeof row] as any, filter)20 )21 );22});2324function handleFilterChange(newFilters: TableFilterState) {25 filters.value = newFilters;26}27</script>2829<template>30 <SimpleTable31 :columns="externalFilterConfig.headers"32 :rows="filteredRows"33 :external-filter-handling="true"34 :column-resizing="true"35 :height="props.height"36 :theme="props.theme"37 :on-filter-change="handleFilterChange"38 />39</template>
Angularexternal-filter-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, Row, TableFilterState, Theme } from "@simple-table/angular";3import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "external-filter-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <simple-table12 [rows]="filteredRows"13 [columns]="headers"14 [externalFilterHandling]="true"15 [columnResizing]="true"16 [height]="height"17 [theme]="theme"18 [onFilterChange]="handleFilterChange"19 ></simple-table>20 `,21})22export class ExternalFilterDemoComponent {23 @Input() height: string | number = "400px";24 @Input() theme?: Theme;2526 readonly headers: AngularColumnDef[] = externalFilterConfig.headers;27 private filters: TableFilterState = {};2829 handleFilterChange = (newFilters: TableFilterState) => {30 this.filters = newFilters;31 };3233 get filteredRows(): Row[] {34 const entries = Object.entries(this.filters);35 if (entries.length === 0) return externalFilterConfig.rows as Row[];3637 return (externalFilterConfig.rows as Row[]).filter((row) =>38 entries.every(([accessor, filter]) =>39 matchesFilter(row[accessor] as any, filter)40 )41 );42 }43}444546// external-filter.demo-data.ts47// Self-contained demo table setup for this example.48import type { AngularColumnDef, TableFilterState } from "@simple-table/angular";495051type CellValue = string | number | boolean | null | undefined;5253export function matchesFilter(54 value: CellValue,55 filter: TableFilterState[string]56): boolean {57 const { operator } = filter;5859 switch (operator) {60 case "equals":61 return value === filter.value;62 case "notEquals":63 return value !== filter.value;64 case "contains":65 return String(value).toLowerCase().includes(String(filter.value).toLowerCase());66 case "notContains":67 return !String(value).toLowerCase().includes(String(filter.value).toLowerCase());68 case "startsWith":69 return String(value).toLowerCase().startsWith(String(filter.value).toLowerCase());70 case "endsWith":71 return String(value).toLowerCase().endsWith(String(filter.value).toLowerCase());72 case "greaterThan":73 return Number(value) > Number(filter.value);74 case "lessThan":75 return Number(value) < Number(filter.value);76 case "greaterThanOrEqual":77 return Number(value) >= Number(filter.value);78 case "lessThanOrEqual":79 return Number(value) <= Number(filter.value);80 case "between":81 return (82 filter.values != null &&83 Number(value) >= Number(filter.values[0]) &&84 Number(value) <= Number(filter.values[1])85 );86 case "in":87 return filter.values != null && filter.values.includes(value);88 case "notIn":89 return filter.values != null && !filter.values.includes(value);90 case "isEmpty":91 return value == null || value === "";92 case "isNotEmpty":93 return value != null && value !== "";94 default:95 return true;96 }97}9899const DEPARTMENT_OPTIONS = [100 { label: "AI Research", value: "AI Research" },101 { label: "UX Design", value: "UX Design" },102 { label: "DevOps", value: "DevOps" },103 { label: "Marketing", value: "Marketing" },104 { label: "Engineering", value: "Engineering" },105 { label: "Product", value: "Product" },106 { label: "Sales", value: "Sales" },107];108109const LOCATION_OPTIONS = [110 { label: "San Francisco", value: "San Francisco" },111 { label: "Tokyo", value: "Tokyo" },112 { label: "Lagos", value: "Lagos" },113 { label: "Mexico City", value: "Mexico City" },114 { label: "Kolkata", value: "Kolkata" },115 { label: "Stockholm", value: "Stockholm" },116 { label: "Dubai", value: "Dubai" },117 { label: "Milan", value: "Milan" },118 { label: "Seoul", value: "Seoul" },119 { label: "Austin", value: "Austin" },120 { label: "London", value: "London" },121 { label: "Moscow", value: "Moscow" },122];123124export const externalFilterData = [125 { id: 1, name: "Dr. Elena Vasquez", age: 42, email: "elena.vasquez@techcorp.com", salary: 145000, department: "AI Research", active: true, location: "San Francisco" },126 { id: 2, name: "Kai Tanaka", age: 29, email: "k.tanaka@techcorp.com", salary: 95000, department: "UX Design", active: true, location: "Tokyo" },127 { id: 3, name: "Amara Okafor", age: 35, email: "amara.okafor@techcorp.com", salary: 125000, department: "DevOps", active: false, location: "Lagos" },128 { id: 4, name: "Santiago Rodriguez", age: 27, email: "s.rodriguez@techcorp.com", salary: 82000, department: "Marketing", active: true, location: "Mexico City" },129 { id: 5, name: "Priya Chakraborty", age: 33, email: "priya.c@techcorp.com", salary: 118000, department: "Engineering", active: true, location: "Kolkata" },130 { id: 6, name: "Magnus Eriksson", age: 38, email: "magnus.erik@techcorp.com", salary: 110000, department: "Product", active: false, location: "Stockholm" },131 { id: 7, name: "Zara Al-Rashid", age: 31, email: "zara.alrashid@techcorp.com", salary: 98000, department: "Sales", active: true, location: "Dubai" },132 { id: 8, name: "Luca Rossi", age: 26, email: "luca.rossi@techcorp.com", salary: 75000, department: "Marketing", active: true, location: "Milan" },133 { id: 9, name: "Dr. Sarah Kim", age: 45, email: "sarah.kim@techcorp.com", salary: 165000, department: "AI Research", active: true, location: "Seoul" },134 { id: 10, name: "Olumide Adebayo", age: 30, email: "olumide.a@techcorp.com", salary: 105000, department: "Engineering", active: false, location: "Austin" },135 { id: 11, name: "Isabella Chen", age: 24, email: "isabella.chen@techcorp.com", salary: 68000, department: "UX Design", active: true, location: "London" },136 { id: 12, name: "Dmitri Volkov", age: 39, email: "dmitri.volkov@techcorp.com", salary: 135000, department: "DevOps", active: true, location: "Moscow" },137];138139export const externalFilterHeaders: AngularColumnDef[] = [140 { accessor: "name", label: "Name", width: "1fr", minWidth: 120, filterable: true, type: "string" },141 { accessor: "age", label: "Age", width: 120, filterable: true, type: "number" },142 {143 accessor: "department",144 label: "Department",145 width: 150,146 filterable: true,147 type: "enum",148 enumOptions: DEPARTMENT_OPTIONS,149 },150 {151 accessor: "location",152 label: "Location",153 width: 150,154 filterable: true,155 type: "enum",156 enumOptions: LOCATION_OPTIONS,157 },158 { accessor: "active", label: "Active", width: 120, filterable: true, type: "boolean" },159 {160 accessor: "salary",161 label: "Salary",162 width: 120,163 filterable: true,164 type: "number",165 align: "right",166 valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,167 },168];169170export const externalFilterConfig = {171 headers: externalFilterHeaders,172 rows: externalFilterData,173 tableProps: { externalFilterHandling: true, columnResizing: true },174} as const;175
Svelte
Copy
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme, TableFilterState } from "@simple-table/svelte";3 import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4 import "@simple-table/svelte/styles.css";56 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();78 let filters = $state<TableFilterState>({});910 let filteredRows = $derived.by(() => {11 const entries = Object.entries(filters);12 if (entries.length === 0) return externalFilterConfig.rows;1314 return externalFilterConfig.rows.filter((row) =>15 entries.every(([accessor, filter]) =>16 matchesFilter(row[accessor as keyof typeof row] as any, filter)17 )18 );19 });2021 function handleFilterChange(newFilters: TableFilterState) {22 filters = newFilters;23 }24</script>2526<SimpleTable27 columns={externalFilterConfig.headers}28 rows={filteredRows}29 externalFilterHandling={true}30 columnResizing={true}31 onFilterChange={handleFilterChange}32 {height}33 {theme}34/>
Solid TSX
Copy
1import { createSignal, createMemo } from "solid-js";2import {SimpleTable} from "@simple-table/solid";import type { Theme, TableFilterState } from "@simple-table/solid";3import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4import "@simple-table/solid/styles.css";56export default function ExternalFilterDemo(props: { height?: string | number; theme?: Theme }) {7 const [filters, setFilters] = createSignal<TableFilterState>({});89 const filteredData = createMemo(() => {10 const entries = Object.entries(filters());11 if (entries.length === 0) return externalFilterConfig.rows;1213 return externalFilterConfig.rows.filter((row) =>14 entries.every(([accessor, filter]) =>15 matchesFilter(row[accessor as keyof typeof row] as any, filter)16 )17 );18 });1920 return (21 <SimpleTable22 columns={externalFilterConfig.headers}23 rows={filteredData()}24 onFilterChange={setFilters}25 externalFilterHandling26 columnResizing27 height={props.height ?? "400px"}28 theme={props.theme}29 />30 );31}
TypeScriptExternalFilterDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, TableFilterState } from "simple-table-core";3import { externalFilterConfig, matchesFilter } from "./external-filter.demo-data";4import "simple-table-core/styles.css";56export function renderExternalFilterDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): SimpleTableVanilla {10 let currentFilters: TableFilterState = {};1112 const applyFilters = () => {13 const entries = Object.entries(currentFilters);14 if (entries.length === 0) {15 table.update({ rows: externalFilterConfig.rows });16 return;17 }18 const filtered = externalFilterConfig.rows.filter((row) =>19 entries.every(([accessor, filter]) =>20 matchesFilter(row[accessor as keyof typeof row] as any, filter)21 )22 );23 table.update({ rows: filtered });24 };2526 const table = new SimpleTableVanilla(container, {27 columns: externalFilterConfig.headers,28 rows: externalFilterConfig.rows,29 externalFilterHandling: true,30 columnResizing: true,31 height: options?.height ?? "400px",32 theme: options?.theme,33 onFilterChange: (newFilters: TableFilterState) => {34 currentFilters = newFilters;35 applyFilters();36 },37 });3839 return table;40}414243// external-filter.demo-data.ts44// Self-contained demo table setup for this example.45import type { ColumnDef, TableFilterState } from "simple-table-core";464748type CellValue = string | number | boolean | null | undefined;4950export function matchesFilter(51 value: CellValue,52 filter: TableFilterState[string]53): boolean {54 const { operator } = filter;5556 switch (operator) {57 case "equals":58 return value === filter.value;59 case "notEquals":60 return value !== filter.value;61 case "contains":62 return String(value).toLowerCase().includes(String(filter.value).toLowerCase());63 case "notContains":64 return !String(value).toLowerCase().includes(String(filter.value).toLowerCase());65 case "startsWith":66 return String(value).toLowerCase().startsWith(String(filter.value).toLowerCase());67 case "endsWith":68 return String(value).toLowerCase().endsWith(String(filter.value).toLowerCase());69 case "greaterThan":70 return Number(value) > Number(filter.value);71 case "lessThan":72 return Number(value) < Number(filter.value);73 case "greaterThanOrEqual":74 return Number(value) >= Number(filter.value);75 case "lessThanOrEqual":76 return Number(value) <= Number(filter.value);77 case "between":78 return (79 filter.values != null &&80 Number(value) >= Number(filter.values[0]) &&81 Number(value) <= Number(filter.values[1])82 );83 case "in":84 return filter.values != null && filter.values.includes(value);85 case "notIn":86 return filter.values != null && !filter.values.includes(value);87 case "isEmpty":88 return value == null || value === "";89 case "isNotEmpty":90 return value != null && value !== "";91 default:92 return true;93 }94}9596const DEPARTMENT_OPTIONS = [97 { label: "AI Research", value: "AI Research" },98 { label: "UX Design", value: "UX Design" },99 { label: "DevOps", value: "DevOps" },100 { label: "Marketing", value: "Marketing" },101 { label: "Engineering", value: "Engineering" },102 { label: "Product", value: "Product" },103 { label: "Sales", value: "Sales" },104];105106const LOCATION_OPTIONS = [107 { label: "San Francisco", value: "San Francisco" },108 { label: "Tokyo", value: "Tokyo" },109 { label: "Lagos", value: "Lagos" },110 { label: "Mexico City", value: "Mexico City" },111 { label: "Kolkata", value: "Kolkata" },112 { label: "Stockholm", value: "Stockholm" },113 { label: "Dubai", value: "Dubai" },114 { label: "Milan", value: "Milan" },115 { label: "Seoul", value: "Seoul" },116 { label: "Austin", value: "Austin" },117 { label: "London", value: "London" },118 { label: "Moscow", value: "Moscow" },119];120121export const externalFilterData = [122 { id: 1, name: "Dr. Elena Vasquez", age: 42, email: "elena.vasquez@techcorp.com", salary: 145000, department: "AI Research", active: true, location: "San Francisco" },123 { id: 2, name: "Kai Tanaka", age: 29, email: "k.tanaka@techcorp.com", salary: 95000, department: "UX Design", active: true, location: "Tokyo" },124 { id: 3, name: "Amara Okafor", age: 35, email: "amara.okafor@techcorp.com", salary: 125000, department: "DevOps", active: false, location: "Lagos" },125 { id: 4, name: "Santiago Rodriguez", age: 27, email: "s.rodriguez@techcorp.com", salary: 82000, department: "Marketing", active: true, location: "Mexico City" },126 { id: 5, name: "Priya Chakraborty", age: 33, email: "priya.c@techcorp.com", salary: 118000, department: "Engineering", active: true, location: "Kolkata" },127 { id: 6, name: "Magnus Eriksson", age: 38, email: "magnus.erik@techcorp.com", salary: 110000, department: "Product", active: false, location: "Stockholm" },128 { id: 7, name: "Zara Al-Rashid", age: 31, email: "zara.alrashid@techcorp.com", salary: 98000, department: "Sales", active: true, location: "Dubai" },129 { id: 8, name: "Luca Rossi", age: 26, email: "luca.rossi@techcorp.com", salary: 75000, department: "Marketing", active: true, location: "Milan" },130 { id: 9, name: "Dr. Sarah Kim", age: 45, email: "sarah.kim@techcorp.com", salary: 165000, department: "AI Research", active: true, location: "Seoul" },131 { id: 10, name: "Olumide Adebayo", age: 30, email: "olumide.a@techcorp.com", salary: 105000, department: "Engineering", active: false, location: "Austin" },132 { id: 11, name: "Isabella Chen", age: 24, email: "isabella.chen@techcorp.com", salary: 68000, department: "UX Design", active: true, location: "London" },133 { id: 12, name: "Dmitri Volkov", age: 39, email: "dmitri.volkov@techcorp.com", salary: 135000, department: "DevOps", active: true, location: "Moscow" },134];135136export const externalFilterHeaders: ColumnDef[] = [137 { accessor: "name", label: "Name", width: "1fr", minWidth: 120, filterable: true, type: "string" },138 { accessor: "age", label: "Age", width: 120, filterable: true, type: "number" },139 {140 accessor: "department",141 label: "Department",142 width: 150,143 filterable: true,144 type: "enum",145 enumOptions: DEPARTMENT_OPTIONS,146 },147 {148 accessor: "location",149 label: "Location",150 width: 150,151 filterable: true,152 type: "enum",153 enumOptions: LOCATION_OPTIONS,154 },155 { accessor: "active", label: "Active", width: 120, filterable: true, type: "boolean" },156 {157 accessor: "salary",158 label: "Salary",159 width: 120,160 filterable: true,161 type: "number",162 align: "right",163 valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,164 },165];166167export const externalFilterConfig = {168 headers: externalFilterHeaders,169 rows: externalFilterData,170 tableProps: { externalFilterHandling: true, columnResizing: true },171} as const;172
Props
Column Filtering Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
ColumnDef.filterableboolean | Optional | Enables the header filter control for the column. Operators depend on column type. | |
ColumnDef.filterOperatorsFilterOperator[] | Optional | Restricts which operators appear (in this order). Only valid for the column type. No effect on enum columns. | |
onFilterChange | Optional | Fires when active filters change. Keys are accessors; values are FilterCondition. | |
externalFilterHandlingboolean | Optional | Disables internal filtering. Provide already-filtered rows (e.g. from your API). |