The l_ui_table helper renders a fully styled, accessible data table from a collection and column configuration. It handles header cells, row scoping, custom rendering, actions, and empty states.
Basic table
Pass a collection and an array of column hashes. The first column is automatically treated as the primary (<th scope="row">) column.
Users
Name
Email
Alice Johnson
alice@example.com
Bob Smith
bob@example.com
Carol Williams
carol@example.com
David Brown
david@example.com
Eve Davis
eve@example.com
Frank Miller
frank@example.com
Grace Lee
grace@example.com
Henry Wilson
henry@example.com
Ivy Chen
ivy@example.com
Jack Taylor
jack@example.com
Custom cell rendering
Use render: procs to customise how each cell is displayed. You can also set label: to override the column header text. For date/time columns, the l_ui_format_datetime helper formats values as "15 Apr 2026, 10:30".
This is the body of "Testing best practices". It covers i...
11 Apr 2026, 10:28
Complex cell rendering with partials
For cells with complex markup, call render inside the render: proc to delegate to a partial. This keeps your table call clean while allowing rich cell layouts.
Collections don't have to be ActiveRecord - arrays of hashes work too. Use render: procs to read hash keys (e.g. r[:key]) just as you would for object attributes.
Keyboard shortcuts
Shortcut
Action
Ctrl+S
Save
Ctrl+Z
Undo
Ctrl+Shift+P
Command palette
Empty state
When the collection is empty, the table renders a single "No records found." row spanning all columns.
Empty table
Name
Email
No records found.
Turbo-targetable rows
Each <tr> is automatically given id="user_1"-style ids (via dom_id(record)) when records respond to to_key, so individual rows can be replaced from anywhere in the app with turbo_stream.replace dom_id(@user), .... Pass row_id: as a proc to override, or return nil to omit the id.
Users with turbo-targetable rows
Name
Email
Alice Johnson
alice@example.com
Bob Smith
bob@example.com
Carol Williams
carol@example.com
Options
l_ui_table options
Option
Type
Description
records
Collection
The collection to render - ActiveRecord relations, arrays of objects, or arrays of hashes (first positional argument)
columns:
Array
Array of column hashes (see column options below)
caption:
String
Accessible table caption (rendered as screen-reader-only text)
actions:
Proc
Receives each record, returns content for the actions cell
actions_label:
String
Header text for the actions column (defaults to "Actions")
query:
Ransack::Search
Ransack search object to enable sortable column headers
turbo_frame:
String
Turbo Frame ID for sort links
row_id:
Proc
Receives each record, returns the <tr> id. Defaults to dom_id(record) for ActiveRecord-like records
Column options
Column options
Option
Type
Description
attribute:
Symbol
Used for label generation and sort links
label:
String
Custom header text (defaults to humanised attribute name)
primary:
Boolean
Renders as <th scope="row"> (defaults to first column)
sortable:
Boolean
Show sort link when query: is provided (defaults to true)
render:
Proc
Required. Receives the record, returns cell content