Table

Component

Interactive examples and API documentation

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
Details: Full-time, Madrid
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
Member
Role
Email
Ana Garcia Senior Developer ana@company.com
Carlos Lopez Developer carlos@company.com
Maria Ruiz Junior Developer maria@company.com
Lucia Fernandez Design 2
Roberto Silva Marketing 4

Nested Tables with Pagination

Nested table can also have its own pagination and features.

Name
Department
Team Size
Diego Martinez Engineering 3
Lucia Fernandez Design 2
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.
Product
Qty
Notebook 4
Monitor 2
Chair 6
Total items: 13
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.
Active
Hakumi Signal
Event intelligence service orchestrating over 18 providers.
Shipping
Hakumi Atlas
Customer data backbone with governance baked in from day zero.
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
  • 1
  • 10 / page
    10 / page
    20 / page
    50 / page
    100 / page
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 }.