Grid
The grid is the resource’s listing: columns, search, filters, pagination,
actions, and export, all declared on Grid[T]. Every string path is
verified against your model’s parsed schema at boot — a typo fails startup,
not a click.
posts.Grid(func(g *steward.Grid[Post]) {
g.Column("ID").Sortable().Width(60)
g.Column("Title").Limit(40).Sortable()
g.Column("Status").Badge(map[any]string{"draft": "secondary", "published": "green"})
g.Column("Featured").Switch()
g.Column("Author.Name", "Author")
g.QuickSearch("Title", "Body")
g.DefaultSort("ID", true)
g.PerPage(10, 10, 20, 50, 100)
})Columns
Column(path) takes a field path — a direct field ("Title") or a one-hop
relation ("Author.Name", preloaded automatically). Pass a custom heading
as the second argument. Presentation helpers chain:
| Helper | Renders |
|---|---|
Badge(map[any]string) | value → colored badge ("green", "red", "secondary", …) |
Bool() | ✓ / ✗ dot |
Link(func(row *T) string) | the value as a link to a computed URL |
Image(w, h) | the value as an <img> (storage-relative paths resolve) |
Limit(n) | truncated text with the full value on hover |
Copyable() | value with a copy-to-clipboard button |
Using(map[any]string) | raw value → display text |
Display(func(v any, row *T) template.HTML) | anything you like |
Behavior helpers: Sortable(), Hide() (available in the column picker but
off by default), Width(px), Help("tooltip").
Fully computed columns don’t need a field at all:
g.ColumnFunc("words", "Words", func(row *Post) template.HTML {
return template.HTML(fmt.Sprint(len(strings.Fields(row.Body))))
})Inline editing
Two helpers turn cells into editors that save through the form’s validation
pipeline (a PUT per field — hooks and rules included):
g.Column("Title").Editable() // click → text input, Enter saves
g.Column("Featured").Switch() // toggle saves immediatelyQuick search
QuickSearch(paths...) enables the search box. Besides plain terms it
understands a small DSL: field:value targets one column, >n / <n
compare numbers, and %text% forces a contains-match.
Filters
g.Filter(func(f *steward.Filters[Post]) {
f.Equal("Status").Select(steward.Options{"draft": "Draft", "published": "Published"})
f.Like("Title")
f.Between("CreatedAt", "Created").Datetime()
})Constructors: Equal, Like, Gt, Lt, In, Between, Date — each
takes a path and optional label. Select(opts) renders a dropdown,
Datetime() switches date inputs to date-time, Placeholder(s) hints.
Actions
Three kinds of custom actions, built with the same Action value:
publish := steward.NewAction("publish", "Publish",
func(c *steward.Context, ids []string) (*steward.Envelope, error) {
// mutate, then tell the client what to do
return steward.Success("Published.").Refresh(), nil
}).Icon("upload").Confirm("Publish the selected posts?")
g.RowAction(publish) // one button per row (ids = that row)
g.BatchAction(publish) // acts on the checked rows
g.ToolAction(publish) // toolbar button, no row contextConfirm shows the shared alert dialog; Danger() styles the button (and
the dialog’s confirm action) destructively.
Trees, groups, reordering
g.Tree("ParentID") // depth-first hierarchy with collapse carets
g.GroupColumns("Publishing", "Status", "Featured") // grouped header over contiguous columns
g.Reorderable("/posts/_order") // drag-drop row handles, POSTs the new orderTree mode renders the whole hierarchy (searching or filtering falls back to a flat list).
Pagination & export
PerPage(def, options...) sets the default page size (10) and the
selector’s choices. Windowed page links (1 … 18 19 20 … 37) appear
automatically. CSV export honors the current filters, search, and sort, and
offers all-pages / current-page / selection modes.
Switching features off
DisableCreate, DisableDelete, DisableEdit, DisableView,
DisableFilter, DisableExport, DisableRowSelector,
DisablePagination, DisableQuickSearch — each removes the UI and the
behavior (e.g. DisableDelete also rejects DELETE requests).
JSON
The same grid serves Accept: application/json — filters, search, sort,
and pagination included. See JSON API.