While building my Todo page, I wanted to see if It’d be possible to parse the todo.txt spec and then render a table from it.

However, rendering anything in the DOM from plain JS can get super bulk and messy really quickly, so I thought I’d write a cleaner, reusable utility to render out tables.

I write all my scripts in Typescript, so first lets layout some basic data & types.

We’ve got a Column generic Type that contains information about our columns.

export interface Column<T> {
  header: string;
  accessor: keyof T;
  renderer?: (value: T[keyof T], row?: T) => string;

And then we have a basic footprint of our render function.

export const renderTable = <T>(args: {
  columns: Column<T>[];
  data: T[];
}): Element => {}

We’re going to pass in an array of Objects, with the object type here being T.

This function will be used accordingly:

import {renderTable} from "./render-table";

interface ExampleDataInterface {
  name: string;
  age: number;

const exampleData: ExampleDataInterface = [
  {name: "John Doe", age: 25, approved: false},
  {name: "Jane Doe", age: 26, approved: true}

const tableElement = renderTable<ExampleDataInterface>({
  columns: [
      {header: 'Legal Name', accessor: 'name'},
      {header: 'Users Age', accessor: 'age'},
          header: 'Approved',
          accessor: 'approved',
          renderer: (value) => value ? 'Yes' : 'No'
  data: exampleData

Inside our render function we first want to destructure our arguments and create our basic elements.

// Destructure our args into their own variables
const { columns, data, sortable } = args;

// Create the base table elements
const wrapper = document.createElement('div');

const table = document.createElement('table');
const header = document.createElement('thead');
const body = document.createElement('tbody');

// Connect the base table elements together

We now want to generate our header row where our Column titles will live.

// Create the header row elements and apply a className
const headerRow = document.createElement('tr');
headerRow.className = 'header';

// For our columns, add a th containing the header value
columns.forEach((cell) => {
  const columnHeader = document.createElement('th');
  columnHeader.innerText = cell.header;

// Append the header tr to the tables thead

Next we want to generate our data rows. We can do this by iterating over each item in our data array and then iterating over each column

data.forEach((cell) => {
  // Create a new row for this data
  const row = document.createElement('tr');
    // For each column in the table
    columns.forEach((parentColumn) => {
      // Create a new cell
      const newCell = document.createElement('td');
      // Set the cells inner text to either the output of the provided renderer, or the raw text
      newCell.innerText = `${
          ? parentColumn.renderer(cell[parentColumn.accessor], cell)
          : cell[parentColumn.accessor]
      // Append the cell to the newly created row
  // Append the row to the tables tbody

© 2024 Pfych