Basic usage
Basic table with actions.
Users
|
Name
|
Age
|
Address
|
Action
|
|---|---|---|---|
| Juan Perez | 32 | Madrid | Edit Juan Perez |
| Maria Lopez | 28 | Barcelona | Edit Maria Lopez |
| Carlos Ruiz | 45 | Valencia | Edit Carlos Ruiz |
Code
<% columns = [
{ title: "Name", data_index: :name, key: :name },
{ title: "Age", data_index: :age, key: :age },
{ title: "Address", data_index: :address, key: :address },
{
title: "Action",
key: :action,
render: ->(_value, row, _index) { "Edit #{row[:name]}" }
}
] %>
<% data = [
{ key: "1", name: "Juan Perez", age: 32, address: "Madrid" },
{ key: "2", name: "Maria Lopez", age: 28, address: "Barcelona" },
{ key: "3", name: "Carlos Ruiz", age: 45, address: "Valencia" }
] %>
<%= render Hakumi::Table::Component.new(
columns: columns,
data_source: data,
title: "Users"
) %>
JSX style API
Columns declared with the table builder.
|
Name
|
Info
|
|---|---|
| Alonso | |
| Camila |
Code
<% data = [
{ key: "1", name: "Alonso", age: 29, address: "Bogota" },
{ key: "2", name: "Camila", age: 31, address: "Medellin" }
] %>
<%= render Hakumi::Table::Component.new(data_source: data) do |table| %>
<% table.with_column(title: "Name", data_index: :name, key: :name) %>
<% table.with_group(title: "Info") do |group| %>
<% group.with_column(title: "Age", data_index: :age, key: :age) %>
<% group.with_column(title: "Address", data_index: :address, key: :address) %>
<% end %>
<% end %>
Selection
Selectable rows with custom selections.
|
|
Name
|
Role
|
Team
|
|---|---|---|---|
| Sofia | Designer | UX | |
| Luis | Engineer | Core | |
| Ana | PM | Growth |
Code
<% columns = [
{ title: "Name", data_index: :name, key: :name },
{ title: "Role", data_index: :role, key: :role },
{ title: "Team", data_index: :team, key: :team }
] %>
<% data = [
{ key: "1", name: "Sofia", role: "Designer", team: "UX" },
{ key: "2", name: "Luis", role: "Engineer", team: "Core" },
{ key: "3", name: "Ana", role: "PM", team: "Growth" }
] %>
<%= render Hakumi::Table::Component.new(
columns: columns,
data_source: data,
row_selection: {
type: :checkbox,
selected_row_keys: [ "1" ],
selections: true
}
) %>
Filter and sorter
Client-side filtering and sorting.
|
Name
|
Age
|
City
|
|---|---|---|
| Pablo | 33 | Madrid |
| Rita | 27 | Lisbon |
| Nora | 41 | Madrid |
Code
<% columns = [
{ title: "Name", data_index: :name, key: :name, sorter: true },
{ title: "Age", data_index: :age, key: :age, sorter: true },
{
title: "City",
data_index: :city,
key: :city,
filters: [
{ text: "Madrid", value: "Madrid" },
{ text: "Lisbon", value: "Lisbon" }
]
}
] %>
<% data = [
{ key: "1", name: "Pablo", age: 33, city: "Madrid" },
{ key: "2", name: "Rita", age: 27, city: "Lisbon" },
{ key: "3", name: "Nora", age: 41, city: "Madrid" }
] %>
<%= render Hakumi::Table::Component.new(
columns: columns,
data_source: data
) %>
Expandable rows
Expandable row details.
|
|
Name
|
Department
|
|---|---|---|
| Diego | Finance | |
| Lucia | Engineering | |
Code
<% columns = [
{ title: "Name", data_index: :name, key: :name },
{ title: "Department", data_index: :department, key: :department }
] %>
<% data = [
{ key: "1", name: "Diego", department: "Finance", details: "Full-time, Madrid" },
{ key: "2", name: "Lucia", department: "Engineering", details: "Remote, Porto" }
] %>
<%= render Hakumi::Table::Component.new(
columns: columns,
data_source: data,
expandable: {
expanded_row_keys: [ "1" ],
expanded_row_render: ->(row, _index) { "Details: #{row[:details]}" }
}
) %>
Nested tables
Render nested tables inside expandable rows.
Nested Tables
Click the expand icon to see team members in a nested table.
|
|
Name
|
Department
|
Team Size
|
|---|---|---|---|
| Diego Martinez | Engineering | 3 | |
| Lucia Fernandez | Design | 2 | |
| Roberto Silva | Marketing | 4 | |
Code
<% # Parent table columns %>
<% parent_columns = [
{ title: "Name", data_index: :name, key: :name, width: "30%" },
{ title: "Department", data_index: :department, key: :department, width: "30%" },
{ title: "Team Size", data_index: :team_size, key: :team_size, width: "20%" }
] %>
<% # Child table columns (for nested table) %>
<% child_columns = [
{ title: "Member", data_index: :member_name, key: :member_name },
{ title: "Role", data_index: :role, key: :role },
{ title: "Email", data_index: :email, key: :email }
] %>
<% # Parent data with nested team members %>
<% parent_data = [
{
key: "1",
name: "Diego Martinez",
department: "Engineering",
team_size: 3,
team_members: [
{ key: "1-1", member_name: "Ana Garcia", role: "Senior Developer", email: "ana@company.com" },
{ key: "1-2", member_name: "Carlos Lopez", role: "Developer", email: "carlos@company.com" },
{ key: "1-3", member_name: "Maria Ruiz", role: "Junior Developer", email: "maria@company.com" }
]
},
{
key: "2",
name: "Lucia Fernandez",
department: "Design",
team_size: 2,
team_members: [
{ key: "2-1", member_name: "Pedro Sanchez", role: "UI Designer", email: "pedro@company.com" },
{ key: "2-2", member_name: "Sofia Torres", role: "UX Designer", email: "sofia@company.com" }
]
},
{
key: "3",
name: "Roberto Silva",
department: "Marketing",
team_size: 4,
team_members: [
{ key: "3-1", member_name: "Laura Gomez", role: "Content Manager", email: "laura@company.com" },
{ key: "3-2", member_name: "Miguel Castro", role: "SEO Specialist", email: "miguel@company.com" },
{ key: "3-3", member_name: "Elena Morales", role: "Social Media", email: "elena@company.com" },
{ key: "3-4", member_name: "Juan Diaz", role: "Analytics", email: "juan@company.com" }
]
}
] %>
<div style="margin-bottom: 32px">
<h3>Nested Tables</h3>
<p>Click the expand icon to see team members in a nested table.</p>
<%= render Hakumi::Table::Component.new(
id: "nested-table-parent",
columns: parent_columns,
data_source: parent_data,
bordered: true,
expandable: {
expanded_row_keys: ["1"],
expanded_row_render: ->(row, _index) {
render(Hakumi::Table::Component.new(
columns: child_columns,
data_source: row[:team_members],
size: :small,
bordered: true,
show_header: true
))
}
}
) %>
</div>
<div style="margin-bottom: 32px">
<h3>Nested Tables with Pagination</h3>
<p>Nested table can also have its own pagination and features.</p>
<%= render Hakumi::Table::Component.new(
id: "nested-table-paginated",
columns: parent_columns,
data_source: parent_data,
bordered: true,
pagination: { current: 1, page_size: 2, total: 3 },
expandable: {
expanded_row_render: ->(row, _index) {
render(Hakumi::Table::Component.new(
columns: child_columns,
data_source: row[:team_members],
size: :small,
bordered: true,
pagination: { current: 1, page_size: 2, total: row[:team_members].length }
))
}
}
) %>
</div>
Size and bordered
Compact sizes and bordered styles.
|
Name
|
Status
|
|---|---|
| Olivia | Active |
| Mateo | Inactive |
|
Name
|
Status
|
|---|---|
| Olivia | Active |
| Mateo | Inactive |
Code
<% columns = [
{ title: "Name", data_index: :name, key: :name },
{ title: "Status", data_index: :status, key: :status }
] %>
<% data = [
{ key: "1", name: "Olivia", status: "Active" },
{ key: "2", name: "Mateo", status: "Inactive" }
] %>
<%= render Hakumi::Space::Component.new(direction: :vertical, size: :middle) do |space| %>
<% space.with_item do %>
<%= render Hakumi::Table::Component.new(columns: columns, data_source: data, size: :middle, bordered: true) %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Table::Component.new(columns: columns, data_source: data, size: :small, bordered: true) %>
<% end %>
<% end %>
Summary and pagination
Summary slot with pagination.
Code
<% columns = [
{ title: "Product", data_index: :product, key: :product },
{ title: "Qty", data_index: :quantity, key: :quantity, sorter: true }
] %>
<% data = [
{ key: "1", product: "Notebook", quantity: 4 },
{ key: "2", product: "Monitor", quantity: 2 },
{ key: "3", product: "Chair", quantity: 6 },
{ key: "4", product: "Desk", quantity: 1 }
] %>
<%= render Hakumi::Table::Component.new(
columns: columns,
data_source: data,
summary: "Total items: #{data.sum { |row| row[:quantity] }}",
pagination: { current: 1, page_size: 3, total: 4 }
) %>
Programmatic rendering
Render table with HakumiComponents.renderComponent.
Code
<div id="table-programmatic-target"></div>
<%= render Hakumi::Button::Component.new(
type: :primary,
id: "table-programmatic-button"
) do %>
Render programmatically
<% end %>
<script>
(() => {
const targetSelector = "#table-programmatic-target"
const buttonId = "table-programmatic-button"
let wired = false
const renderTable = () => window.HakumiComponents.renderComponent("table", {
params: {
columns: JSON.stringify([
{ title: "Name", data_index: "name", key: "name" },
{ title: "Age", data_index: "age", key: "age", sorter: true }
]),
data_source: JSON.stringify([
{ key: "1", name: "Noa", age: 24 },
{ key: "2", name: "Leo", age: 35 }
])
},
target: targetSelector,
mode: "replace"
})
const wire = () => {
if (wired) return true
const button = document.getElementById(buttonId)
if (!button) return false
if (!window.HakumiComponents?.renderComponent) return false
button.addEventListener("click", async (event) => {
event.preventDefault()
await renderTable()
})
wired = true
return true
}
if (wire()) return
const onReady = () => {
if (wire()) {
document.removeEventListener("turbo:load", onReady)
window.removeEventListener("load", onReady)
}
}
document.addEventListener("turbo:load", onReady)
window.addEventListener("load", onReady)
})()
</script>
Fixed header & columns
Scroll with sticky header, left and right pinned columns.
Distributed Leadership
|
Name
|
Role
|
Country
|
Status
|
Experience
|
Last Login
|
Actions
|
|---|---|---|---|---|---|---|
| Andrea Torres | Product Manager | Spain | Active | 7 years | 2026-01-02 14:10 | Contact Andrea Torres |
| Luis Romero | Lead Engineer | Mexico | Active | 10 years | 2026-01-01 09:20 | Contact Luis Romero |
| Marina Costa | UX Director | Portugal | Away | 8 years | 2025-12-30 18:45 | Contact Marina Costa |
| Samuel Ortiz | QA Lead | Colombia | Busy | 6 years | 2025-12-29 12:15 | Contact Samuel Ortiz |
| Lucia Peña | Data Scientist | Argentina | Offline | 5 years | 2025-12-28 22:03 | Contact Lucia Peña |
| Hugo Medina | Cloud Architect | Chile | Active | 11 years | 2025-12-27 07:55 | Contact Hugo Medina |
| Sara Navas | Design Ops | Spain | Active | 4 years | 2025-12-26 16:42 | Contact Sara Navas |
| Iván Duarte | Mobile Lead | Peru | Away | 9 years | 2025-12-25 13:05 | Contact Iván Duarte |
Code
<% columns = [
{ title: "Name", data_index: :name, key: :name, width: "160px", fixed: :left },
{ title: "Role", data_index: :role, key: :role, width: "180px", fixed: :left },
{ title: "Country", data_index: :country, key: :country, width: "180px" },
{ title: "Status", data_index: :status, key: :status, width: "160px" },
{ title: "Experience", data_index: :experience, key: :experience, width: "160px" },
{ title: "Last Login", data_index: :last_login, key: :last_login, width: "200px" },
{ title: "Actions", key: :actions, width: "200px", fixed: :right,
render: ->(_value, row, _index) { "Contact #{row[:name]}" } }
] %>
<% data = [
{ key: "1", name: "Andrea Torres", role: "Product Manager", country: "Spain", status: "Active", experience: "7 years", last_login: "2026-01-02 14:10" },
{ key: "2", name: "Luis Romero", role: "Lead Engineer", country: "Mexico", status: "Active", experience: "10 years", last_login: "2026-01-01 09:20" },
{ key: "3", name: "Marina Costa", role: "UX Director", country: "Portugal", status: "Away", experience: "8 years", last_login: "2025-12-30 18:45" },
{ key: "4", name: "Samuel Ortiz", role: "QA Lead", country: "Colombia", status: "Busy", experience: "6 years", last_login: "2025-12-29 12:15" },
{ key: "5", name: "Lucia Peña", role: "Data Scientist", country: "Argentina", status: "Offline", experience: "5 years", last_login: "2025-12-28 22:03" },
{ key: "6", name: "Hugo Medina", role: "Cloud Architect", country: "Chile", status: "Active", experience: "11 years", last_login: "2025-12-27 07:55" },
{ key: "7", name: "Sara Navas", role: "Design Ops", country: "Spain", status: "Active", experience: "4 years", last_login: "2025-12-26 16:42" },
{ key: "8", name: "Iván Duarte", role: "Mobile Lead", country: "Peru", status: "Away", experience: "9 years", last_login: "2025-12-25 13:05" }
] %>
<%= render Hakumi::Table::Component.new(
columns: columns,
data_source: data,
scroll: { x: "1000px", y: "320px" },
sticky: true,
title: "Distributed Leadership"
) %>
Ellipsis with tooltips
Show titles automatically or use custom tooltips for truncated text.
|
Product
|
Description
|
Status
|
|---|---|---|
| Hakumi Insight Platform |
Unified analytics workspace for product, ops and AI teams.
Notes: Hakumi Insight Platform
|
Active |
| Hakumi Signal |
Event intelligence service orchestrating over 18 providers.
Notes: Hakumi Signal
|
Shipping |
| Hakumi Atlas |
Customer data backbone with governance baked in from day zero.
Notes: Hakumi Atlas
|
Planned |
Code
<% columns = [
{ title: "Product", data_index: :product, key: :product, ellipsis: { show_title: true } },
{ title: "Description", data_index: :description, key: :description,
ellipsis: { tooltip: { placement: :top_left, title: ->(row, _) { "Notes: #{row[:product]}" } } } },
{ title: "Status", data_index: :status, key: :status, width: "140px" }
] %>
<% data = [
{ key: "1", product: "Hakumi Insight Platform", description: "Unified analytics workspace for product, ops and AI teams.", status: "Active" },
{ key: "2", product: "Hakumi Signal", description: "Event intelligence service orchestrating over 18 providers.", status: "Shipping" },
{ key: "3", product: "Hakumi Atlas", description: "Customer data backbone with governance baked in from day zero.", status: "Planned" }
] %>
<%= render Hakumi::Table::Component.new(
columns: columns,
data_source: data,
scroll: { x: "640px" }
) %>
Editable rows & cells
Table with editable cells and rows configuration.
Cell Editable
Click cells to edit
|
Name
|
Age
|
Address
|
|---|---|---|
| Juan Perez | 32 | Madrid |
| Maria Lopez | 28 | Barcelona |
| Carlos Ruiz | 45 | Valencia |
Row Editable
Click cells to edit (row mode)
|
Name
|
Age
|
Address
|
|---|---|---|
| Juan Perez | 32 | Madrid |
| Maria Lopez | 28 | Barcelona |
| Carlos Ruiz | 45 | Valencia |
Code
<% columns = [
{
title: "Name",
data_index: :name,
key: :name,
editable: true,
width: "30%"
},
{
title: "Age",
data_index: :age,
key: :age,
editable: { input_type: :number },
should_cell_update: "preventAgeDecrement",
width: "20%"
},
{
title: "Address",
data_index: :address,
key: :address,
width: "50%"
}
] %>
<% data = [
{ key: "1", name: "Juan Perez", age: 32, address: "Madrid" },
{ key: "2", name: "Maria Lopez", age: 28, address: "Barcelona" },
{ key: "3", name: "Carlos Ruiz", age: 45, address: "Valencia" }
] %>
<div style="margin-bottom: 32px">
<h3>Cell Editable</h3>
<%= render Hakumi::Table::Component.new(
id: "editable-table-cell",
columns: columns,
data_source: data,
title: "Click cells to edit",
bordered: true,
editable: { mode: :cell }
) %>
</div>
<div style="margin-bottom: 32px">
<h3>Row Editable</h3>
<%= render Hakumi::Table::Component.new(
id: "editable-table-row",
columns: columns,
data_source: data,
title: "Click cells to edit (row mode)",
bordered: true,
editable: { mode: :row }
) %>
</div>
<script>
// Register validation function globally before Stimulus connects
window.HakumiTableShouldCellUpdate = window.HakumiTableShouldCellUpdate || {}
window.HakumiTableShouldCellUpdate.preventAgeDecrement = ({ value, originalValue }) => {
const previous = Number(originalValue)
const next = Number(value)
if (!Number.isNaN(previous) && !Number.isNaN(next) && next < previous) {
return "Age cannot decrease."
}
return true
}
</script>
<script>
(() => {
const cellTable = document.getElementById("editable-table-cell")
const rowTable = document.getElementById("editable-table-row")
if (!cellTable || !rowTable) return
let wired = false
const wire = () => {
if (wired) return true
const cellApi = cellTable.hakumiTable
const rowApi = rowTable.hakumiTable
if (!cellApi || !rowApi) return false
// Move validation hook from global to instance-level
const hookFn = window.HakumiTableShouldCellUpdate.preventAgeDecrement
if (hookFn) {
cellApi.registerShouldCellUpdate("preventAgeDecrement", hookFn)
rowApi.registerShouldCellUpdate("preventAgeDecrement", hookFn)
}
wired = true
return true
}
if (wire()) return
const onReady = () => {
if (wire()) {
document.removeEventListener("turbo:load", onReady)
window.removeEventListener("load", onReady)
}
}
document.addEventListener("turbo:load", onReady)
window.addEventListener("load", onReady)
// Handle prevented updates - user decides how to display feedback
document.addEventListener("hakumi:table:cell-update-prevented", (event) => {
const { reason } = event.detail
if (window.HakumiComponents?.renderComponent) {
window.HakumiComponents.renderComponent("message", {
params: { type: "warning", message: reason }
})
}
})
// Handle successful edits - backend persistence (cell mode)
document.addEventListener("hakumi:table:edit", (event) => {
const { rowKey, columnKey, value } = event.detail
console.log(`[Table Cell] Saved row=${rowKey} col=${columnKey} value="${value}"`)
})
// Handle successful row edits - backend persistence (row mode)
document.addEventListener("hakumi:table:row-edit", (event) => {
const { rowKey, changes, params } = event.detail
console.log(`[Table Row] Saved row=${rowKey} with ${changes.length} changes:`, changes)
console.log(`[Table Row] Rails params:`, params)
// Single API call with Rails-formatted params (ready to use!)
// fetch(`/api/rows/${rowKey}`, {
// method: 'PATCH',
// body: JSON.stringify(params),
// headers: { 'Content-Type': 'application/json' }
// })
})
})()
</script>
Server-side pagination
Table with AJAX data loading, pagination, and sorting. Just set data_url and the component handles everything.
|
ID
|
Name
|
Department
|
Age
|
City
|
Salary
|
|---|---|---|---|---|---|
|
No data |
|||||
Code
<%= render Hakumi::Table::Component.new(
columns: [
{ title: "ID", data_index: :id, key: :id, width: "80px", sorter: true },
{ title: "Name", data_index: :name, key: :name, sorter: true },
{ title: "Department", data_index: :department, key: :department, sorter: true },
{ title: "Age", data_index: :age, key: :age, width: "80px", sorter: true },
{ title: "City", data_index: :city, key: :city, sorter: true },
{ title: "Salary", data_index: :salary_formatted, key: :salary, sorter: true, sort_value: :salary }
],
data_source: [],
bordered: true,
data_url: "/playground/table_data",
pagination: { page_size: 10 }
) %>
Drag sorting
Drag and drop rows or columns to reorder. Supports handle-based row drag.
|
|
Name
|
Age
|
Address
|
|---|---|---|---|
| John Brown | 32 | New York No. 1 Lake Park | |
| Jim Green | 42 | London No. 1 Lake Park | |
| Joe Black | 32 | Sydney No. 1 Lake Park | |
| Jane White | 28 | Tokyo No. 1 Lake Park |
Code
<%= render Hakumi::Table::Component.new(
columns: [
{ title: "Name", data_index: :name, key: :name },
{ title: "Age", data_index: :age, key: :age },
{ title: "Address", data_index: :address, key: :address }
],
data_source: [
{ key: "1", name: "John Brown", age: 32, address: "New York No. 1 Lake Park" },
{ key: "2", name: "Jim Green", age: 42, address: "London No. 1 Lake Park" },
{ key: "3", name: "Joe Black", age: 32, address: "Sydney No. 1 Lake Park" },
{ key: "4", name: "Jane White", age: 28, address: "Tokyo No. 1 Lake Park" }
],
bordered: true,
row_drag: :handle
) %>
Responsive columns
Columns that show/hide based on viewport breakpoints. Resize browser to see effect.
|
Name
|
Age
|
Email
|
Phone
|
Department
|
Status
|
|---|---|---|---|---|---|
| John Brown | 32 | john@example.com | 555-0101 | Engineering | Active |
| Jane Smith | 28 | jane@example.com | 555-0102 | Design | Active |
| Bob Wilson | 45 | bob@example.com | 555-0103 | Marketing | Inactive |
| Alice Lee | 36 | alice@example.com | 555-0104 | Sales | Active |
Code
<%= render Hakumi::Table::Component.new(
columns: [
{ title: "Name", data_index: :name, key: :name, responsive: [:xs, :sm, :md, :lg, :xl, :xxl] },
{ title: "Age", data_index: :age, key: :age, width: "80px", responsive: [:xs, :sm, :md, :lg, :xl, :xxl] },
{ title: "Email", data_index: :email, key: :email, responsive: [:md, :lg, :xl, :xxl] },
{ title: "Phone", data_index: :phone, key: :phone, responsive: [:lg, :xl, :xxl] },
{ title: "Department", data_index: :department, key: :department, responsive: [:xl, :xxl] },
{ title: "Status", data_index: :status, key: :status, width: "100px", responsive: [:xs, :sm, :md, :lg, :xl, :xxl] }
],
data_source: [
{ key: "1", name: "John Brown", age: 32, email: "john@example.com", phone: "555-0101", department: "Engineering", status: "Active" },
{ key: "2", name: "Jane Smith", age: 28, email: "jane@example.com", phone: "555-0102", department: "Design", status: "Active" },
{ key: "3", name: "Bob Wilson", age: 45, email: "bob@example.com", phone: "555-0103", department: "Marketing", status: "Inactive" },
{ key: "4", name: "Alice Lee", age: 36, email: "alice@example.com", phone: "555-0104", department: "Sales", status: "Active" }
],
bordered: true
) %>
Table API
| Prop | Type | Default | Description |
|---|---|---|---|
columns |
Array |
- |
Column definitions (title, data_index, key, sorter, filters, render, etc.). |
data_source |
Array |
[] |
Data rows (hashes or objects). |
row_key |
Symbol or Proc |
:key |
Row key field or proc. |
size |
Symbol |
:default |
Table size. Options: :default, :middle, :small. |
bordered |
Boolean |
false |
Show borders. |
title |
String or Proc |
nil |
Table title. |
footer |
String or Proc |
nil |
Table footer. |
row_selection |
Hash |
nil |
Row selection settings (type, selected_row_keys, selections, preserve_selected_row_keys). |
expandable |
Hash |
nil |
Expandable row settings (expanded_row_render, expanded_row_keys). |
pagination |
Hash or Boolean |
nil |
Pagination settings. For server-side: { href, turbo_frame, preserve_params }. |
summary |
String or Proc |
nil |
Summary content. |
empty_text |
String |
nil |
Custom empty state text. |
scroll |
Hash |
nil |
Scroll configuration: { x: '1000px', y: '400px' }. Enables horizontal/vertical scroll. |
sticky |
Boolean |
false |
Sticky header when scrolling vertically. |
editable |
Boolean or Hash |
nil |
Editable configuration ({ mode: :cell | :row, trigger: :click }). |
row_drag |
Boolean | Symbol or Hash |
nil |
Row drag: true (whole row), :handle (with handle column), or { enabled: true, handle: true }. |
column_drag |
Boolean |
false |
Enable column reordering via drag and drop. |
data_url |
String |
nil |
Server endpoint for AJAX data loading. Enables automatic pagination/sorting via fetch. |
Pagination Options (server-side)
| Prop | Type | Default | Description |
|---|---|---|---|
href |
String |
nil |
Server endpoint URL for fetching paginated data. Enables server-side mode. |
turbo_frame |
String |
nil |
Turbo Frame ID for updating content without full page reload. |
page_param |
String |
page |
URL parameter name for current page. |
per_page_param |
String |
per_page |
URL parameter name for page size. |
preserve_params |
String |
nil |
Comma-separated list of params to preserve (e.g., 'sort_field,sort_order'). |
current |
Integer |
1 |
Current page number. |
page_size |
Integer |
10 |
Number of items per page. |
total |
Integer |
- |
Total number of records (required for server-side). |
show_size_changer |
Boolean |
false |
Show page size selector. |
show_total |
Proc |
nil |
Lambda to render total info: ->(total, range) { ... }. |
Column Options (sorting)
| Prop | Type | Default | Description |
|---|---|---|---|
sorter |
Boolean or Proc |
nil |
Enable sorting. Set true for default sort or proc for custom. |
default_sort_order |
String |
nil |
Initial sort order: 'ascend' or 'descend'. Used for server-side sort state. |
sort_directions |
Array |
['ascend', 'descend'] |
Available sort directions for this column. |
Column Options (responsive)
| Prop | Type | Default | Description |
|---|---|---|---|
responsive |
Array |
nil |
Breakpoints where column is visible: [:xs, :sm, :md, :lg, :xl, :xxl]. Hidden by default when set. |
ellipsis |
Boolean or Hash |
false |
Truncate text. Options: true, { show_title: true }, { tooltip: { title: '...' } }. |
Column Options (fixed)
| Prop | Type | Default | Description |
|---|---|---|---|
fixed |
Symbol |
nil |
Pin column to left or right: :left, :right. Requires scroll: { x: ... }. |
width |
String |
nil |
Column width (required for fixed columns): '150px', '20%'. |
Column Options (editable)
| Prop | Type | Default | Description |
|---|---|---|---|
editable |
Boolean or Hash |
nil |
Make column editable. Inherits table-level config or override with { input_type: :text }. |
editable.input_type |
Symbol |
:text |
Input type: :text, :number, :textarea, :select. |
editable.options |
Array |
nil |
Options for select input: [['Label', 'value'], ...]. |
JavaScript API (element.hakumiTable)
| Prop | Type | Default | Description |
|---|---|---|---|
getSelectedRowKeys() |
Function |
- |
Get selected row keys. |
setSelectedRowKeys(keys) |
Function |
- |
Set selected row keys. |
clearSelection() |
Function |
- |
Clear selection. |
selectAll() |
Function |
- |
Select all rows. |
invertSelection() |
Function |
- |
Invert selection. |
getSorter() |
Function |
- |
Get sorter state. |
setSorter(sorter) |
Function |
- |
Set sorter state. |
clearSorter() |
Function |
- |
Clear sorter state. |
getFilters() |
Function |
- |
Get current filters. |
setFilters(filters) |
Function |
- |
Set filters. |
clearFilters() |
Function |
- |
Clear filters. |
getExpandedRowKeys() |
Function |
- |
Get expanded row keys. |
setExpandedRowKeys(keys) |
Function |
- |
Set expanded row keys. |
toggleRowExpansion(key) |
Function |
- |
Toggle row expansion. |
getRowOrder() |
Function |
- |
Get current row keys in display order. |
getColumnOrder() |
Function |
- |
Get current column keys in display order. |
loadData(options) |
Function |
- |
Load data from data_url. Options: { page, pageSize, sorter }. Returns Promise. |
setPage(page) |
Function |
- |
Navigate to specific page. |
setPageSize(size) |
Function |
- |
Change page size and reload. |
getPagination() |
Function |
- |
Get current pagination state: { current, pageSize, total }. |
refresh() |
Function |
- |
Reload current page data. |
Events
| Prop | Type | Default | Description |
|---|---|---|---|
hakumi--table:row_reorder |
CustomEvent |
- |
Fired after row drag ends. Detail: { oldIndex, newIndex, rowKey, order }. |
hakumi--table:column_reorder |
CustomEvent |
- |
Fired after column drag ends. Detail: { oldIndex, newIndex, columnKey, order }. |
hakumi--table:load_complete |
CustomEvent |
- |
Fired after AJAX loadData completes. Detail: { data, pagination, sorter }. |
hakumi--table:load_error |
CustomEvent |
- |
Fired on AJAX loadData error. Detail: { error }. |