# Element UI Table Sorting, Filtering, and Pagination: A Complete Implementation Guide

> Master Element UI table sorting filtering and pagination. Learn how to implement these advanced features for efficient data management in your application.

- Repository: [饿了么前端/element](https://github.com/ElemeFE/element)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Element UI tables implement sorting via the `handleSortClick` method in [`table-header.js`](https://github.com/ElemeFE/element/blob/main/table-header.js), column filtering through lazy-loaded `FilterPanel` instances, and pagination as an independent component emitting `size-change` and `current-change` events for data slicing.**

The ElemeFE/element repository provides a sophisticated Table component architecture that cleanly separates layout rendering from state management. By configuring the `sortable` and `filters` properties on `el-table-column` components and wiring the standalone `el-pagination` events to your data logic, you create responsive data grids that handle large datasets efficiently.

## How Table Sorting Works

Column sorting in Element UI is handled internally by the table store, triggered from the header rendering logic. In [`packages/table/src/table-header.js`](https://github.com/ElemeFE/element/blob/main/packages/table/src/table-header.js), each column configured with `sortable` renders two caret icons (`<i class="sort-caret …">`) that capture click events.

When a user clicks a sort trigger, the `handleSortClick` method toggles the column's `order` property between `ascending`, `descending`, and `null`. This commits a `changeSortCondition` mutation to the table store, which updates `sortProp` and `sortOrder` states and reorders the row data before rendering.

To enable sorting, add the `sortable` prop to any column definition:

```vue
<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="Date" sortable width="180" />
    <el-table-column prop="name" label="Name" />
  </el-table>
</template>

<script>
export default {
  data() {
    return {
      tableData: [
        { date: '2024-01-01', name: 'Alice' },
        { date: '2024-01-02', name: 'Bob' }
      ]
    };
  }
};
</script>

```

The table automatically handles string and number comparisons. For custom sorting logic, use the `sort-method` prop on the column to define a comparator function.

## Implementing Column Filtering

Filtering utilizes a dynamic `FilterPanel` component that instantiates when users interact with the filter trigger. In [`packages/table/src/table-header.js`](https://github.com/ElemeFE/element/blob/main/packages/table/src/table-header.js), columns marked with `filters` render a clickable `el-table__column-filter-trigger` element.

Clicking this trigger lazily loads a `FilterPanel` Vue instance displaying a popover with checkboxes for each item in the column's `filters` array (an array of `{ text, value }` objects). When selections change, the panel invokes the store's `filterChange` mutation, updating `column.filteredValue` and recomputing the displayed rows.

You must provide a `filter-method` function that determines whether a row should display for a given filter value:

```vue
<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column
      prop="tag"
      label="Tag"
      :filters="[{ text: 'Home', value: 'Home' }, { text: 'Office', value: 'Office' }]"
      :filter-method="filterTag"
      filter-placement="bottom-end">
      <template slot-scope="scope">
        <el-tag :type="scope.row.tag === 'Home' ? 'primary' : 'success'">
          {{ scope.row.tag }}
        </el-tag>
      </template>
    </el-table-column>
  </el-table>
</template>

<script>
export default {
  data() {
    return {
      tableData: [
        { tag: 'Home', address: '123 Main St' },
        { tag: 'Office', address: '456 Corporate Blvd' }
      ]
    };
  },
  methods: {
    filterTag(value, row) {
      return row.tag === value;
    }
  }
};
</script>

```

The `filter-placement` prop controls the popover positioning, accepting any valid Popper.js placement value.

## Configuring Table Pagination

Unlike sorting and filtering, pagination is not built into the Table component. Instead, [`packages/pagination/src/pagination.js`](https://github.com/ElemeFE/element/blob/main/packages/pagination/src/pagination.js) provides a standalone `el-pagination` component that emits events for you to slice your data source manually.

The pagination component accepts a `layout` prop defining which elements to display (e.g., `"total, sizes, prev, pager, next, jumper"`). It emits `size-change` when the user selects a new page size and `current-change` when navigating between pages.

For client-side pagination, maintain the full dataset in your component state and compute a sliced view:

```vue
<template>
  <div>
    <el-table :data="pagedData" style="width: 100%">
      <el-table-column prop="date" label="Date" />
      <el-table-column prop="name" label="Name" />
    </el-table>
    
    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page.sync="currentPage"
      :page-size="pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :page-sizes="[5, 10, 20, 50]"
      :total="tableData.length">
    </el-pagination>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tableData: [ /* full dataset */ ],
      currentPage: 1,
      pageSize: 10
    };
  },
  computed: {
    pagedData() {
      const start = (this.currentPage - 1) * this.pageSize;
      return this.tableData.slice(start, start + this.pageSize);
    }
  },
  methods: {
    handleSizeChange(size) {
      this.pageSize = size;
      this.currentPage = 1;
    },
    handleCurrentChange(page) {
      this.currentPage = page;
    }
  }
};
</script>

```

For server-side pagination, replace the `pagedData` computed property with an API call triggered by the pagination events.

## Complete Data Grid Implementation

Combining all three features requires careful sequencing: the Table component handles sorting automatically, but you must account for filtered row counts when configuring pagination. Access the table store's `states.columns` to retrieve active filter values for accurate total calculations.

```vue
<template>
  <div>
    <el-table 
      ref="dataTable"
      :data="pagedData" 
      style="width: 100%">
      <el-table-column prop="date" label="Date" sortable width="150" />
      <el-table-column
        prop="tag"
        label="Tag"
        :filters="tagFilters"
        :filter-method="filterTag"
        filter-placement="bottom-end">
        <template slot-scope="scope">
          <el-tag>{{ scope.row.tag }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="address" label="Address" />
    </el-table>

    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page.sync="currentPage"
      :page-size="pageSize"
      layout="total, sizes, prev, pager, next"
      :page-sizes="[5, 10, 20]"
      :total="filteredCount">
    </el-pagination>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tableData: [ /* full dataset */ ],
      currentPage: 1,
      pageSize: 10,
      tagFilters: [
        { text: 'Home', value: 'Home' },
        { text: 'Office', value: 'Office' }
      ]
    };
  },
  computed: {
    filteredCount() {
      // Access the table store to respect active filters in pagination total
      const table = this.$refs.dataTable;
      if (!table) return this.tableData.length;
      const tagColumn = table.store.states.columns.find(c => c.property === 'tag');
      if (!tagColumn || !tagColumn.filteredValue || !tagColumn.filteredValue.length) {
        return this.tableData.length;
      }
      return this.tableData.filter(row => tagColumn.filteredValue.includes(row.tag)).length;
    },
    filteredData() {
      const table = this.$refs.dataTable;
      if (!table) return this.tableData;
      const tagColumn = table.store.states.columns.find(c => c.property === 'tag');
      if (!tagColumn || !tagColumn.filteredValue || !tagColumn.filteredValue.length) {
        return this.tableData;
      }
      return this.tableData.filter(row => tagColumn.filteredValue.includes(row.tag));
    },
    pagedData() {
      const start = (this.currentPage - 1) * this.pageSize;
      return this.filteredData.slice(start, start + this.pageSize);
    }
  },
  methods: {
    filterTag(value, row) {
      return row.tag === value;
    },
    handleSizeChange(size) {
      this.pageSize = size;
      this.currentPage = 1;
    },
    handleCurrentChange(page) {
      this.currentPage = page;
    }
  }
};
</script>

```

This implementation mirrors the official examples in [`examples/docs/en-US/table.md`](https://github.com/ElemeFE/element/blob/main/examples/docs/en-US/table.md) and [`examples/docs/en-US/pagination.md`](https://github.com/ElemeFE/element/blob/main/examples/docs/en-US/pagination.md), ensuring compatibility with the Element UI store architecture.

## Summary

- **Sorting** relies on [`packages/table/src/table-header.js`](https://github.com/ElemeFE/element/blob/main/packages/table/src/table-header.js) rendering sort carets that trigger `handleSortClick`, committing `changeSortCondition` mutations to reorder rows automatically.
- **Filtering** uses lazy-loaded `FilterPanel` instances to update `column.filteredValue` via the `filterChange` store mutation, requiring a custom `filter-method` for matching logic.
- **Pagination** operates independently in [`packages/pagination/src/pagination.js`](https://github.com/ElemeFE/element/blob/main/packages/pagination/src/pagination.js), emitting `size-change` and `current-change` events that you handle to slice the data array bound to the table.
- For combined grids, reference the table store's `states.columns` to synchronize pagination totals with active filter states.

## Frequently Asked Questions

### How do I enable default sorting on table load?

Set the `default-sort` prop on the `el-table` component to an object specifying the column `prop` and initial `order` (either `ascending` or `descending`). For example: `:default-sort="{ prop: 'date', order: 'descending' }"`. The table store initializes `sortProp` and `sortOrder` from this value before the first render.

### Can I use server-side sorting and filtering instead of client-side?

Yes. Remove the `sortable` and `filters` props from columns to disable UI handlers, then implement custom header slots with clickable elements. Trigger your API calls within these handlers and update the `data` prop bound to the table with the server-response. Alternatively, set `sortable="custom"` and listen for the `sort-change` event to trigger remote requests while keeping the UI indicators.

### Why does my pagination total not update when filters are applied?

The `el-pagination` component does not observe the table's internal filter state. You must manually calculate the filtered row count by accessing `this.$refs.tableName.store.states.columns` to check `filteredValue` arrays, or maintain a separate filtered dataset computed property that feeds both the table `:data` and pagination `:total` props.

### How do I customize the sort icons in the table header?

Override the CSS classes `.sort-caret` and its modifiers `.ascending`/`.descending` in your stylesheet. The icons are rendered as `<i>` elements inside [`packages/table/src/table-header.js`](https://github.com/ElemeFE/element/blob/main/packages/table/src/table-header.js), allowing you to replace the default caret symbols with custom icons or SVG backgrounds through scoped CSS or deep selectors.