MRT logoMantine React Table

On This Page

    Sorting Feature Guide

    Mantine React Table supports almost any sorting scenario you may have. Client-side sorting is enabled by default, but you can opt to implement your own server-side sorting logic, or even replace the default client-side sorting with your own implementation.

    Relevant Table Options

    1
    boolean
    true
    MRT Global Filtering Docs
    2
    boolean
    3
    boolean
    true
    4
    boolean
    true
    5
    (table: Table<TData>) => () => RowModel<TData>
    TanStack Table Sorting Docs
    6
    (e: unknown) => boolean
    TanStack Table Sorting Docs
    7
    boolean
    TanStack Table Sorting Docs
    8
    number
    TanStack Table Sorting Docs
    9
    OnChangeFn<SortingState>
    TanStack Table Sorting Docs
    10
    boolean
    TanStack Table Sorting Docs
    11
    Record<string, SortingFn>
    TanStack Table Sorting Docs

    Relevant Column Options

    1
    boolean
    true
    2
    boolean
    3
    boolean
    false
    4
    boolean
    5
    false | 1 | -1
    6
    SortingFnOption

    Relevant State Options

    1
    Array<{ id: string, desc: boolean }>
    []
    TanStack Table Sorting Docs

    Disable Sorting

    Sorting can be disabled globally by setting the enableSorting table option to false. This will disable sorting for all columns. You can also disable sorting for individual columns by setting the enableSorting column option to false.
    const columns = [
    {
    accessorKey: 'name',
    header: 'Name',
    enableSorting: false, // disable sorting for this column
    },
    ];
    const table = useMantineReactTable({
    columns,
    data,
    enableSorting: false, //disable sorting for all columns
    });
    return <MantineReactTable table={table} />;

    Initial/Default Sorting

    You can sort by a column or multiple columns by default by setting the sorting state option in either the initialState or state table options.
    const table = useMantineReactTable({
    columns,
    data,
    initialState: {
    sorting: [
    {
    id: 'age', //sort by age by default on page load
    desc: true,
    },
    {
    id: 'lastName', //then sort by lastName if age is the same
    desc: true,
    },
    ],
    },
    });

    Default Sorting Features

    Client-side sorting is enabled by default. When sorting is toggled on for a column, the table will be sorted by an alphanumeric sorting algorithm by default.

    Multi-Sorting

    Multi-sorting is also enabled by default, which means a user can sort by multiple columns at once. This can be accomplished by clicking on a column header while holding down the shift key. The table will then be sorted by the previously sorted column, and then by the newly clicked column. Or if you want multi-sorting to be the default click behavior without the need to hold shift, you can set the isMultiSortEvent table option to () => true.
    const table = useMantineReactTable({
    columns,
    data,
    isMultiSortEvent: () => true, //multi-sorting will be the default click behavior without the need to hold shift
    });
    You can limit the number of columns that can be sorted at once by setting the maxMultiSortColCount table option, or you can disable multi-sorting entirely by setting the enableMultiSort table option to false.

    Sorting Removal

    By default, users can remove a sort on a column by clicking through the sort direction options or selecting "Clear Sort" from the column actions menu. You can disable this feature by setting the enableSortingRemoval table option to false.
    const table = useMantineReactTable({
    columns,
    data,
    enableSortingRemoval: false, //users will not be able to remove a sort on a column
    });

    Sort Direction

    By default, columns with string datatypes will sort alphabetically in ascending order, but columns with number datatypes will sort numerically in descending order. You can change the default sort direction per column by specifying the sortDescFirst column option to either true or false. You can also change the default sort direction globally by setting the sortDescFirst table option to either true or false.
    VioletDoeSan FranciscoCalifornia100000
    MasonZhangSacramentoCalifornia100000
    LebronJamesIndianapolisIndiana40000
    JosephWilliamsValentineNebraska100000
    AllisonBrownOmahaNebraska10000
    HarrySmithHickmanNebraska20000
    SallyWilliamsonAllianceNebraska30000
    NoahBrownToledoOhio50000
    MichaelMcGinnisHarrisonburgVirginia150000
    1-9 of 9
    1
    import { MantineReactTable, type MRT_ColumnDef } from 'mantine-react-table';
    2
    import { data, type Person } from './makeData';
    3
    import { Button } from '@mantine/core';
    4
    5
    const columns: MRT_ColumnDef<Person>[] = [
    6
    {
    7
    accessorKey: 'firstName',
    8
    header: 'First Name',
    9
    sortDescFirst: false, //sort first name in ascending order by default on first sort click (default for non-numeric columns)
    10
    },
    11
    12
    {
    13
    accessorKey: 'lastName',
    14
    header: 'Last Name',
    15
    },
    16
    {
    17
    accessorKey: 'city',
    18
    header: 'City',
    19
    },
    20
    {
    21
    accessorKey: 'state',
    22
    header: 'State',
    23
    },
    24
    25
    {
    26
    accessorKey: 'salary',
    27
    header: 'Salary',
    28
    sortDescFirst: true, //sort salary in descending order by default on first sort click (default for numeric columns)
    29
    },
    30
    ];
    31
    32
    const Example = () => {
    33
    return (
    34
    <MantineReactTable
    35
    columns={columns}
    36
    data={data}
    37
    isMultiSortEvent={() => true} //now no need to hold `shift` key to multi-sort
    38
    maxMultiSortColCount={3} //prevent more than 3 columns from being sorted at once
    39
    initialState={{
    40
    sorting: [
    41
    { id: 'state', desc: false }, //sort by state in ascending order by default
    42
    { id: 'city', desc: true }, //then sort by city in descending order by default
    43
    ],
    44
    }}
    45
    renderTopToolbarCustomActions={({ table }) => (
    46
    <Button onClick={() => table.resetSorting(true)}>
    47
    Clear All Sorting
    48
    </Button>
    49
    )}
    50
    />
    51
    );
    52
    };
    53
    54
    export default Example;
    1
    import { MantineReactTable } from 'mantine-react-table';
    2
    import { data } from './makeData';
    3
    import { Button } from '@mantine/core';
    4
    5
    const columns = [
    6
    {
    7
    accessorKey: 'firstName',
    8
    header: 'First Name',
    9
    sortDescFirst: false, //sort first name in ascending order by default on first sort click (default for non-numeric columns)
    10
    },
    11
    12
    {
    13
    accessorKey: 'lastName',
    14
    header: 'Last Name',
    15
    },
    16
    {
    17
    accessorKey: 'city',
    18
    header: 'City',
    19
    },
    20
    {
    21
    accessorKey: 'state',
    22
    header: 'State',
    23
    },
    24
    25
    {
    26
    accessorKey: 'salary',
    27
    header: 'Salary',
    28
    sortDescFirst: true, //sort salary in descending order by default on first sort click (default for numeric columns)
    29
    },
    30
    ];
    31
    32
    const Example = () => {
    33
    return (
    34
    <MantineReactTable
    35
    columns={columns}
    36
    data={data}
    37
    isMultiSortEvent={() => true} //now no need to hold `shift` key to multi-sort
    38
    maxMultiSortColCount={3} //prevent more than 3 columns from being sorted at once
    39
    initialState={{
    40
    sorting: [
    41
    { id: 'state', desc: false }, //sort by state in ascending order by default
    42
    { id: 'city', desc: true }, //then sort by city in descending order by default
    43
    ],
    44
    }}
    45
    renderTopToolbarCustomActions={({ table }) => (
    46
    <Button onClick={() => table.resetSorting(true)}>
    47
    Clear All Sorting
    48
    </Button>
    49
    )}
    50
    />
    51
    );
    52
    };
    53
    54
    export default Example;

    Sorting Functions

    By default, Mantine React Table will use an alphanumeric sorting function for all columns.
    There are 6 built-in sorting functions that you can choose from: alphanumeric, alphanumericCaseSensitive, text, textCaseSensitive, datetime, and basic. You can learn more about these built-in sorting function in the TanStack Table Sorting API docs.

    Add Custom Sorting Functions

    If none of these sorting functions meet your needs, you can add your own custom sorting functions by specifying more sorting functions in the sortingFns table option.
    const table = useMantineReactTable({
    columns,
    data,
    sortingFns: {
    //will add a new sorting function to the list of other sorting functions already available
    myCustomSortingFn: (rowA, rowB, columnId) => // your custom sorting logic
    },
    });

    Change Sorting Function Per Column

    You can now choose a sorting function for each column by either passing a string value of the built-in sorting function names to the sortingFn column option, or by passing a custom sorting function to the sortingFn column option.
    const columns = [
    {
    accessorKey: 'name',
    header: 'Name',
    sortingFn: 'textCaseSensitive', //use the built-in textCaseSensitive sorting function instead of the default alphanumeric sorting function
    },
    {
    accessorKey: 'age',
    header: 'Age',
    //use your own custom sorting function instead of any of the built-in sorting functions
    sortingFn: (rowA, rowB, columnId) => // your custom sorting logic
    },
    ];

    Manual Server-Side Sorting

    If you are working with large data sets, you may want to let your back-end apis handle all of the sorting and pagination processing instead of doing it client-side. You can do this by setting the manualSorting table option to true. This will disable the default client-side sorting and pagination features, and will allow you to implement your own sorting and pagination logic.
    When manualSorting is set to true, Mantine React Table assumes that your data is already sorted by the time you are passing it to the table.
    If you need to sort your data in a back-end api, then you will also probably need access to the internal sorting state from the table. You can do this by managing the sorting state yourself, and then passing it to the table via the state table option. You can also pass a callback function to the onSortingChange table option, which will be called whenever the sorting state changes internally in the table
    const [sorting, setSorting] = useState([]);
    useEffect(() => {
    //do something with the sorting state when it changes
    }, [sorting]);
    const table = useMantineReactTable({
    columns,
    data,
    manualSorting: true,
    state: { sorting },
    onSortingChange: setSorting,
    });
    return <MantineReactTable table={table} />;

    Remote Sorting Example

    Here is the full Remote Data example showing how to implement server-side sorting, filtering, and pagination with Mantine React Table.
    No records to display
    0-0 of 0
    1
    import { useEffect, useMemo, useState } from 'react';
    2
    import {
    3
    MantineReactTable,
    4
    useMantineReactTable,
    5
    type MRT_ColumnDef,
    6
    type MRT_ColumnFiltersState,
    7
    type MRT_PaginationState,
    8
    type MRT_SortingState,
    9
    } from 'mantine-react-table';
    10
    11
    type UserApiResponse = {
    12
    data: Array<User>;
    13
    meta: {
    14
    totalRowCount: number;
    15
    };
    16
    };
    17
    18
    type User = {
    19
    firstName: string;
    20
    lastName: string;
    21
    address: string;
    22
    state: string;
    23
    phoneNumber: string;
    24
    };
    25
    26
    const Example = () => {
    27
    //data and fetching state
    28
    const [data, setData] = useState<User[]>([]);
    29
    const [isError, setIsError] = useState(false);
    30
    const [isLoading, setIsLoading] = useState(false);
    31
    const [isRefetching, setIsRefetching] = useState(false);
    32
    const [rowCount, setRowCount] = useState(0);
    33
    34
    //table state
    35
    const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(
    36
    [],
    37
    );
    38
    const [globalFilter, setGlobalFilter] = useState('');
    39
    const [sorting, setSorting] = useState<MRT_SortingState>([]);
    40
    const [pagination, setPagination] = useState<MRT_PaginationState>({
    41
    pageIndex: 0,
    42
    pageSize: 10,
    43
    });
    44
    45
    //if you want to avoid useEffect, look at the React Query example instead
    46
    useEffect(() => {
    47
    const fetchData = async () => {
    48
    if (!data.length) {
    49
    setIsLoading(true);
    50
    } else {
    51
    setIsRefetching(true);
    52
    }
    53
    54
    const url = new URL(
    55
    '/api/data',
    56
    process.env.NODE_ENV === 'production'
    57
    ? 'https://www.mantine-react-table.com'
    58
    : 'http://localhost:3001',
    59
    );
    60
    url.searchParams.set(
    61
    'start',
    62
    `${pagination.pageIndex * pagination.pageSize}`,
    63
    );
    64
    url.searchParams.set('size', `${pagination.pageSize}`);
    65
    url.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
    66
    url.searchParams.set('globalFilter', globalFilter ?? '');
    67
    url.searchParams.set('sorting', JSON.stringify(sorting ?? []));
    68
    69
    try {
    70
    const response = await fetch(url.href);
    71
    const json = (await response.json()) as UserApiResponse;
    72
    setData(json.data);
    73
    setRowCount(json.meta.totalRowCount);
    74
    } catch (error) {
    75
    setIsError(true);
    76
    console.error(error);
    77
    return;
    78
    }
    79
    setIsError(false);
    80
    setIsLoading(false);
    81
    setIsRefetching(false);
    82
    };
    83
    fetchData();
    84
    // eslint-disable-next-line react-hooks/exhaustive-deps
    85
    }, [
    86
    columnFilters, //refetch when column filters change
    87
    globalFilter, //refetch when global filter changes
    88
    pagination.pageIndex, //refetch when page index changes
    89
    pagination.pageSize, //refetch when page size changes
    90
    sorting, //refetch when sorting changes
    91
    ]);
    92
    93
    const columns = useMemo<MRT_ColumnDef<User>[]>(
    94
    () => [
    95
    {
    96
    accessorKey: 'firstName',
    97
    header: 'First Name',
    98
    },
    99
    100
    {
    101
    accessorKey: 'lastName',
    102
    header: 'Last Name',
    103
    },
    104
    {
    105
    accessorKey: 'address',
    106
    header: 'Address',
    107
    },
    108
    {
    109
    accessorKey: 'state',
    110
    header: 'State',
    111
    },
    112
    {
    113
    accessorKey: 'phoneNumber',
    114
    header: 'Phone Number',
    115
    },
    116
    ],
    117
    [],
    118
    );
    119
    120
    const table = useMantineReactTable({
    121
    columns,
    122
    data,
    123
    enableRowSelection: true,
    124
    getRowId: (row) => row.phoneNumber,
    125
    initialState: { showColumnFilters: true },
    126
    manualFiltering: true,
    127
    manualPagination: true,
    128
    manualSorting: true,
    129
    rowCount,
    130
    onColumnFiltersChange: setColumnFilters,
    131
    onGlobalFilterChange: setGlobalFilter,
    132
    onPaginationChange: setPagination,
    133
    onSortingChange: setSorting,
    134
    state: {
    135
    columnFilters,
    136
    globalFilter,
    137
    isLoading,
    138
    pagination,
    139
    showAlertBanner: isError,
    140
    showProgressBars: isRefetching,
    141
    sorting,
    142
    },
    143
    mantineToolbarAlertBannerProps: isError
    144
    ? { color: 'red', children: 'Error loading data' }
    145
    : undefined,
    146
    });
    147
    148
    return <MantineReactTable table={table} />;
    149
    };
    150
    151
    export default Example;
    1
    import { useEffect, useMemo, useState } from 'react';
    2
    import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
    3
    4
    const Example = () => {
    5
    //data and fetching state
    6
    const [data, setData] = useState([]);
    7
    const [isError, setIsError] = useState(false);
    8
    const [isLoading, setIsLoading] = useState(false);
    9
    const [isRefetching, setIsRefetching] = useState(false);
    10
    const [rowCount, setRowCount] = useState(0);
    11
    12
    //table state
    13
    const [columnFilters, setColumnFilters] = useState([]);
    14
    const [globalFilter, setGlobalFilter] = useState('');
    15
    const [sorting, setSorting] = useState([]);
    16
    const [pagination, setPagination] = useState({
    17
    pageIndex: 0,
    18
    pageSize: 10,
    19
    });
    20
    21
    //if you want to avoid useEffect, look at the React Query example instead
    22
    useEffect(() => {
    23
    const fetchData = async () => {
    24
    if (!data.length) {
    25
    setIsLoading(true);
    26
    } else {
    27
    setIsRefetching(true);
    28
    }
    29
    30
    const url = new URL(
    31
    '/api/data',
    32
    process.env.NODE_ENV === 'production'
    33
    ? 'https://www.mantine-react-table.com'
    34
    : 'http://localhost:3001',
    35
    );
    36
    url.searchParams.set(
    37
    'start',
    38
    `${pagination.pageIndex * pagination.pageSize}`,
    39
    );
    40
    url.searchParams.set('size', `${pagination.pageSize}`);
    41
    url.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
    42
    url.searchParams.set('globalFilter', globalFilter ?? '');
    43
    url.searchParams.set('sorting', JSON.stringify(sorting ?? []));
    44
    45
    try {
    46
    const response = await fetch(url.href);
    47
    const json = await response.json();
    48
    setData(json.data);
    49
    setRowCount(json.meta.totalRowCount);
    50
    } catch (error) {
    51
    setIsError(true);
    52
    console.error(error);
    53
    return;
    54
    }
    55
    setIsError(false);
    56
    setIsLoading(false);
    57
    setIsRefetching(false);
    58
    };
    59
    fetchData();
    60
    // eslint-disable-next-line react-hooks/exhaustive-deps
    61
    }, [
    62
    columnFilters, //refetch when column filters change
    63
    globalFilter, //refetch when global filter changes
    64
    pagination.pageIndex, //refetch when page index changes
    65
    pagination.pageSize, //refetch when page size changes
    66
    sorting, //refetch when sorting changes
    67
    ]);
    68
    69
    const columns = useMemo(
    70
    () => [
    71
    {
    72
    accessorKey: 'firstName',
    73
    header: 'First Name',
    74
    },
    75
    76
    {
    77
    accessorKey: 'lastName',
    78
    header: 'Last Name',
    79
    },
    80
    {
    81
    accessorKey: 'address',
    82
    header: 'Address',
    83
    },
    84
    {
    85
    accessorKey: 'state',
    86
    header: 'State',
    87
    },
    88
    {
    89
    accessorKey: 'phoneNumber',
    90
    header: 'Phone Number',
    91
    },
    92
    ],
    93
    [],
    94
    );
    95
    96
    const table = useMantineReactTable({
    97
    columns,
    98
    data,
    99
    enableRowSelection: true,
    100
    getRowId: (row) => row.phoneNumber,
    101
    initialState: { showColumnFilters: true },
    102
    manualFiltering: true,
    103
    manualPagination: true,
    104
    manualSorting: true,
    105
    rowCount,
    106
    onColumnFiltersChange: setColumnFilters,
    107
    onGlobalFilterChange: setGlobalFilter,
    108
    onPaginationChange: setPagination,
    109
    onSortingChange: setSorting,
    110
    state: {
    111
    columnFilters,
    112
    globalFilter,
    113
    isLoading,
    114
    pagination,
    115
    showAlertBanner: isError,
    116
    showProgressBars: isRefetching,
    117
    sorting,
    118
    },
    119
    mantineToolbarAlertBannerProps: isError
    120
    ? { color: 'red', children: 'Error loading data' }
    121
    : undefined,
    122
    });
    123
    124
    return <MantineReactTable table={table} />;
    125
    };
    126
    127
    export default Example;
    1
    import {
    2
    type MRT_ColumnFiltersState,
    3
    type MRT_SortingState,
    4
    } from 'mantine-react-table';
    5
    import { type NextApiRequest, type NextApiResponse } from 'next';
    6
    import { getData } from './mock';
    7
    8
    //This is just a simple mock of a backend API where you would do server-side pagination, filtering, and sorting
    9
    //You would most likely want way more validation and error handling than this in a real world application
    10
    //Also most of this logic should actually be in the database query itself, but this is just a mock
    11
    export default function handler(req: NextApiRequest, res: NextApiResponse) {
    12
    let dbData = getData();
    13
    const { start, size, filters, filterModes, sorting, globalFilter } =
    14
    req.query as Record<string, string>;
    15
    16
    const parsedFilterModes = JSON.parse(filterModes ?? '{}') as Record<
    17
    string,
    18
    string
    19
    >;
    20
    21
    const parsedColumnFilters = JSON.parse(filters) as MRT_ColumnFiltersState;
    22
    if (parsedColumnFilters?.length) {
    23
    parsedColumnFilters.map((filter) => {
    24
    const { id: columnId, value: filterValue } = filter;
    25
    const filterMode = parsedFilterModes?.[columnId] ?? 'contains';
    26
    dbData = dbData.filter((row) => {
    27
    const rowValue = row[columnId]?.toString()?.toLowerCase();
    28
    if (filterMode === 'contains') {
    29
    return rowValue.includes?.((filterValue as string).toLowerCase());
    30
    } else if (filterMode === 'startsWith') {
    31
    return rowValue.startsWith?.((filterValue as string).toLowerCase());
    32
    } else if (filterMode === 'endsWith') {
    33
    return rowValue.endsWith?.((filterValue as string).toLowerCase());
    34
    }
    35
    });
    36
    });
    37
    }
    38
    39
    if (globalFilter) {
    40
    dbData = dbData.filter((row) =>
    41
    Object.keys(row).some(
    42
    (columnId) =>
    43
    row[columnId]
    44
    ?.toString()
    45
    ?.toLowerCase()
    46
    ?.includes?.((globalFilter as string).toLowerCase()),
    47
    ),
    48
    );
    49
    }
    50
    51
    const parsedSorting = JSON.parse(sorting) as MRT_SortingState;
    52
    if (parsedSorting?.length) {
    53
    const sort = parsedSorting[0];
    54
    const { id, desc } = sort;
    55
    dbData.sort((a, b) => {
    56
    if (desc) {
    57
    return a[id] < b[id] ? 1 : -1;
    58
    }
    59
    return a[id] > b[id] ? 1 : -1;
    60
    });
    61
    }
    62
    63
    res.status(200).json({
    64
    data:
    65
    dbData?.slice(parseInt(start), parseInt(start) + parseInt(size)) ?? [],
    66
    meta: { totalRowCount: dbData.length },
    67
    });
    68
    }
    View Extra Storybook Examples
    You can help make these docs better! PRs are Welcome
    Using Material-UI instead of Mantine?
    Check out Material React Table