Skip to main content

Empty Header Helper

Use empty_header when a table column header should appear visually empty, but still needs an accessible label for screen readers.

This is most common for action columns (for example: Edit/Delete buttons).

Why use this helper

  • Keeps table headers semantically correct (<th> with a valid scope)
  • Adds accessible labeling for assistive technology
  • Prevents invalid scope values
  • Supports utility classes for width/alignment

Method signature

empty_header(*classes, text: nil, scope: 'col')

Arguments

  • *classes (String | Symbol, splat): Optional CSS classes added to the <th> element.
  • text (String | nil): Optional hidden text label. If present, it renders inside <span class="sr-only">...</span>.
  • scope (String | Symbol): Header scope. Allowed values are col, row, colgroup, and rowgroup.

Validation rules

  • You must provide text.
  • If argument is blank/nil, the helper raises:
ArgumentError: '`text` must be provided'
  • If scope is not one of the allowed values, the helper raises:
ArgumentError: 'Scope must be one of: `col`, `row`, `colgroup`, `rowgroup`'

Basic usage in a table

<table class="table">
 <thead>
  <tr>
   <th scope="col">Name</th>
   <th scope="col">Status</th>
    <%= empty_header('w-0 text-nowrap', text: 'Actions') %>
  </tr>
 </thead>
 <tbody>
  <% @users.each do |user| %>
   <tr>
    <td><%= user.name %></td>
    <td><%= user.status %></td>
    <td class="text-nowrap">
     <%= link_to 'Edit', edit_user_path(user), class: 'btn btn-sm btn-secondary' %>
    </td>
   </tr>
  <% end %>
 </tbody>
</table>

Row header usage

When used for row headers, set scope: 'row':

<%= empty_header(text: 'Row actions', scope: 'row') %>

Accessibility guidance

  • Prefer short, descriptive labels like Actions, Row actions, or Select row
  • Keep labels consistent across similar tables
  • Do not leave action columns without an accessible header label