#design system #frontend #UI

Owning the Steering Wheel: How We Rebuilt Our Design System Table

Owning the Steering Wheel: How We Rebuilt Our Design System Table
Photo by Aram Ramazyan on Unsplash

The table is one of the core components in our product, especially in a data‑heavy application, where multiple user flows depend on it being both performant and predictable.

Component history

The table has always been an evolving component in our library, going through several phases and heading in multiple directions over time.

Table design evolution

The first phase of this journey relied entirely on third‑party packages to satisfy our table needs. Two different packages were introduced, each serving a distinct purpose.

The first was built using react-data-table-component and served as the first reusable table component in our design system. Shortly after, another table was built using handsontable, with the sole purpose of providing a reusable way to create spreadsheet‑like experiences.

The second phase took us to the opposite end of the spectrum: building the component completely in‑house, from scratch.

The result of this iteration -Table (V1)- served us well for a couple of years. However, as the scale we operated at increased rapidly, more complex requirements began to surface, and the limitations of this approach became evident.

This ushered in the era of the enhanced Table (V2), built using the team’s accumulated experience and by leveraging newer tools and approaches from the design systems and component‑library ecosystem. This time, we adopted a hybrid approach, combining third‑party utilities with in‑house customization. We’ll dive into this in more detail later.

Motive: Why Rebuild?

As the product continued to scale, the existing component -DS/Table (V1) -started to hit its limits. Adding new features became risky and time‑consuming, and fixing bugs often turned into deep, time‑draining investigations.

At that point, this was no longer just a technical inconvenience, it was a scalability risk. The table was used extensively across the product, and the lack of confidence in such a critical component began to slow the team down.

Exploration and Alternatives

We began exploring different approaches and carefully weighing their trade‑offs. Given the complexity and scale of our requirements, choosing the wrong direction would have long‑term consequences, so this decision was made deliberately and collectively as a team.

Naturally, the options spanned the same spectrum we had already experienced: on one end, total dependency on third‑party libraries; on the other, a fully in‑house implementation. Neither extreme proved to be the right answer for us.

Total dependency on third‑party packages represented the phase of “just making it work.” At that stage, concerns such as bundle size, customization flexibility, design consistency, and long‑term maintainability were not top priorities. The focus was purely on utility.

As it became clear that the third‑party approach was too rigid and could not fully satisfy our needs, we were pushed toward the other extreme: building everything in‑house. This came with an entirely different scope and level of complexity. Creating such a component from scratch required covering all the foundational pieces of a reusable design‑system table.

We had to build our own state management, virtualization, sorting, and filtering logic. This led to a large and complex component structure, where we owned every piece of logic and were responsible for ensuring that all of it worked correctly together.

Eventually, we realized that we didn’t need to reinvent the wheel when it came to core logic and algorithms, but we did need to own the steering wheel when it came to UI and UX. This realization led us to adopt a hybrid approach: relying on a third‑party utility for the heavy lifting, while wrapping it with our own design‑system components.

Based on our experience with both extremes, we aligned on this approach and chose TanStack Table as our headless table utility.

Why TanStack Table?

The options in the headless table space are relatively limited. The TanStack ecosystem has emerged as a strong reference point in this area, with tools such as TanStack Query playing a significant role in shaping modern frontend development practices over the past few years.

Nevertheless, we took the time to thoroughly evaluate the utility before committing to such a significant change. We reviewed its feature set to ensure that our core use cases were covered out of the box, identified potential gaps that might require additional work, and assessed community adoption and real‑world production usage. We also reviewed the source code itself to validate its quality.

The tool checked all our boxes, and we decided it was time to rebuild our table.

Implementation and Design Decisions

Building a design-system component -especially one as complex as a table- is not just about implementation details or elegant abstractions. It’s about offering a clear, self-explanatory interface and a developer experience that enables progress instead of forcing repeated reconsideration of fundamental decisions.

Drawing from our previous experience, we knew we needed to strike a balance between a robust yet rigid component that solved our current use cases, and a flexible one that could evolve over time without introducing regressions.

The design was guided by three core principles: modularity, separation of concerns, and opt‑in features.

image.png
Component folder structure

Since the heavy logic was handled by TanStack Table, our focus shifted toward structuring the component in a way that aligned with these principles.

The 📁 components folder contains the complete collection of UI building blocks that make up the table. Developers can pick what suits their needs without worrying about inconsistent design or duplicated logic.

The 📁 types folder holds the TypeScript types required for the component to function with full type safety—both internally (for internal components) and externally (for consumers of the design system).

We decided not to export TanStack types directly. By wrapping them in custom types, we prevent "leaky abstractions," ensuring that if we ever replace the underlying utility, the external API remains stable.

The 📁 hooks folder contains the core logic hooks used to build the table, including:

  • Preparing the core configuration object required by TanStack Table.
  • Building the context used to share table‑specific values across the component tree.
  • Defining advanced custom filtering utilities.
  • Implementing a custom infinite‑scrolling mechanism.

Not all tables are created equal, and not every table is expected to support the same functionality. For that reason, we chose to leave feature composition to the consumer of the component. This is where the 📁 middleware folder comes in.

By design, the base table ships with no additional features such as sorting or filtering. Each feature is opt‑in and encapsulated in its own hook. Calling one of these hooks returns a partial configuration that augments the overall table configuration passed to TanStack Table. Developers can freely choose which features they need and pass them as an array-middleware: [sortingMW, colPinningMW, colVisMW, globalFilterMW]-to the hook responsible for assembling the final configuration.

image.png
Middleware hooks

Releasing Strategy

Creating a reliable and robust component was only part of the challenge. For a high‑impact component used across multiple applications-each relying on different variations-the question of how to release it to production without disrupting delivery or degrading the user experience became equally important.

Our solution was two‑fold: incremental rollout and gradual deprecation.

The first step was to mark Table V1 as deprecated using the @deprecated JSDoc tag, ensuring that it was clearly struck-through by editors and avoided in new usage. We then ensured that all new stories were built using Table V2.

After a few weeks in production, where the component faced real‑world usage and demand, we gained the confidence to begin phasing out the remaining legacy implementations, both third‑party and in‑house.

Result: Beyond the Code

The most meaningful outcome was a shift in how the team related to the table. Tasks that were once dreaded became routine, as confidence in the component grew.

With a more predictable foundation, feature delivery sped up and hesitation around changes faded. We no longer fight the component; we leverage it.

That shift removed friction and allowed the table to function as what a design-system component should be: a reliable primitive the team can build on with confidence.