Skip to main content
Light Dark System

Table

<ol-table> | OlTable
Since 1.7 experimental

Table component.

<ol-table id="default-example" style="height: 280px;"></ol-table>

<script>
  // Data generation
  self.allData = new Array(1000).fill(0).map((_, index) => {
    return {
      id: index + 1,
      title: `Post #${index + 1}`,
      status: ['published', 'pending', 'draft'][Math.floor(Math.random() * 3)],
      visits: Math.ceil(Math.random() * 1000),
      createdAt: new Date(Date.now() + index * 47 * 60 * 60 * 1000)
    };
  });

  const table = document.querySelector('#default-example');
  table.columns =  [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'visits', size: 50 },
    { accessorKey: 'status', size: 50 },
    { accessorKey: 'createdAt' }
  ];
  table.data = self.allData.slice(0, 10);
</script>
import OlTable from '@onlive.site/ui/dist/react/table';

const App = () => (
  <>
    <OlTable
      columns={[
        { accessorKey: 'id', size: 50 },
        { accessorKey: 'title', size: 50 },
        { accessorKey: 'visits', size: 50 },
        { accessorKey: 'status', size: 50 },
        { accessorKey: 'createdAt' }
      ]}
      data={self.allData.slice(0, 10)}
    >
    </OlTable>
  </>
);

Examples

Column and Cell formatting

Customize the headers and cells.

<ol-table id="custom-header-cell-example" style="height: 280px;"></ol-table>

<script>
  const table = document.querySelector('#custom-header-cell-example');
  table.columns =  [
    {
      header: "ID",
      accessorKey: 'id',
      size: 50,
    },
    {
      header: "Title", // or header: () => html`<span>Title</span>`
      accessorKey: 'title',
      size: 50,
    },
    {
      header:  "Visits",
      accessorKey: 'visits',
      size: 50,
    },
    {
      header: 'Status',
      accessorKey: 'status',
      size: 50,
      cell: info => {
        const value = info.getValue();
        const badge = document.createElement('ol-badge');
        badge.pill = true;
        badge.textContent = value;

        switch(value) {
          case 'published':
            badge.variant = 'success';
            break;
          case 'pending':
            badge.variant = 'warning';
            break;
          case 'draft':
            badge.variant = 'neutral';
            break;
        }

        return badge;
      }
    },
    {
      header: 'Created At',
      accessorFn: row => row.createdAt.toLocaleString(),
    }
  ];
  table.data = self.allData.slice(0, 10);
</script>

Actions Column

Add an actions column with buttons for row-specific actions.

<ol-table id="actions-column-example" style="height: 280px;"></ol-table>

<script>
  const table = document.querySelector('#actions-column-example');
  table.columns = [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'status', size: 50 },
    {
      header: 'Actions',
      id: 'actions',
      size: 120,
      enableSorting: false,
      cell: info => {
        const row = info.row.original;
        const container = document.createElement('div');
        container.style.display = 'flex';
        container.style.gap = '8px';

        // Edit button
        const editBtn = document.createElement('ol-button');
        editBtn.size = 'small';
        editBtn.variant = 'default';
        editBtn.textContent = 'Edit';
        editBtn.addEventListener('click', () => {
          alert(`Edit row ${row.id}`);
        });

        // Delete button
        const deleteBtn = document.createElement('ol-button');
        deleteBtn.size = 'small';
        deleteBtn.variant = 'danger';
        deleteBtn.textContent = 'Delete';
        deleteBtn.addEventListener('click', () => {
          if (confirm(`Delete ${row.title}?`)) {
            alert(`Deleted row ${row.id}`);
          }
        });

        container.appendChild(editBtn);
        container.appendChild(deleteBtn);
        return container;
      }
    }
  ];
  table.data = self.allData.slice(0, 10);
</script>

Selection

Use the selectionMode property to set the selection mode to single or multiple.

Multiple selection

<ol-table id="multiple-selection-example" style="height: 280px;"></ol-table>
<p id="selected-rows-multiple"></p>

<script>
  const table = document.querySelector('#multiple-selection-example');
  table.selectionMode = "multiple";
  table.columns =  [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'visits', size: 50 },
    { accessorKey: 'status', size: 50 },
    { accessorKey: 'createdAt' }
  ];
  table.data = self.allData.slice(0, 10);

  table.addEventListener('ol-row-select', ({ detail }) => {
    document.querySelector("#selected-rows-multiple").textContent = `Selected rows: ${Object.keys(detail).join(', ')}`
  });
</script>

Single selection

<ol-table id="single-selection-example" style="height: 280px;"></ol-table>
<p id="selected-rows-single"></p>

<script>
  const table = document.querySelector('#single-selection-example');
  table.selectionMode = "single";
  table.columns =  [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'visits', size: 50 },
    { accessorKey: 'status', size: 50 },
    { accessorKey: 'createdAt' }
  ];
  table.data = self.allData.slice(0, 10);

  table.addEventListener('ol-row-select', ({ detail }) => {
    document.querySelector("#selected-rows-single").textContent = `Selected rows: ${Object.keys(detail).join(', ')}`
  });
</script>

Pagination

Client-side Pagination

<ol-table id="pagination-client-example" style="height: 280px;"></ol-table>

<script>
  const table = document.querySelector('#pagination-client-example');
  table.paginationMode = "client"; // By default is `client`
  table.pagination = {
    pageIndex: 0,
    pageSize: 10
  };
  table.columns =  [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'visits', size: 50 },
    { accessorKey: 'status', size: 50 },
    { accessorKey: 'createdAt' }
  ];
  table.data = self.allData;
</script>

Server-side Pagination

Use the paginationMode property to set the server-side pagination.

<ol-table id="pagination-server-example" style="height: 280px;"></ol-table>

<script>
  const fetchData = async (page, size) => {
    return new Promise((resolve) => setTimeout(() => resolve(self.allData.slice(page * size, (page + 1) * size)), 750));
  }

  const table = document.querySelector('#pagination-server-example');
  const pageSize = 10;

  table.columns =  [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'visits', size: 50 },
    { accessorKey: 'status', size: 50 },
    { accessorKey: 'createdAt' }
  ];
  table.paginationMode = 'server';
  table.pagination = {
    pageIndex: 0,
    pageSize,
    pageCount: Math.floor(self.allData.length / pageSize),
  };
  table.data = self.allData.slice(0, pageSize);
  table.addEventListener('ol-paginate', async ({ detail: { pageIndex, pageSize } }) => {
    table.loading = true;
    table.pagination = {
      pageIndex,
      pageSize,
      pageCount: Math.floor(self.allData.length / pageSize),
    };
    table.data = await fetchData(pageIndex, pageSize)
    table.loading = false;
  });
</script>

Infinite-scroll Pagination

Use the paginationType property to set the infinite scroll pagination.

<ol-table id="infinite-scroll-example" style="height: 280px;"></ol-table>

<script>
  const fetchData = async (page, size) => {
    return new Promise((resolve) => setTimeout(() => resolve(self.allData.slice(page * size, (page + 1) * size)), 750));
  }

  const table = document.querySelector('#infinite-scroll-example');
  const pageSize = 10;

  table.columns =  [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'visits', size: 50 },
    { accessorKey: 'status', size: 50 },
    { accessorKey: 'createdAt' }
  ];
  table.paginationMode = 'server';
  table.paginationType = 'infinite-scroll';
  table.pagination = {
    pageIndex: 0,
    pageSize,
    pageCount: Math.floor(self.allData.length / pageSize),
  };
  table.selectionMode = "multiple";
  table.data = self.allData.slice(0, pageSize);
  table.addEventListener('ol-paginate', async ({ detail: { pageIndex, pageSize } }) => {
    table.loading = true;
    table.pagination = {
      pageIndex,
      pageSize,
      pageCount: Math.floor(self.allData.length / pageSize),
    };
    table.data = [...table.data, ...(await fetchData(pageIndex, pageSize))];
    table.loading = false;
  });
</script>

Sorting

Use the sortingMode property to set the sorting mode to client or server. Defaults is server.

Client-side Sorting

<ol-table id="sorting-client-example" style="height: 280px;"></ol-table>

<script>
  // Custom sort function
  const sortStatusFn = (rowA, rowB) => {
    const statusA = rowA.original.status;
    const statusB = rowB.original.status;
    const statusOrder = ['published', 'pending', 'draft'];
    return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB);
  };

  const table = document.querySelector('#sorting-client-example');
  table.sortingMode = "client";
  table.sorting = [{
    id: 'visits',
    desc: true
  }];
  table.columns =  [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'visits', size: 50 },
    { accessorKey: 'status', size: 50, sortingFn: sortStatusFn },
    { accessorKey: 'createdAt' }
  ];
  table.data = self.allData.slice(0, 10);
</script>

Server-side Sorting

<ol-table id="sorting-server-example" style="height: 280px;"></ol-table>

<script>
  const fetchData = async (page, size, sortFields = []) => {
    return new Promise((resolve) => setTimeout(() => {
        if (sortFields[0]) {
          self.allData.sort((a, b) => {
            return (a[sortFields[0].id] > b[sortFields[0].id] ? 1 : -1) * (sortFields[0].desc ? -1 : 1);
          });
        }

        resolve(self.allData.slice(page * size, (page + 1) * size));
      }, 750)
    );
  }

  const table = document.querySelector('#sorting-server-example');
  table.sortingMode = "server";
  table.columns =  [
    { accessorKey: 'id', size: 50 },
    { accessorKey: 'title', size: 50 },
    { accessorKey: 'visits', size: 50 },
    { accessorKey: 'status', size: 50 },
    { accessorKey: 'createdAt' }
  ];
  table.data = self.allData.slice(0, 10);

  table.addEventListener('ol-column-sort', async ({ detail }) => {
    table.loading = true;
    table.data = await fetchData(0, 10, detail)
    table.loading = false;
  });
</script>

All features

<ol-table id="all-features-example" style="height: 350px;"></ol-table>
<p id="selected-rows-all-features"></p>

<script>
  const fetchData = async (page, size, sortFields = []) => {
    return new Promise((resolve) => setTimeout(() => {
        if (sortFields[0]) {
          self.allData.sort((a, b) => {
            return (a[sortFields[0].id] > b[sortFields[0].id] ? 1 : -1) * (sortFields[0].desc ? -1 : 1);
          });
        }

        resolve(self.allData.slice(page * size, (page + 1) * size));
      }, 750)
    );
  }

  const pageSize = 10;
  const table = document.querySelector('#all-features-example');
  table.selectionMode = "multiple";
  table.sortingMode = "server";
  table.paginationMode = 'server';
  table.pagination = {
    pageIndex: 0,
    pageSize,
    pageCount: Math.floor(self.allData.length / pageSize),
  };
  table.columns =  [
    {
      header: "ID",
      accessorKey: 'id',
      size: 50,
    },
    {
      header: "Title", // or header: () => html`<span>Title</span>`
      accessorKey: 'title',
      size: 50,
    },
    {
      header:  "Visits",
      accessorKey: 'visits',
      size: 50,
    },
    {
      header: 'Status',
      accessorKey: 'status',
      size: 50,
      cell: info => {
        const value = info.getValue();
        const badge = document.createElement('ol-badge');
        badge.pill = true;
        badge.textContent = value;

        switch(value) {
          case 'published':
            badge.variant = 'success';
            break;
          case 'pending':
            badge.variant = 'warning';
            break;
          case 'draft':
            badge.variant = 'neutral';
            break;
        }

        return badge;
      }
    },
    {
      header: 'Created At',
      accessorFn: row => row.createdAt.toLocaleString(),
    }
  ];
  table.data = self.allData.slice(0, pageSize);

  table.addEventListener('ol-paginate', async ({ detail: { pageIndex, pageSize } }) => {
    table.loading = true;
    table.pagination = {
      pageIndex,
      pageSize,
      pageCount: Math.floor(self.allData.length / pageSize),
    };
    table.data = await fetchData(pageIndex, pageSize)
    table.loading = false;
  });

  table.addEventListener('ol-row-select', ({ detail: rowSelection }) => {
    document.querySelector("#selected-rows-all-features").textContent = `Selected rows: ${Object.keys(rowSelection).join(', ')}`
  });

  table.addEventListener('ol-column-sort', async ({ detail: sortFields }) => {
    table.loading = true;
    table.data = await fetchData(0, pageSize, sortFields)
    table.loading = false;
  });
</script>
import OlTable from '@onlive.site/ui/dist/react/table';
import { createRoot } from "react-dom/client";
import { useState } from "react";
import { SortingState } from "@onlive.site/ui/dist/components/table/table.component.js";

const STATUS_COLORS = {
  published: "green",
  pending: "orange",
  draft: "gray",
  default: "blue",
} as const;

const StyledBadge = ({ value }: { value: string }) => (
  <div
    style={{
      backgroundColor: STATUS_COLORS[value as keyof typeof STATUS_COLORS] || STATUS_COLORS.default,
      padding: "4px 8px",
      borderRadius: "4px",
      color: "white",
    }}
  >
    {value}
  </div>
);

const getHtmlElement = (children: React.ReactNode) => {
  const element = document.createElement("div");
  const root = createRoot(element);

  root.render(children);

  return element;
};

const allData = new Array(1000).fill(0).map((_, index) => {
  return {
    id: index + 1,
    title: `Post #${index + 1}`,
    status: ["published", "pending", "draft"][Math.floor(Math.random() * 3)],
    visits: Math.ceil(Math.random() * 1000),
    createdAt: new Date(Date.now() + index * 47 * 60 * 60 * 1000),
  };
});

const App = () => {
  const [pageIndex, setPageIndex] = useState(0);
  const [pageSize, setPageSize] = useState(10);
  const [data, setData] = useState<typeof allData>(allData.slice(0, pageSize));
  const [loading, setLoading] = useState(false);
  const [SortingStates, setSortingStates] = useState<SortingState>([]);
  const [selectedRows, setSelectedRows] = useState<string[]>([]);

  const fetchData = async (page: number, size: number, SortingStates: SortingState = []) => {
    return new Promise<typeof allData>((resolve) =>
      setTimeout(() => {
        if (SortingStates[0]) {
          allData.sort((a, b) => {
            const key = SortingStates[0].id as keyof (typeof allData)[0];
            return (a[key] > b[key] ? 1 : -1) * (SortingStates[0].desc ? -1 : 1);
          });
        }

        resolve(allData.slice(page * size, (page + 1) * size));
      }, 750)
    );
  };

  return (
    <div>
      <main>
        <OlTable
          loading={loading}
          paginationMode="server"
          paginationType="pages"
          pagination={{
            pageIndex,
            pageSize,
            pageCount: Math.ceil(allData.length / pageSize),
          }}
          sortingMode="server"
          selectionMode="multiple"
          columns={[
            { accessorKey: "id", size: 50 },
            { accessorKey: "title", size: 50 },
            { accessorKey: "visits", size: 50 },
            {
              accessorKey: "status",
              cell: (info) => getHtmlElement(<StyledBadge value={info.getValue() as string} />),
            },
            { accessorKey: "createdAt" },
          ]}
          data={data}
          onOlPaginate={async ({ detail: { pageIndex, pageSize } }) => {
            setLoading(true);
            setPageIndex(pageIndex);
            setPageSize(pageSize);
            setData(await fetchData(pageIndex, pageSize, SortingStates));
            setLoading(false);
          }}
          onOlColumnSort={async ({ detail }) => {
            setLoading(true);
            setSortingStates(detail as SortingState);
            setData(await fetchData(pageIndex, 10, SortingStates as SortingState));
            setLoading(false);
          }}
          onOlRowSelect={({ detail: rows }) => {
            setSelectedRows(Object.keys(rows));
          }}
        ></OlTable>

        <p>Selected rows: {selectedRows.join(", ")}</p>
      </main>
    </div>
  );
}

Importing

If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

Script Import Bundler React

To import this component from the CDN using a script tag:

<script type="module" src="https://cdn.onlive.site/@onlive.site/ui@1.8.20/cdn/components/table/table.js"></script>

To import this component from the CDN using a JavaScript import:

import 'https://cdn.onlive.site/@onlive.site/ui@1.8.20/cdn/components/table/table.js';

To import this component using a bundler:

import '@onlive.site/ui/dist/components/table/table.js';

To import this component as a React component:

import OlTable from '@onlive.site/ui/dist/react/table';

Properties

Name Description Reflects Type Default
loading The loading state. boolean false
debug Enable table debugging for development purpose. boolean false
columns The array of column defs to use for the table. ColumnDef[] []
data The data for the table to display. Columns can access this data via string/index or a functional accessor. When the data option changes reference, the table will reprocess the data. unknown[] []
paginationMode
pagination-mode
The pagination mode. PaginationMode 'client'
paginationType
pagination-type
The pagination type. PaginationType 'pages'
pagination The pagination state. PaginationState -
selectionMode
selection-mode
The selection mode. SelectionMode -
sortingMode
sorting-mode
The sorting mode. SortingMode -
sorting The sorting state. SortingState []
sortingRemoval
sorting-removal
Use the sorting removal step. boolean true
rowSelection
row-selection
Use the sorting removal step. Record {}
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
ol-paginate onOlPaginate Emitted when the table is paginated. { pageIndex: number, pageSize: number }
ol-row-select onOlRowSelect Emitted when a row is selected. { rowSelection: Record<string, boolean> }
ol-column-sort onOlColumnSort Emitted when a column is sorted. { sortFields: { desc: boolean; id: string; }[] }

Learn more about events.

Custom Properties

Name Description Default
--ol-table-head-background-color Table head background color.
--ol-table-head-spacing Table head spacing.
--ol-table-cell-spacing Table cell spacing.
--ol-table-cell-line-height Table cell line height.
--ol-table-cell-font-size Table cell font size.

Learn more about customizing CSS custom properties.

Parts

Name Description
base The component’s base wrapper.
scroll The component’s scroll.
table The component’s table.
head The component’s table head.
body The component’s table body.
footer-actions The component’s table footer actions.

Learn more about customizing CSS parts.

Dependencies

This component automatically imports the following dependencies.

  • <ol-button>
  • <ol-checkbox>
  • <ol-icon>
  • <ol-icon-button>
  • <ol-option>
  • <ol-popup>
  • <ol-progress-bar>
  • <ol-select>
  • <ol-spinner>
  • <ol-tag>