React data table

Table, Bro is the Custom Element ‹table-bro›, so a React 19 app renders it in JSX as it is, without a wrapper package. A ref gives the element its columns, rows and config by configure(), an effect adds the listener of its bro-action events, and the theme prop sets its theme. The page below is the working page of the sample project samples/frameworks/react of the repository, with React 19.3; StackBlitz opens it in the browser.

Loading
Items per page:

How do I install Table, Bro in React?

  1. Install the packages

    The table engine @table-bro/core and a skin, @table-bro/native of plain HTML elements; @table-bro/bootstrap with bootstrap gives the skin of Bootstrap 5:

    npm install @table-bro/core @table-bro/native
    
  2. Register the element and the skin

    Once, before the tables: the import of the skin defines ‹table-bro› and registers the skin nv. The CSS of the core and of the skin come as imports, which Vite and the other bundlers take. src/skin.ts:

    // The CSS of the core and of the skin, then the skin: its import defines <table-bro> and registers the skin nv
    import '@table-bro/core/dist/table-bro.min.css';
    import '@table-bro/native/dist/table-bro.native.min.css';
    import '@table-bro/native';
    
    import './sample-page.css';
    
  3. Describe the columns, the rows and the config

    conf.skin names the registered skin. src/orders.ts:

    import type {BroCol, BroConfig, BroRow} from '@table-bro/core';
    
    export interface Order extends BroRow {
      description: string;
      price: number;
    }
    
    export const cols: BroCol<Order>[] = [
      {
        title: 'Description',
        name: 'description'
      }, {
        title: 'Price',
        name: 'price',
        dataType: 'number',
        className: 'bro-col_text-right'
      }
    ];
    
    export const rows: Order[] = [
      {
        description: 'Modern Cotton Chair',
        price: 100
      }, {
        description: 'Awesome Concrete Ball',
        price: 200
      }
    ];
    
    export const conf: BroConfig = {
      skin: 'nv',
      toolbarTitle: 'Orders'
    };
    

How do I pass cols, rows and conf?

The columns, the rows and the config go to the table in one call, configure({cols, rows, conf}), once the element is in the document: a ref gives the element, useEffect makes the call after the first render. The same effect adds the listener of bro-action and removes it at the end; the sample logs the name of each action. src/OrdersTable.tsx:

import {useEffect, useRef} from 'react';

import type {BroActionDetail, TableBroHTMLElement} from '@table-bro/core';

import {cols, conf, rows} from './orders';

export function OrdersTable() {
  const table = useRef<TableBroHTMLElement>(null);

  useEffect(() => {
    const element = table.current!;
    const onAction = (event: CustomEvent<BroActionDetail>) => console.log(event.detail.action);

    element.configure({cols, rows, conf});
    element.addEventListener('bro-action', onAction);

    return () => element.removeEventListener('bro-action', onAction);
  }, []);

  return (
    <div className="bro-table-shadow-container">
      <table-bro id="orders" ref={table} theme="auto"></table-bro>
    </div>
  );
}

React 19 sets a prop of a custom element as a property when the element has a property of that name, and as an attribute otherwise (React 19, Support for Custom Elements): theme is a property of ‹table-bro›. "jsx": "react-jsx" in tsconfig.json of the sample gives the JSX runtime of React to TypeScript and to Vite.

How does TypeScript know the element?

JSX of React knows the HTML elements only; a declaration adds ‹table-bro› with the methods of TableBroHTMLElement. src/elements.d.ts:

// The element <table-bro> in the JSX of React, with its attribute theme
import type {DetailedHTMLProps, HTMLAttributes} from 'react';

import type {TableBroHTMLElement} from '@table-bro/core';

declare module 'react' {
  namespace JSX {
    interface IntrinsicElements {
      'table-bro': DetailedHTMLProps<HTMLAttributes<TableBroHTMLElement>, TableBroHTMLElement> & {theme?: 'light' | 'dark' | 'auto'};
    }
  }
}

How do I render it?

src/main.tsx imports the skin before the tables:

// The skin is registered before the tables
import './skin';

import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';

import {OrdersTable} from './OrdersTable';

createRoot(document.getElementById('app')!).render(
  <StrictMode>
    <OrdersTable />
  </StrictMode>
);

How do I switch the theme?

The theme attribute or property of the table is 'light', 'dark' or 'auto', which follows the color scheme of the system; the attribute wins over config.theme. The sample sets 'auto', as a table without the attribute does; a variable of the state bound to it switches the theme, and the table puts the class bro-dark-theme or bro-light-theme on itself. More in Dark/Light mode.

Questions

Does React 19 need a wrapper for Custom Elements?

No. React 19 passes the props of a custom element as properties or attributes (React, Custom HTML elements) and passes all 32 tests of Custom Elements Everywhere. Table, Bro takes its data by configure() through a ref, which works in every version of React.

Can I listen to bro-action without a ref?

The sample uses addEventListener in an effect: it works in React 18 and 19 and removes the listener on unmount.

Does it work with Next.js?

The table renders in the browser: configure() runs in an effect, so a client component ('use client') works, and the server render leaves the element empty.

Related

Getting started, Custom content in tables using Custom Elements, MCP server for AI coding agents, Angular data table, Preact data table, Solid data table, Svelte data table, Vue 3 data table.

DJ