Columns

This page is about the Column row values the table works with, not about writing column definitions. For that, see Column Definitions.

It is the port of TanStack's Columns guide. The difference worth stating first: in TanStack a column def is a plain object and the table turns it into a column instance with methods on it. Here there is only one thing. Table.column returns a Column row, the with* builders return a Column row, and that is what the table reads. No construction step happens in between.

Column row is opaque, so you read it through functions.

Where to get columns from

Header and cell objects

If you are rendering markup, you probably want headers or cells rather than columns. Neither of them carries a column though: a header carries headerColumnId and a cell carries columnId, so going the other way is a lookup:

headerColumn : Table.Header Person -> Maybe (Table.Column Person)
headerColumn header =
    Table.findColumn config (Table.headerColumnId header)

Column list functions

TanStack elm-table
table.getColumn(id) findColumn
table.getAllColumns() Config.columns, the record field you built
table.getAllFlatColumns() allColumns
table.getAllLeafColumns() leafColumns, then orderColumns
table.getVisibleFlatColumns() visibleFlatColumns
table.getVisibleLeafColumns() visibleLeafColumns
column.columns columnColumns
column.getFlatColumns() columnFlatColumns
column.getLeafColumns() columnLeafColumns
table.getStartLeafColumns() leftLeafColumns
table.getCenterLeafColumns() centerLeafColumns
table.getEndLeafColumns() rightLeafColumns
table.getStartVisibleLeafColumns() leftVisibleLeafColumns
table.getCenterVisibleLeafColumns() centerVisibleLeafColumns
table.getEndVisibleLeafColumns() rightVisibleLeafColumns

The ones that only walk the column tree take just the Config. The ones that depend on order, visibility, or pinning take the State as well.

allColumns is the flattened tree: every column, group columns included, each group before its children. TanStack's getAllColumns() is the unflattened tree, which here is simply the columns field of your Config, since Config is a plain record.

leafColumns is in definition order. TanStack's getAllLeafColumns() also applies the table order, which is orderColumns here.

config : Table.Config Person
config =
    Table.config
        [ Table.display "select"
        , Table.group "name"
            [ Table.column "firstName" (.firstName >> Value.String)
                |> Table.withHeader "First"
            , Table.column "lastName" (.lastName >> Value.String)
                |> Table.withHeader "Last"
            ]
            |> Table.withHeader "Name"
        , Table.column "salary" (.salary >> Value.Number)
            |> Table.withHeader "Salary"
            |> Table.withSize 120
        ]
columnTree : List String
columnTree =
    Table.allColumns config
        |> List.map (\col -> String.repeat (Table.columnDepth col) "  " ++ columnLabel col)

findColumn searches that same list, so group columns are found too:

salaryWidth : Float
salaryWidth =
    Table.findColumn config "salary"
        |> Maybe.map (Table.columnSize config)
        |> Maybe.withDefault 0

The tree and the leaf list

This is the distinction to get right, because the two lists have different lengths and different uses.

The tree is what you wrote. allColumns walks it, columnColumns gives one group's children, columnDepth says how deep a column sits, and columnParentId points back up.

childLabels : Table.Column Person -> List String
childLabels col =
    List.map columnLabel (Table.columnColumns col)
parentLabel : Table.Column Person -> String
parentLabel col =
    Table.columnParentId col
        |> Maybe.andThen (Table.findColumn config)
        |> Maybe.map columnLabel
        |> Maybe.withDefault "(top level)"

The leaf list is the columns that actually produce cells. Group columns have no accessor and no cells, so they are not in it. columnLeafColumns is the per-column version: the leaves under one column, or the column itself when it is already a leaf.

leafIds : List String
leafIds =
    List.map Table.columnId (Table.leafColumns config)
coveredLeafIds : Table.Column Person -> List String
coveredLeafIds col =
    List.map Table.columnId (Table.columnLeafColumns col)

Which list to render

renderedColumns : Table.State -> List (Table.Column Person)
renderedColumns state =
    Table.visibleLeafColumns config state
  • Header cells: neither. Use headerGroups, which turns the column tree into header rows with the right colspan and rowspan. See Header Groups.
  • A column visibility menu: allColumns, so the user sees the group columns and the already-hidden ones. visibleFlatColumns is the same list minus the hidden columns, which is what you want for a legend or an export, not for a menu that turns columns back on.
viewColumnMenu : Table.State -> Html Msg
viewColumnMenu state =
    ul []
        (Table.allColumns config
            |> List.map
                (\col ->
                    li []
                        [ label []
                            [ input
                                [ type_ "checkbox"
                                , checked (Table.columnIsVisible state col)
                                , onClick (ToggleColumn col)
                                ]
                                []
                            , text (columnLabel col)
                            ]
                        ]
                )
        )

Column objects

TanStack elm-table
column.id columnId
column.columnDef.header columnHeader
column.columnDef.footer columnFooter
column.depth columnDepth
column.columns columnColumns
column.parent columnParentId
column.accessorFn columnAccessor
column.getSize() columnSize
column.columnDef.minSize columnMinSize
column.columnDef.maxSize columnMaxSize
column.columnDef none, the column is the definition

Column ids

Every column has an id, and you always write it yourself: Table.column "firstName" .... There is no accessorKey to derive one from, because an accessor here is a function rather than a key path, and no header to fall back on. Ids must be unique across the whole table, group columns included.

Column defs

column.columnDef has no counterpart. The Column row value is the definition, built by Table.column, Table.group, or Table.display and then piped through with* builders. So columnHeader reads back what withHeader set, and so on.

columnHeader and columnFooter are Maybe String, since both builders are optional. Most views fall back to the column id:

columnLabel : Table.Column Person -> String
columnLabel col =
    Maybe.withDefault (Table.columnId col) (Table.columnHeader col)

Nested grouped column properties

  • columnColumns: the children of a group column, empty on an accessor or display column.
  • columnDepth: 0 at the top level, stamped by Table.group when the column is nested.
  • columnParentId: the id of the group column above, Nothing at the top level. TanStack stores the parent column itself; here it is an id, so pair it with findColumn.
  • columnFlatColumns: one column and everything below it, the column itself first.

Accessors and sizes

columnAccessor gives back the function you passed to Table.column, or Nothing for a group or display column. That is the way to tell the three kinds apart:

hasAccessor : Table.Column Person -> Bool
hasAccessor col =
    Table.columnAccessor col /= Nothing

columnSize, columnMinSize, and columnMaxSize take the Config because an unset size falls back to Config.defaultColumn, which defaults to 150, 20, and 9007199254740991 pixels. They are the static sizes. For the rendered width, which also accounts for State.columnSizing, use getColumnSize. See Column Sizing.

Ordering a column list

orderColumns puts any column list into table order: State.columnOrder first, unlisted columns behind the listed ones, then the grouped-column rules.

orderedLeafColumns : Table.State -> List (Table.Column Person)
orderedLeafColumns state =
    Table.leafColumns config
        |> Table.orderColumns config state

orderGroupedColumns applies only Config.groupedColumnMode to a leaf column list, moving the grouped columns to the front (the default), removing them, or leaving the list alone.

groupedFirst : Table.State -> List (Table.Column Person)
groupedFirst state =
    Table.leafColumns config
        |> Table.orderGroupedColumns config state

visibleLeafColumns already runs the ordering, so reach for these two only when you are building a list of your own. Note that visibleLeafColumns is not pin-ordered, matching TanStack: the pin split happens in visibleCells and in the header functions. See Column Ordering and Column Pinning.

More column APIs

The per-feature column queries are with their features: columnIsVisible and columnCanHide in Column Visibility, columnIsPinned and columnCanPin in Column Pinning, getCanSort in Sorting, getCanFilter in Column Filtering, getCanGroup in Grouping.

Several of those take a column id rather than a Column row, because they only read the State. Check the signature in the module reference.

Column rendering

Do not render headers or cells from columns. Use headers and cells, which carry the spans, the order, and the values. Columns are for lists of columns: a visibility menu, a grouping picker, a settings panel.