Skip to content
Get Started

Board Widget

The board widget renders a Kanban-style page from two page queries:

  • a row entity for the cards, such as Issue
  • a column entity for the board lanes, such as Column

It is a good fit for ticket queues, editorial workflows, approval pipelines, and any other status-driven flow where records move between named columns.

[entity.Column]
fields = [
{ name = "id", type = "ULID", primary_key = true },
{ name = "name", type = "Text", required = true },
{ name = "position", type = "Integer", default = 0 }
]
[entity.Issue]
fields = [
{ name = "id", type = "ULID", primary_key = true },
{ name = "title", type = "Text", required = true },
{ name = "columnId", type = "Ref", ref = "Column.id", required = true },
{ name = "position", type = "Integer", default = 0 },
{ name = "important", type = "Boolean", default = false }
]
[page."/issues"]
title = "Issue Board"
auth = "optional"
[page."/issues".query.columns]
entity = "Column"
orderBy = { position = "asc" }
[page."/issues".query.issues]
entity = "Issue"
orderBy = { position = "asc" }
[page."/issues".widget]
kind = "board"
entity = "Issue"
group_by = "columnId"
column_entity = "Column"
column_label = "name"
column_order = "position"
rank_field = "position"
[page."/issues".widget.card]
title = "title"
toggles = [{ field = "important", label_on = "", label_off = "" }]
[page."/issues".widget.on_move]
update = { columnId = "$to.id", position = "$index" }
[page."/issues".widget.on_column_rename]
update = { name = "$value" }
[page."/issues".widget.on_toggle]
update = { "$field" = "!$row.$field" }

The board widget reads its data from the page queries. You need:

  • one query whose entity matches the widget entity for the cards
  • one query whose entity matches column_entity for the columns

The renderer matches queries by entity name, so naming the queries issues and columns is conventional but not required.

FieldDescription
kindMust be board
entityEntity used for the board cards
column_entityEntity used for the board columns
group_byField on the card entity that points to the current column. Defaults to columnId
column_labelColumn field used as the visible title. Defaults to name
column_orderColumn field used to sort columns. Defaults to position
rank_fieldCard field used to sort cards within a column. Defaults to position

Customize how each card renders with [page."/path".widget.card]:

[page."/issues".widget.card]
title = "title"
toggles = [{ field = "important", label_on = "", label_off = "" }]

Supported keys:

FieldDescription
titleCard field to display as the primary label. Defaults to title
togglesOptional toggle buttons rendered on the card

Each toggle can specify:

FieldDescription
fieldBoolean field to toggle
labelShared label if you do not need separate on/off states
label_onLabel when the toggle is on
label_offLabel when the toggle is off

The board widget becomes interactive when you define event handlers.

[page."/issues".widget.on_move]
update = { columnId = "$to.id", position = "$index" }

This runs when a card is dropped into a column. Common uses:

  • update the column foreign key
  • update a sort or rank field
  • trigger a workflow after movement
[page."/issues".widget.on_column_rename]
update = { name = "$value" }

With this handler in place, column titles become editable in the UI.

[page."/issues".widget.on_toggle]
update = { "$field" = "!$row.$field" }

This is typically used for booleans such as important, blocked, or done.

Board interactions still go through the normal entity read and update paths. That means:

  • users must be allowed to read the card and column entities to see the board
  • users must be allowed to update the target entity for on_move, on_toggle, or on_column_rename

If access control denies the operation, the widget request fails server-side.

  • Keep column_order and rank_field numeric for predictable ordering
  • Use explicit page queries with orderBy so the initial board load is stable
  • Treat group_by as the source of truth for a card’s current column
  • Start with one or two event handlers and add more interaction only where it improves the workflow