Custom Footer Renderers: Why Full Control Beats Feature Flags in React Tables

UI ControlDeveloper ExperienceCustomization

The footer isn't an afterthought—it's where users navigate, understand their data, and control what they see. Yet most React table libraries trap you between rigid flags and rebuilding from scratch.

Picture this: You're building a data-heavy dashboard. Everything's going smooth—rows load fast, columns sort perfectly, filters feel snappy. Then comes the footer. Your designer hands you a mockup with custom pagination buttons, row counters with specific copy, and maybe a "rows per page" selector styled to match your brand.

You crack open your table library's docs. Option 1: A dozen boolean flags scattered across props—showPagination, paginationPosition, paginationStyle. You toggle them all, but the result? Close, but not quite right. The spacing's off, the icons don't match, and that "Showing X-Y of Z" text is hardcoded.

Option 2 (looking at you, TanStack): Rebuild the entire table UI from scratch, including headers, cells, and yes—footers. Sure, you get total control, but now you're maintaining layout logic, accessibility, and responsive behavior yourself. That "lightweight headless library" suddenly feels heavyweight.

There's a better way: custom footer renderers. One prop. Full control. Zero compromises.

The Problem: Footers Are Personal

Table footers aren't generic. They're where your brand shows through, where your users interact with data navigation, and where you communicate important information. Every app has different needs:

Design Requirements

Your pagination needs to match your design system—specific colors, spacing, icons, animations, and hover states that scream "this is our app."

Feature Combinations

Maybe you need row counts AND page numbers AND a "jump to page" input AND an export button. Good luck finding flags for that exact combo.

Custom Interactions

Want to track analytics when users change pages? Show loading states? Display contextual help? These scenarios fall outside the flag-based model entirely.

Accessibility Needs

Screen reader announcements, keyboard navigation patterns, ARIA labels—these require direct access to the DOM and behavior, not just styling flags.

Two Approaches to Footer Customization

When it comes to customizing table footers, libraries typically offer one of two paths. Let's break down the trade-offs:

Approach 1: The Feature Flag Maze

This is the "configure your way there" approach. The library provides dozens of boolean props and configuration options:

React TSX
1<DataTable
2 showPagination={true}
3 paginationPosition="bottom"
4 paginationAlign="right"
5 showPageNumbers={true}
6 showRowCount={true}
7 rowCountFormat="Showing {start}-{end} of {total}"
8 paginationSize="large"
9 showFirstLastButtons={true}
10 showPreviousNextButtons={true}
11 pageNumbersToShow={5}
12 // ... and 20 more pagination props
13/>

The Good

  • Quick starts: Toggle a few flags and you've got basic pagination working
  • Consistent styling: The library handles the basic look and feel
  • Less code: No need to write your own pagination logic

The Bad

  • Limited combinations: Want that one specific layout? If it's not in the flags, you're stuck
  • Style surgery: Overriding the default styles often requires !important wars and deep CSS selectors
  • Prop explosion: More features = more flags = more complexity in your component

Approach 2: The TanStack Way (Build Everything)

TanStack Table takes a "headless" approach—it provides the logic and state management, but you build all the UI yourself, including the entire table structure:

React TSX
1import { useReactTable, getCoreRowModel, getPaginationRowModel } from '@tanstack/react-table'
2
3function MyTable() {
4 const table = useReactTable({
5 data,
6 columns,
7 getCoreRowModel: getCoreRowModel(),
8 getPaginationRowModel: getPaginationRowModel(),
9 })
10
11 return (
12 <>
13 {/* Build entire table structure */}
14 <table>
15 <thead>
16 {table.getHeaderGroups().map(headerGroup => (
17 <tr key={headerGroup.id}>
18 {headerGroup.headers.map(header => (
19 <th key={header.id}>
20 {/* Custom header rendering */}
21 </th>
22 ))}
23 </tr>
24 ))}
25 </thead>
26 <tbody>
27 {/* Custom body rendering */}
28 </tbody>
29 </table>
30
31 {/* Build your own footer from scratch */}
32 <div className="pagination">
33 <button
34 onClick={() => table.previousPage()}
35 disabled={!table.getCanPreviousPage()}
36 >
37 Previous
38 </button>
39 <span>
40 Page {table.getState().pagination.pageIndex + 1} of{' '}
41 {table.getPageCount()}
42 </span>
43 <button
44 onClick={() => table.nextPage()}
45 disabled={!table.getCanNextPage()}
46 >
47 Next
48 </button>
49 </div>
50 </>
51 )
52}

The Good

  • Ultimate control: Every pixel is yours to command
  • Framework agnostic: Use any styling solution, any component library
  • Zero style conflicts: No library CSS to override

The Bad

  • Build EVERYTHING: Table structure, headers, rows, cells, footer—all on you
  • Complexity creep: Simple tables turn into hundreds of lines of UI code
  • Maintenance burden: You're now maintaining table layout, accessibility, responsive behavior, and more

The reality: TanStack is powerful, but overkill for most apps. You wanted to customize the footer, not rebuild the entire table from scratch.

The Solution: Custom Footer Renderers

Enter the footer renderer pattern—the sweet spot between flags and full control. Here's how it works in Simple Table:

React TSX
1<SimpleTable
2 defaultHeaders={headers}
3 rows={data}
4 rowIdAccessor="id"
5 shouldPaginate={true}
6 rowsPerPage={10}
7 footerRenderer={({
8 currentPage,
9 totalPages,
10 startRow,
11 endRow,
12 totalRows,
13 hasPrevPage,
14 hasNextPage,
15 onPrevPage,
16 onNextPage,
17 onPageChange,
18 }) => (
19 <div className="custom-footer">
20 {/* Build exactly the footer YOU want */}
21 <div className="row-info">
22 Showing {startRow}-{endRow} of {totalRows} items
23 </div>
24
25 <div className="pagination-controls">
26 <button
27 onClick={onPrevPage}
28 disabled={!hasPrevPage}
29 className="nav-button"
30 >
31 ← Previous
32 </button>
33
34 {/* Render custom page numbers */}
35 {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
36 <button
37 key={page}
38 onClick={() => onPageChange(page)}
39 className={currentPage === page ? 'active' : ''}
40 >
41 {page}
42 </button>
43 ))}
44
45 <button
46 onClick={onNextPage}
47 disabled={!hasNextPage}
48 className="nav-button"
49 >
50 Next →
51 </button>
52 </div>
53 </div>
54 )}
55/>

Why This Approach Wins

One Prop

No flag maze. Just pass a render function to footerRenderer and you're done.

Full Control

Build any footer design you want. Custom buttons, dropdowns, analytics—it's all just JSX.

Logic Handled

Simple Table manages pagination state. You just render the UI with the data it provides.

Style Freedom

Use CSS-in-JS, Tailwind, CSS Modules, or plain CSS. Your footer, your rules.

Easy Testing

Footer logic is just a React component. Test it like any other component.

Scope Focused

Unlike TanStack, you only customize what you need—the table itself still "just works."

Real-World Footer Scenarios

Let's look at some scenarios where custom footer renderers shine:

📊 Analytics Dashboard

Need: Show "Viewing X-Y of Z results" with an Export button and a "Rows per page" dropdown.

React TSX
1footerRenderer={({ startRow, endRow, totalRows, ...props }) => (
2 <div style={{ display: 'flex', justifyContent: 'space-between', padding: '16px' }}>
3 <span>Viewing {startRow}-{endRow} of {totalRows} results</span>
4 <div>
5 <select onChange={handleRowsPerPageChange}>
6 <option value="10">10 per page</option>
7 <option value="25">25 per page</option>
8 <option value="50">50 per page</option>
9 </select>
10 <button onClick={handleExport}>Export CSV</button>
11 </div>
12 </div>
13)}

🛒 E-Commerce Admin

Need: Compact pagination with tooltips and accessibility labels.

React TSX
1footerRenderer={({ currentPage, totalPages, onPageChange, ...props }) => (
2 <nav aria-label="Product pagination">
3 {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
4 <button
5 key={page}
6 onClick={() => onPageChange(page)}
7 aria-current={currentPage === page ? 'page' : undefined}
8 aria-label={`Go to page ${page}`}
9 title={`Page ${page} of ${totalPages}`}
10 >
11 {page}
12 </button>
13 ))}
14 </nav>
15)}

📱 Mobile-First App

Need: Stacked layout on mobile, horizontal on desktop. Show "Load More" instead of page numbers.

React TSX
1footerRenderer={({ hasNextPage, onNextPage, startRow, endRow, totalRows }) => (
2 <div className="footer-mobile-responsive">
3 <div className="row-count">
4 {startRow}-{endRow} of {totalRows}
5 </div>
6 {hasNextPage && (
7 <button onClick={onNextPage} className="load-more">
8 Load More Results
9 </button>
10 )}
11 </div>
12)}
13
14// CSS
15.footer-mobile-responsive {
16 display: flex;
17 flex-direction: column;
18 gap: 12px;
19}
20
21@media (min-width: 768px) {
22 .footer-mobile-responsive {
23 flex-direction: row;
24 justify-content: space-between;
25 }
26}

The Verdict: Why Footer Renderers Win

Let's put all three approaches side-by-side:

CriterionFeature FlagsTanStack (Build All)Footer Renderer
Setup Time⚡ Fast🐌 Slow⚡ Fast
Customization❌ Limited✅ Unlimited✅ Unlimited
Code Complexity📄 Low📚 Very High📄 Low-Medium
Maintenance⚠️ Fight with flags⚠️ Own everything✅ Just the footer
Table Features✅ Built-in❌ Build yourself✅ Built-in
Best ForSimple needs onlyTotal control freaksMost real-world apps

Getting Started with Footer Renderers

Ready to try footer renderers? Here's a complete working example:

React TSX
1import { SimpleTable } from "simple-table-core";
2import "simple-table-core/styles.css";
3
4const headers = [
5 { accessor: "id", label: "ID", width: 60 },
6 { accessor: "name", label: "Name", width: "1fr" },
7 { accessor: "email", label: "Email", width: "1fr" },
8];
9
10const MyDataTable = ({ data }) => {
11 return (
12 <SimpleTable
13 defaultHeaders={headers}
14 rows={data}
15 rowIdAccessor="id"
16 shouldPaginate={true}
17 rowsPerPage={10}
18 footerRenderer={({
19 currentPage,
20 totalPages,
21 startRow,
22 endRow,
23 totalRows,
24 hasPrevPage,
25 hasNextPage,
26 onPrevPage,
27 onNextPage,
28 onPageChange,
29 }) => (
30 <div style={{
31 display: 'flex',
32 alignItems: 'center',
33 justifyContent: 'space-between',
34 padding: '16px',
35 borderTop: '1px solid #e5e7eb',
36 }}>
37 {/* Row counter */}
38 <span style={{ fontSize: '14px', color: '#6b7280' }}>
39 Showing {startRow}-{endRow} of {totalRows} results
40 </span>
41
42 {/* Pagination controls */}
43 <div style={{ display: 'flex', gap: '8px' }}>
44 <button
45 onClick={onPrevPage}
46 disabled={!hasPrevPage}
47 style={{
48 padding: '8px 16px',
49 border: '1px solid #d1d5db',
50 borderRadius: '6px',
51 background: hasPrevPage ? 'white' : '#f3f4f6',
52 cursor: hasPrevPage ? 'pointer' : 'not-allowed',
53 }}
54 >
55 Previous
56 </button>
57
58 {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
59 <button
60 key={page}
61 onClick={() => onPageChange(page)}
62 style={{
63 padding: '8px 12px',
64 border: '1px solid #d1d5db',
65 borderRadius: '6px',
66 background: currentPage === page ? '#3b82f6' : 'white',
67 color: currentPage === page ? 'white' : '#374151',
68 cursor: 'pointer',
69 fontWeight: currentPage === page ? '600' : '400',
70 }}
71 >
72 {page}
73 </button>
74 ))}
75
76 <button
77 onClick={onNextPage}
78 disabled={!hasNextPage}
79 style={{
80 padding: '8px 16px',
81 border: '1px solid #d1d5db',
82 borderRadius: '6px',
83 background: hasNextPage ? 'white' : '#f3f4f6',
84 cursor: hasNextPage ? 'pointer' : 'not-allowed',
85 }}
86 >
87 Next
88 </button>
89 </div>
90 </div>
91 )}
92 />
93 );
94};

That's it! Simple Table handles all the pagination logic, state management, and data slicing. You just focus on rendering the UI you want.

The Bottom Line

Building tables doesn't have to be a compromise between "limited customization" and "rebuild everything from scratch." Custom footer renderers give you the best of both worlds:

  • Full design freedom without fighting with flags or overriding styles
  • Minimal code complexity compared to building tables from scratch
  • All table features included (sorting, filtering, selection, etc.)
  • Easy to maintain because you only own the footer, not the whole table

Footer UI is personal. It's where your brand shows, where your users navigate, and where design requirements get specific. Don't settle for close enough—take full control without the overhead.

Try it yourself: Simple Table's footer renderer gives you the flexibility you need with none of the complexity you don't. One prop. Total control. Zero compromises.

Ready to build tables with complete footer control?

Simple Table's footer renderer pattern combines the ease of use you expect with the customization power you need. Join developers who've ditched the flag maze and avoided TanStack complexity. Experience the perfect balance of control and simplicity.