Skip to content
Gx
GitHub

Table

A data table built from composable parts.

<table.Table>
  <table.TableCaption>A list of invoices.</table.TableCaption>
  <table.TableHeader>
    <table.TableRow>
      <table.TableHead>Invoice</table.TableHead>
      <table.TableHead>Status</table.TableHead>
      <table.TableHead>Amount</table.TableHead>
    </table.TableRow>
  </table.TableHeader>
  <table.TableBody>
    <table.TableRow>
      <table.TableCell>INV-001</table.TableCell>
      <table.TableCell>Paid</table.TableCell>
      <table.TableCell>$120.00</table.TableCell>
    </table.TableRow>
    <table.TableRow>
      <table.TableCell>INV-002</table.TableCell>
      <table.TableCell>Open</table.TableCell>
      <table.TableCell>$80.00</table.TableCell>
    </table.TableRow>
  </table.TableBody>
</table.Table>

Installation

Run the command in the app module.

Terminal
gx add table

The command writes these files.

File Path in the app
Table.gx ui/table/Table.gx
TableHeader.gx ui/table/TableHeader.gx
TableBody.gx ui/table/TableBody.gx
TableFooter.gx ui/table/TableFooter.gx
TableRow.gx ui/table/TableRow.gx
TableHead.gx ui/table/TableHead.gx
TableCell.gx ui/table/TableCell.gx
TableCaption.gx ui/table/TableCaption.gx
Table.fixtures.go ui/table/Table.fixtures.go
TableBody.fixtures.go ui/table/TableBody.fixtures.go
TableCaption.fixtures.go ui/table/TableCaption.fixtures.go
TableCell.fixtures.go ui/table/TableCell.fixtures.go
TableFooter.fixtures.go ui/table/TableFooter.fixtures.go
TableHead.fixtures.go ui/table/TableHead.fixtures.go
TableHeader.fixtures.go ui/table/TableHeader.fixtures.go
TableRow.fixtures.go ui/table/TableRow.fixtures.go

Copy each file to its path in the app. Change each import of a registry package to the path of that package in the app.

ui/table/Table.gx
package table

props {
  // Class adds classes to the table element.
  Class    string = ""
  // Children is the content of the table: the caption, header, body and footer.
  Children gx.Node
  // Attrs adds HTML attributes to the table element.
  Attrs    gx.Attrs = nil
}

<div data-slot="table-container" class="relative w-full overflow-x-auto">
  <table data-slot="table" class={gx.Cx("w-full caption-bottom text-sm", p.Class)} {...p.Attrs}>{p.Children}</table>
</div>
ui/table/TableHeader.gx
package table

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the rows of the header.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<thead data-slot="table-header" class={gx.Cx("[&_tr]:border-b", p.Class)} {...p.Attrs}>{p.Children}</thead>
ui/table/TableBody.gx
package table

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the rows of the body.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<tbody data-slot="table-body" class={gx.Cx("[&_tr:last-child]:border-0", p.Class)} {...p.Attrs}>{p.Children}</tbody>
ui/table/TableFooter.gx
package table

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the rows of the footer.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<tfoot data-slot="table-footer" class={gx.Cx("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", p.Class)} {...p.Attrs}>{p.Children}</tfoot>
ui/table/TableRow.gx
package table

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the cells of the row.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<tr data-slot="table-row" class={gx.Cx("border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted", p.Class)} {...p.Attrs}>{p.Children}</tr>
ui/table/TableHead.gx
package table

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the header cell.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<th data-slot="table-head" class={gx.Cx("h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has(:is([role=checkbox],input[type=checkbox],label:has(>input[type=checkbox])))]:pr-0 [&>:is([role=checkbox],input[type=checkbox],label:has(>input[type=checkbox]))]:translate-y-[2px]", p.Class)} {...p.Attrs}>{p.Children}</th>
ui/table/TableCell.gx
package table

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the content of the cell.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<td data-slot="table-cell" class={gx.Cx("p-2 align-middle whitespace-nowrap [&:has(:is([role=checkbox],input[type=checkbox],label:has(>input[type=checkbox])))]:pr-0 [&>:is([role=checkbox],input[type=checkbox],label:has(>input[type=checkbox]))]:translate-y-[2px]", p.Class)} {...p.Attrs}>{p.Children}</td>
ui/table/TableCaption.gx
package table

props {
  // Class adds classes to the root element.
  Class    string = ""
  // Children is the caption text.
  Children gx.Node
  // Attrs adds HTML attributes to the root element.
  Attrs    gx.Attrs = nil
}

<caption data-slot="table-caption" class={gx.Cx("mt-4 text-sm text-muted-foreground", p.Class)} {...p.Attrs}>{p.Children}</caption>
ui/table/Table.fixtures.go
package table

import "github.com/alternayte/gx"

// invoiceRows are the body rows of the table fixtures.
func invoiceRows() gx.Node {
	return gx.Frag(
		TableRow(TableRowProps{Children: gx.Frag(
			TableCell(TableCellProps{Children: gx.Text("INV-001")}),
			TableCell(TableCellProps{Children: gx.Text("Paid")}),
			TableCell(TableCellProps{Children: gx.Text("$120.00")}),
		)}),
		TableRow(TableRowProps{Children: gx.Frag(
			TableCell(TableCellProps{Children: gx.Text("INV-002")}),
			TableCell(TableCellProps{Children: gx.Text("Open")}),
			TableCell(TableCellProps{Children: gx.Text("$80.00")}),
		)}),
	)
}

// invoiceHead is the header row of the table fixtures.
func invoiceHead() gx.Node {
	return TableHeader(TableHeaderProps{Children: TableRow(TableRowProps{Children: gx.Frag(
		TableHead(TableHeadProps{Children: gx.Text("Invoice")}),
		TableHead(TableHeadProps{Children: gx.Text("Status")}),
		TableHead(TableHeadProps{Children: gx.Text("Amount")}),
	)})})
}

var TableFixtures = gx.Fixtures[TableProps]{
	"Simple": {Children: gx.Frag(
		TableCaption(TableCaptionProps{Children: gx.Text("A list of invoices.")}),
		invoiceHead(),
		TableBody(TableBodyProps{Children: invoiceRows()}),
	)},
	"Footer": {Children: gx.Frag(
		invoiceHead(),
		TableBody(TableBodyProps{Children: invoiceRows()}),
		TableFooter(TableFooterProps{Children: TableRow(TableRowProps{Children: gx.Frag(
			TableCell(TableCellProps{Attrs: gx.Attrs{{Key: "colspan", Value: "2"}}, Children: gx.Text("Total")}),
			TableCell(TableCellProps{Children: gx.Text("$200.00")}),
		)})}),
	)},
	"Selected": {Children: gx.Frag(
		invoiceHead(),
		TableBody(TableBodyProps{Children: gx.Frag(
			TableRow(TableRowProps{Attrs: gx.Attrs{{Key: "data-state", Value: "selected"}}, Children: gx.Frag(
				TableCell(TableCellProps{Children: gx.Text("INV-001")}),
				TableCell(TableCellProps{Children: gx.Text("Paid")}),
				TableCell(TableCellProps{Children: gx.Text("$120.00")}),
			)}),
			TableRow(TableRowProps{Children: gx.Frag(
				TableCell(TableCellProps{Children: gx.Text("INV-002")}),
				TableCell(TableCellProps{Children: gx.Text("Open")}),
				TableCell(TableCellProps{Children: gx.Text("$80.00")}),
			)}),
		)}),
	)},
}
ui/table/TableBody.fixtures.go
package table

import "github.com/alternayte/gx"

var TableBodyFixtures = gx.Fixtures[TableBodyProps]{"Empty": {}}
ui/table/TableCaption.fixtures.go
package table

import "github.com/alternayte/gx"

var TableCaptionFixtures = gx.Fixtures[TableCaptionProps]{"Caption": {Children: gx.Text("A caption.")}}
ui/table/TableCell.fixtures.go
package table

import "github.com/alternayte/gx"

var TableCellFixtures = gx.Fixtures[TableCellProps]{"Cell": {Children: gx.Text("Value")}}
ui/table/TableFooter.fixtures.go
package table

import "github.com/alternayte/gx"

var TableFooterFixtures = gx.Fixtures[TableFooterProps]{"Empty": {}}
ui/table/TableHead.fixtures.go
package table

import "github.com/alternayte/gx"

var TableHeadFixtures = gx.Fixtures[TableHeadProps]{"Header": {Children: gx.Text("Column")}}
ui/table/TableHeader.fixtures.go
package table

import "github.com/alternayte/gx"

var TableHeaderFixtures = gx.Fixtures[TableHeaderProps]{"Empty": {}}
ui/table/TableRow.fixtures.go
package table

import "github.com/alternayte/gx"

var TableRowFixtures = gx.Fixtures[TableRowProps]{"Empty": {}}

The theme must define these tokens: --border, --muted, --muted-foreground, --foreground.

Usage

<table.Table>
  <table.TableHeader>
    <table.TableRow>
      <table.TableHead>Invoice</table.TableHead>
      <table.TableHead>Status</table.TableHead>
    </table.TableRow>
  </table.TableHeader>
  <table.TableBody>
    <table.TableRow>
      <table.TableCell>INV-001</table.TableCell>
      <table.TableCell>Paid</table.TableCell>
    </table.TableRow>
  </table.TableBody>
</table.Table>

For sorting, filtering and paging over many rows, use the data table component.

TableFooter holds the total rows. Set data-state="selected" on a TableRow to mark it. A row that holds an expanded control takes the hover colour. Pass class="text-right" to a TableHead and its cells for a number column.

Examples

Table: Simple

<table.Table>
  <table.TableCaption>A list of invoices.</table.TableCaption>
  <table.TableHeader>
    <table.TableRow>
      <table.TableHead>Invoice</table.TableHead>
      <table.TableHead>Status</table.TableHead>
      <table.TableHead>Amount</table.TableHead>
    </table.TableRow>
  </table.TableHeader>
  <table.TableBody>
    <table.TableRow>
      <table.TableCell>INV-001</table.TableCell>
      <table.TableCell>Paid</table.TableCell>
      <table.TableCell>$120.00</table.TableCell>
    </table.TableRow>
    <table.TableRow>
      <table.TableCell>INV-002</table.TableCell>
      <table.TableCell>Open</table.TableCell>
      <table.TableCell>$80.00</table.TableCell>
    </table.TableRow>
  </table.TableBody>
</table.Table>
<table.Table>
  <table.TableHeader>
    <table.TableRow>
      <table.TableHead>Invoice</table.TableHead>
      <table.TableHead>Status</table.TableHead>
      <table.TableHead>Amount</table.TableHead>
    </table.TableRow>
  </table.TableHeader>
  <table.TableBody>
    <table.TableRow>
      <table.TableCell>INV-001</table.TableCell>
      <table.TableCell>Paid</table.TableCell>
      <table.TableCell>$120.00</table.TableCell>
    </table.TableRow>
    <table.TableRow>
      <table.TableCell>INV-002</table.TableCell>
      <table.TableCell>Open</table.TableCell>
      <table.TableCell>$80.00</table.TableCell>
    </table.TableRow>
  </table.TableBody>
  <table.TableFooter>
    <table.TableRow>
      <table.TableCell attrs={gx.Attrs{{Key: "colspan", Value: "2"}}}>Total</table.TableCell>
      <table.TableCell>$200.00</table.TableCell>
    </table.TableRow>
  </table.TableFooter>
</table.Table>

Table: Selected

<table.Table>
  <table.TableHeader>
    <table.TableRow>
      <table.TableHead>Invoice</table.TableHead>
      <table.TableHead>Status</table.TableHead>
      <table.TableHead>Amount</table.TableHead>
    </table.TableRow>
  </table.TableHeader>
  <table.TableBody>
    <table.TableRow attrs={gx.Attrs{{Key: "data-state", Value: "selected"}}}>
      <table.TableCell>INV-001</table.TableCell>
      <table.TableCell>Paid</table.TableCell>
      <table.TableCell>$120.00</table.TableCell>
    </table.TableRow>
    <table.TableRow>
      <table.TableCell>INV-002</table.TableCell>
      <table.TableCell>Open</table.TableCell>
      <table.TableCell>$80.00</table.TableCell>
    </table.TableRow>
  </table.TableBody>
</table.Table>

TableBody: Empty

<table.TableBody children={gx.Frag()} />

TableCaption: Caption

<table.TableCaption>A caption.</table.TableCaption>

TableCell: Cell

<table.TableCell>Value</table.TableCell>

TableFooter: Empty

<table.TableFooter children={gx.Frag()} />

TableHead: Header

<table.TableHead>Column</table.TableHead>

TableHeader: Empty

<table.TableHeader children={gx.Frag()} />

TableRow: Empty

<table.TableRow children={gx.Frag()} />

API reference

A tag sets a prop by its name with a lower-case first letter: Class is class.

table.Table

Prop Type Default Description
Class string "" Class adds classes to the table element.
Children gx.Node Required Children is the content of the table: the caption, header, body and footer.
Attrs gx.Attrs nil Attrs adds HTML attributes to the table element.

table.TableBody

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the rows of the body.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

table.TableCaption

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the caption text.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

table.TableCell

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the cell.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

table.TableFooter

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the rows of the footer.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

table.TableHead

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the content of the header cell.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

table.TableHeader

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the rows of the header.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

table.TableRow

Prop Type Default Description
Class string "" Class adds classes to the root element.
Children gx.Node Required Children is the cells of the row.
Attrs gx.Attrs nil Attrs adds HTML attributes to the root element.

Do and do not

Do

  • Put a TableCaption on every table.
  • Use TableHead for header cells.

Don't

  • Do not use a table for layout.
  • Do not put a table inside a table head.

Keyboard

Key Action
None The component is static.