Column Visibility

Column Visibility

Column visibility hides and shows columns at runtime, which is what a "choose columns" menu writes. Hidden columns disappear from the column lists, the header groups, and the cells of every row, so a table rendered through the "visible" functions needs no other change. This page ports TanStack Table's Column Visibility (React) Guide.

State

Column visibility owns one state slice: a map from column id to whether that column shows.

-- in Table.State
columnVisibility : Dict String Bool

-- in Table.initialState
columnVisibility = Dict.empty

The map is sparse. A column is hidden only when its id is in the map with the value False. An id that is absent, or present with True, shows. An empty map therefore means everything shows, which is why initialState starts there.

Keys are leaf column ids. A group column has no entry of its own; hiding a group column writes an entry for every hideable leaf below it.

Config options

Option Type Default Description
enableHiding Bool True Turns hiding off for the whole table. With it False, columnCanHide is False for every column.

Config is a plain record, so you set it with a record update: { config | enableHiding = False }.

Column options

Builder Type Default Description
withEnableHiding Bool -> Column row -> Column row True Allow or forbid hiding this column.

An id column or a row-selection checkbox column is the usual case for withEnableHiding False:

config : Table.Config Person
config =
    Table.config
        [ Table.column "id" (.id >> Value.String)
            |> Table.withHeader "ID"
            |> Table.withEnableHiding False
        , Table.column "firstName" (.firstName >> Value.String)
            |> Table.withHeader "First name"
        , Table.column "lastName" (.lastName >> Value.String)
            |> Table.withHeader "Last name"
        , Table.column "department" (.department >> Value.String)
            |> Table.withHeader "Department"
        ]

Transitions

Function Signature Description
toggleColumnVisibility Config row -> Column row -> Maybe Bool -> State -> State Show or hide one column. Nothing flips it.
setColumnVisibility Dict String Bool -> State -> State Replace the whole map.
resetColumnVisibility State -> State Empty the map, showing every column again.
toggleAllColumnsVisible Config row -> Maybe Bool -> State -> State Show or hide every leaf column. Nothing flips on the current state. Columns that cannot hide stay visible.

The Maybe Bool is TanStack's optional value argument: Just True shows, Just False hides, Nothing flips.

update : Msg -> Model -> Model
update msg model =
    case msg of
        ToggleColumn column ->
            { model | state = Table.toggleColumnVisibility config column Nothing model.state }

        ToggleAll visible ->
            { model | state = Table.toggleAllColumnsVisible config (Just visible) model.state }

        ResetVisibility ->
            { model | state = Table.resetColumnVisibility model.state }

To start with a column already hidden, write the map once:

startHidden : Table.State
startHidden =
    Table.setColumnVisibility
        (Dict.fromList [ ( "department", False ) ])
        Table.initialState

Queries

Function Signature Description
columnIsVisible State -> Column row -> Bool Does this column show?
columnCanHide Config row -> Column row -> Bool May it be hidden? Both the table and the column have to allow it.
isAllColumnsVisible Config row -> State -> Bool Is every leaf column visible?
isSomeColumnsVisible Config row -> State -> Bool Is at least one leaf column visible?
visibleLeafColumns Config row -> State -> List (Column row) The leaf columns to render, in table order.
visibleFlatColumns Config row -> State -> List (Column row) Every column, group columns included, minus the hidden ones.
visibleCells Config row -> State -> Row row -> List Cell One row's cells whose column is visible, in render order.
visibleCellsByColumnId Config row -> State -> Row row -> Dict String Cell The same cells keyed by column id.

A column menu

columnIsVisible sets the checkbox, columnCanHide disables it, and toggleColumnVisibility is the message it sends:

viewToggle : Table.State -> Table.Column Person -> Html Msg
viewToggle state column =
    label []
        [ input
            [ type_ "checkbox"
            , checked (Table.columnIsVisible state column)
            , disabled (not (Table.columnCanHide config column))
            , onClick (ToggleColumn column)
            ]
            []
        , text (Maybe.withDefault (Table.columnId column) (Table.columnHeader column))
        ]

isAllColumnsVisible and isSomeColumnsVisible drive the "toggle all" box at the top of the list:

viewToggleAll : Table.State -> Html Msg
viewToggleAll state =
    label []
        [ input
            [ type_ "checkbox"
            , checked (Table.isAllColumnsVisible config state)
            , onClick (ToggleAll (not (Table.isSomeColumnsVisible config state)))
            ]
            []
        , text "Toggle all"
        ]
viewToggles : Table.State -> Html Msg
viewToggles state =
    div []
        (viewToggleAll state :: List.map (viewToggle state) (Table.allColumns config))

A visibility-aware body

allColumns and getAllCells ignore visibility, which is what you want in a column menu and not what you want in a table body. Use visibleLeafColumns for the columns and visibleCells for the cells:

viewRow : Table.State -> Table.Row Person -> Html Msg
viewRow state row =
    tr []
        (List.map
            (\cell -> td [] [ text (Value.toString cell.value) ])
            (Table.visibleCells config state row)
        )

headerGroups and the per-region header functions already account for visibility, so a header rendered from them needs no filtering of its own. See Header Groups.

visibleCells also applies the pinning split: left cells, then unpinned cells, then right cells.

Not covered

TanStack's page also describes owning the columnVisibility state through the atoms option or state.columnVisibility plus onColumnVisibilityChange. There is nothing to port: State.columnVisibility is always yours, held in your own model.

column.getToggleVisibilityHandler has no counterpart, because the package produces no event handlers. Write onClick (ToggleColumn column) yourself, as above.

resetColumnVisibility here is TanStack's resetColumnVisibility(true): it empties the map. There is no table.initialState to restore, so to go back to a visibility you started with, call setColumnVisibility with the map you remembered.

Example

Column Visibility, ported from TanStack's Column Visibility example.