How to Use the Astryx Table Component with Column Settings and Data Virtualization

Build interactive, high-performance tables in Astryx by composing the built-in column settings plugin with third-party virtualization libraries through the Table's plugin architecture.

The Astryx design system provides a composable Table component designed for extensibility through plugin hooks. For complex data applications, two critical capabilities are column settings (runtime column visibility, reordering, and grouping) and data virtualization (rendering only visible rows for large datasets). Both integrate seamlessly via the plugins prop and render-prop APIs in facebook/astryx.

Understanding Astryx Table Plugin Architecture

The Table component in packages/core/src/Table/Table.tsx accepts a plugins object that intercepts and transforms rendering at key lifecycle points. This architecture keeps core Table code lean while enabling powerful extensions without internal modifications.

Key design principles from the source:

  • Plugins are pure functions that receive table state and return transformed state or JSX
  • State hooks are separate from plugin factoriesuseTableColumnSettingsState manages state, useTableColumnSettings creates the plugin
  • Render props like renderBody allow complete override of table regions for virtualization

Column Settings: Hide, Reorder, and Group Columns

Column settings in Astryx are implemented as a first-class plugin. The implementation spans two files in packages/core/src/Table/plugins/columnSettings/:

Setting Up Column Settings State

The useTableColumnSettingsState hook initializes and manages column configuration:

import {useTableColumnSettingsState} from '@astryxdesign/core/Table/plugins/columnSettings';

const columnState = useTableColumnSettingsState({
  columns: allColumns,
  activeColumnKeys: ['id', 'name', 'email'],
  onChangeActiveColumnKeys: (keys) => console.log('active columns →', keys),
  columnGroups: [
    {group: 'Info', keys: ['id', 'name']},
    {group: 'Contact', keys: ['email']},
  ],
});

The returned columnState object includes helpers for building UI controls:

Property Purpose
toggleColumn(key) Show/hide a column
isColumnVisible(key) Check visibility state
reset() Restore initial configuration
columnSettingsConfig Object passed to plugin factory

Creating and Applying the Plugin

Feed the state configuration into the plugin factory, then pass to Table:

import {Table} from '@astryxdesign/core';
import {useTableColumnSettings} from '@astryxdesign/core/Table/plugins/columnSettings';

const columnSettingsPlugin = useTableColumnSettings(columnState.columnSettingsConfig);

<Table
  columns={allColumns}
  data={data}
  plugins={{columnSettings: columnSettingsPlugin}}
/>

The plugin automatically:

  • Filters out columns not in activeColumnKeys
  • Orders columns according to activeColumnKeys array sequence
  • Preserves grouping metadata for header rendering

Data Virtualization: Rendering Large Datasets

Astryx does not ship a dedicated virtualization plugin in core, but packages/core/src/Table/BaseTable.tsx explicitly reserves extension points for virtualization with comments referencing "sticky shadows, virtualization." The renderBody prop provides the integration mechanism.

Integration with react-virtual

Use any virtualization library via renderBody. This example uses @tanstack/react-virtual:

import {Table} from '@astryxdesign/core';
import {useVirtual} from '@tanstack/react-virtual';
import {useRef} from 'react';

function VirtualizedTable({columns, data}) {
  const parentRef = useRef(null);
  
  const rowVirtualizer = useVirtual({
    size: data.length,
    parentRef,
    estimateSize: () => 40,
    overscan: 5,
  });

  return (
    <Table
      columns={columns}
      renderBody={() => (
        <div ref={parentRef} style={{overflow: 'auto', height: '400px'}}>
          <div style={{height: rowVirtualizer.totalSize, position: 'relative'}}>
            {rowVirtualizer.virtualItems.map((virtualRow) => (
              <div
                key={virtualRow.index}
                style={{
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  width: '100%',
                  transform: `translateY(${virtualRow.start}px)`,
                }}
              >
                <Table.Row row={data[virtualRow.index]} />
              </div>
            ))}
          </div>
        </div>
      )}
    />
  );
}

Critical integration points:

  • Do not pass data directly to Table when virtualizing—render only the visible slice
  • Reuse Table.Row from packages/core/src/Table/TableRow.tsx to maintain consistent cell rendering with column settings
  • Measure container height and set estimateSize based on your actual row height

Combining Column Settings with Virtualization

Both features compose naturally since they operate on different rendering layers:

<Table
  columns={allColumns}
  plugins={{columnSettings: columnSettingsPlugin}}
  renderBody={() => {
    // Virtualization renders only visible rows
    // Column settings plugin already filtered/reordered columns
    const visibleColumns = columnState.activeColumnKeys
      .map(key => allColumns.find(col => col.key === key))
      .filter(Boolean);
    
    return (
      <div ref={parentRef} style={{overflow: 'auto', height: '400px'}}>
        <div style={{height: rowVirtualizer.totalSize, position: 'relative'}}>
          {rowVirtualizer.virtualItems.map((virtualRow) => (
            <div
              key={virtualRow.index}
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                transform: `translateY(${virtualRow.start}px)`,
              }}
            >
              <Table.Row
                row={data[virtualRow.index]}
                columns={visibleColumns}
              />
            </div>
          ))}
        </div>
      </div>
    );
  }}
/>

The column settings plugin processes columns before virtualization, so virtual rows render only the active subset.

File Reference

File Path Purpose
Table.tsx packages/core/src/Table/Table.tsx Core component with plugins and renderBody props
BaseTable.tsx packages/core/src/Table/BaseTable.tsx Low-level logic with virtualization extension points
useTableColumnSettings.tsx packages/core/src/Table/plugins/columnSettings/useTableColumnSettings.tsx Plugin factory for column filtering/reordering
useTableColumnSettingsState.tsx packages/core/src/Table/plugins/columnSettings/useTableColumnSettingsState.tsx State management hook
TableRow.tsx packages/core/src/Table/TableRow.tsx Row rendering component for custom implementations

Summary

  • Column settings are built-in: Combine useTableColumnSettingsState with useTableColumnSettings, then pass via plugins={{columnSettings: ...}}
  • Virtualization requires integration: Use renderBody to override table body rendering with any virtual-scroll library
  • Composition is explicit: State hooks, plugin factories, and render props are separate concerns, making behavior predictable and testable
  • Internal components are reusable: Table.Row ensures cell rendering stays consistent with Astryx styling when building custom virtualized bodies

Frequently Asked Questions

Does Astryx Table include a built-in virtualization plugin?

No. The core package in facebook/astryx does not ship a virtualization plugin, but BaseTable.tsx contains explicit extension points and the renderBody prop enables integration with any virtual-scroll library. This design choice keeps bundle size minimal for users who don't need virtualization.

How does the column settings plugin affect row data?

It doesn't. The column settings plugin operates solely on the columns array, filtering and reordering before headers and cells render. Row data flows through unchanged, which means virtualization and other row-level transformations remain unaffected.

Can I persist column settings across sessions?

Yes. The useTableColumnSettingsState hook accepts activeColumnKeys and onChangeActiveColumnKeys props. Save activeColumnKeys to localStorage or a backend, then rehydrate on mount. The hook's reset() method restores the initial configuration when needed.

What happens if activeColumnKeys includes a key not in columns?

The plugin silently ignores invalid keys. Only columns present in both the columns array and activeColumnKeys are rendered. This defensive behavior prevents crashes when column schemas evolve independently of saved preferences.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →