Components

Lato provides a set of reusable functional components.
By using Lato components, you can save development time and simplify maintenance tasks.
All components are available via the Lato::ComponentsHelper helper.

Source code: Lato::ComponentsHelper

Navbar

Navbar nav item

Component to render a menu item in the navbar.
Parameters:
- key: the menu item's key. Used to activate the item via the active_navbar function from the Lato::Layoutable concern.
- path: a path or absolute URL to redirect the user.
- &block: block of HTML to render inside the menu item.

<%= lato_navbar_nav_item :account, lato.account_path do %>
  Account
<% end %>

Sidebar

Sidebar nav item

Component to render a menu item in the sidebar.
Parameters:
- key: the menu item's key. Used to activate the item via the active_sidebar function from the Lato::Layoutable concern.
- path: a path or absolute URL to redirect the user.
- &block: block of HTML to render inside the menu item.

<%= lato_sidebar_nav_item :account, lato.account_path do %>
  Account
<% end %>

Page

Page head

<%= lato_page_head 'Page title' %>

Page title

<%= lato_page_head 'Page title', [{ label: 'Title 1', path: main_app.page_path }, { label: 'Title 2' }] %>

Page title

<%= lato_page_head 'Page title' do %>
  <p class="lead">Welcome to this page</p>
<% end %>

Page title

Welcome to this page

Form

Forms can be generated using the lato_form components.
lato_form components are a set of utilities designed to work with Rails' form_with helper and the Stimulus controller lato_form_controller.
Below is a list of available components with usage examples:

  • lato_form_notices displays success feedback (notices set by the controller action).
  • lato_form_errors displays error feedback (based on model instance errors).
  • lato_form_item_label renders the field label.
  • lato_form_item_input_text renders a text input.
  • lato_form_item_input_number renders a number input.
  • lato_form_item_input_email renders an email input.
  • lato_form_item_input_password renders a password input.
  • lato_form_item_input_check renders a checkbox input.
  • lato_form_item_input_select renders a select input.
  • lato_form_item_input_file renders a file input.
  • lato_form_item_input_textarea renders a textarea input.
  • lato_form_item_input_date renders a date input.
  • lato_form_item_input_datetime renders a datetime input.
  • lato_form_item_input_time renders a time input.
  • lato_form_item_input_color renders a color input.
  • lato_form_submit renders the submit button.
<%

user ||= @session.user

%>

<%= turbo_frame_tag 'tutorial_form-example' do %>
  <%= form_with model: user, url: main_app.components_update_user_action_path, data: {
    turbo_frame: '_self',
    controller: 'lato-form'
  } do |form| %>
    <%= lato_form_notices class: %w[mb-3] %>
    <%= lato_form_errors user, class: %w[mb-3] %>

    <div class="row">
      <div class="col col-12 col-lg-6 mb-3">
        <%= lato_form_item_label form, :first_name, 'Name' %>
        <%= lato_form_item_input_text form, :first_name, required: true %>
      </div>

      <div class="col col-12 col-lg-6 mb-3">
        <%= lato_form_item_label form, :last_name, 'Surname' %>
        <%= lato_form_item_input_text form, :last_name, required: true %>
      </div>
    </div>

    <div class="row">
      <div class="col col-12 col-lg-6 mb-3">
        <%= lato_form_item_label form, :select, 'Select' %>
        <%= lato_form_item_input_select form, :select, [['Type 1', '1'], ['Type 2', '2']], required: true %>
      </div>

      <div class="col col-12 col-lg-6 mb-3">
        <%= lato_form_item_label form, :file, 'File' %>
        <%= lato_form_item_input_file form, :file, required: true %>
      </div>
    </div>

    <div class="row">
      <div class="col col-12 mb-3">
        <%= lato_form_item_label form, :textarea, 'Textarea' %>
        <%= lato_form_item_input_textarea form, :textarea, required: true %>
      </div>
    </div>

    <div class="row">
      <div class="col col-12 col-lg-4 mb-3">
        <%= lato_form_item_label form, :date, 'Date' %>
        <%= lato_form_item_input_date form, :date, required: true %>
      </div>

      <div class="col col-12 col-lg-4 mb-3">
        <%= lato_form_item_label form, :datetime, 'Datetime' %>
        <%= lato_form_item_input_datetime form, :datetime, required: true %>
      </div>

      <div class="col col-12 col-lg-4 mb-3">
        <%= lato_form_item_label form, :time, 'Time' %>
        <%= lato_form_item_input_time form, :time, required: true %>
      </div>
    </div>

    <div class="row">
      <div class="col col-12 mb-3">
        <%= lato_form_item_label form, :color, 'Color' %>
        <%= lato_form_item_input_color form, :color, required: true %>
      </div>
    </div>

    <div class="row">
      <div class="col col-12 mb-3">
        <%= lato_form_item_label form, :first_name, 'Name with auto-complete' %>
        <%= lato_form_item_input_text form, :first_name, required: true, data: {
          controller: 'lato-input-autocomplete',
          lato_input_autocomplete_path_value: products_autocomplete_path
        } %>
      </div>
    </div>

    <div class="row">
      <div class="col col-12 mb-3">
        <%= lato_form_item_label form, :first_name, 'Name with auto-complete V2' %>
        <%= lato_form_item_input_text form, :first_name, required: true, data: {
          controller: 'lato-input-autocomplete2',
          lato_input_autocomplete2_path_value: products_autocomplete_path
        } %>
      </div>
    </div>

    <div class="d-flex justify-content-end">
      <%= lato_form_submit form, 'Update', class: %w[btn-success] %>
    </div>
  <% end %>
<% end %>

Index

The lato_index component allows easy visualization of database records.

Index (front-end)

The simplest way to use the component is to pass it an ActiveRecord collection:

<%= lato_index Product.all.limit(5) %>
Id
Code
Status
Lato user
Created at
Updated at
Product parent
1 S9325236E Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
2 S7139673H Created
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
3 S7490915I Completed
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
4 T0364491F Cancelled
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
5 S6895023F Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
5 total results

Customizing columns

Columns can be customized using the :columns parameter:

<%= lato_index Product.all.limit(5), columns: %i[id code status] %>
Id
Code
Status
1 S9325236E Cancelled
2 S7139673H Created
3 S7490915I Completed
4 T0364491F Cancelled
5 S6895023F Cancelled
5 total results

Notes

Column titles can be customized using Rails translation files.
Example:

en:
  activerecord:
    attributes:
      product:
        code: Code
        status: Status

Customizing column content

You can customize column content by creating helper methods with the format model_column(record).
For example, to customize the output of the :lato_user_id column, define the following helper in ProductsHelper:

class ProductsHelper
  def product_lato_user_id(product)
    product.lato_user.full_name
  end
end

Adding custom columns

To add custom columns, declare a method in the model that provides the value.
For example, after adding a lifetime method to the Product model, you can use the :lifetime column:

class Product < ApplicationRecord
  def lifetime
    Time.now - created_at
  end
end
class ProductsHelper
  def product_lifetime(product)
    Time.at(product.lifetime).utc.strftime('%H h %M m')
  end
end
<%= lato_index Product.all.limit(5), columns: %i[id code status lifetime] %>
Id
Code
Status
Lifetime
1 S9325236E Cancelled 17 h 54 m
2 S7139673H Created 17 h 54 m
3 S7490915I Completed 17 h 54 m
4 T0364491F Cancelled 17 h 54 m
5 S6895023F Cancelled 17 h 54 m
5 total results

Notes

Custom columns can also be translated using Rails translation files, just like database-backed columns.

To add columns that don't map to real attributes, use attr_accessor :column_name instead of defining a method in the model:

class Product < ApplicationRecord
  attr_accessor :lifetime
end

Adding actions

<%= lato_index Product.all.limit(5), custom_actions: {
    create: {
      path: products_create_path,
      icon: 'bi bi-plus',
      aria_label: 'Create product',
      title: 'Create product'
    }
 } %>
Id
Code
Status
Lato user
Created at
Updated at
Product parent
1 S9325236E Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
2 S7139673H Created
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
3 S7490915I Completed
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
4 T0364491F Cancelled
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
5 S6895023F Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
5 total results

Adding in-page actions

<%= lato_index Product.all.limit(5), custom_actions: {
    create: {
      path: products_create_path,
      icon: 'bi bi-plus',
      data: { lato_action_target: 'trigger', turbo_frame: dom_id(Product.new, 'form') },
      aria_label: 'Create product',
      title: 'Create product'
    }
 } %>
Id
Code
Status
Lato user
Created at
Updated at
Product parent
1 S9325236E Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
2 S7139673H Created
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
3 S7490915I Completed
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
4 T0364491F Cancelled
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
5 S6895023F Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
5 total results

Notes

The :create_turbo_frame option specifies the turbo-frame ID in the products_create_path page that should be inserted into the overlay.

Adding row-specific actions

Row-specific actions can be added through custom columns (as shown above).
These actions can also be opened in-page via overlays using data-lato-index-target="action" and data-turbo-frame="turbo-frame-ID".

class ProductsHelper
  def product_actions(product)
    content_tag(:div, class: 'btn-group btn-group-sm') do
      concat link_to(
        'Edit',
        products_update_path(product),
        class: 'btn btn-primary',
        data: { lato_action_target: 'trigger', turbo_frame: dom_id(product, 'form') }
      )
    end
  end
end
<%= lato_index Product.all.limit(5), columns: %i[id code status lifetime actions] %>
Id
Code
Status
Lifetime
Actions
1 S9325236E Cancelled 17 h 54 m
2 S7139673H Created 17 h 54 m
3 S7490915I Completed 17 h 54 m
4 T0364491F Cancelled 17 h 54 m
5 S6895023F Cancelled 17 h 54 m
5 total results

Index (back-end)

To make the index fully interactive, use the lato_index_collection function from the Lato::Componentable concern in the controller.

def index
  @products = lato_index_collection(Product.all, pagination: true)
end
<%= lato_index @products %>
Id
Code
Status
Lato user
Created at
Updated at
Product parent
1 S9325236E Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
2 S7139673H Created
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
3 S7490915I Completed
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
4 T0364491F Cancelled
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
5 S6895023F Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
6 S6912488G Completed
User User
27/06/2025 2025-06-27 17:18:36 UTC -
7 S8592863E Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
8 S6753571E Completed
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
9 T0139094A In progress
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
10 S6837583E Cancelled
User User
27/06/2025 2025-06-27 17:18:36 UTC -
11 S7355585Z Created
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
12 S6249070E Created
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
13 T0698020H In progress
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
14 S8096808F In progress
User User
27/06/2025 2025-06-27 17:18:36 UTC -
15 S8433075B Cancelled
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
16 T0167519I Cancelled
User User
27/06/2025 2025-06-27 17:18:36 UTC -
17 S7273832B Cancelled
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
18 T0578210J Completed
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
19 S9287663B Created
Franco Pippo
27/06/2025 2025-06-27 17:18:36 UTC -
20 S8913885Z Cancelled
User User
27/06/2025 2025-06-27 17:18:36 UTC -
21 S6223725B Cancelled
User User
27/06/2025 2025-06-27 17:18:36 UTC -
22 S9058609B Created
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
23 S7544973I Completed
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
24 T0793249E Completed
Abbondo Mimmo
27/06/2025 2025-06-27 17:18:36 UTC -
25 T0616976C Cancelled
User User
27/06/2025 2025-06-27 17:18:36 UTC -
100 total results

Customizing columns

def index
  @products_columns = lato_index_collection(
    Product.all,
    pagination: true,
    columns: %i[code status id]
  )
end
<%= lato_index @products_columns %>
Code
Status
Id
S9325236E Cancelled 1
S7139673H Created 2
S7490915I Completed 3
T0364491F Cancelled 4
S6895023F Cancelled 5
S6912488G Completed 6
S8592863E Cancelled 7
S6753571E Completed 8
T0139094A In progress 9
S6837583E Cancelled 10
S7355585Z Created 11
S6249070E Created 12
T0698020H In progress 13
S8096808F In progress 14
S8433075B Cancelled 15
T0167519I Cancelled 16
S7273832B Cancelled 17
T0578210J Completed 18
S9287663B Created 19
S8913885Z Cancelled 20
S6223725B Cancelled 21
S9058609B Created 22
S7544973I Completed 23
T0793249E Completed 24
T0616976C Cancelled 25
100 total results

Customizing pagination size

Users can choose how many items to display per page using a selector enabled via the :pagination_options parameter in lato_index_collection.

def index
  @products_pagination_options = lato_index_collection(
    Product.all,
    pagination: 10,
    columns: %i[code status id],
    key: 'products_pagination_options'
  )
end
<%= lato_index @products_pagination_options, pagination_options: [10,20,50,100] %>
Code
Status
Id
S9325236E Cancelled 1
S7139673H Created 2
S7490915I Completed 3
T0364491F Cancelled 4
S6895023F Cancelled 5
S6912488G Completed 6
S8592863E Cancelled 7
S6753571E Completed 8
T0139094A In progress 9
S6837583E Cancelled 10
100 total results

Making columns sortable

def index
  @products_sortable_columns = lato_index_collection(
    Product.all,
    pagination: true,
    columns: %i[code status lato_user_id],
    sortable_columns: %i[code status lato_user_id]
  )
end
<%= lato_index @products_sortable_columns %>
Code
Status
Lato user
S9325236E Cancelled
Abbondo Mimmo
S7139673H Created
Abbondo Mimmo
S7490915I Completed
Franco Pippo
T0364491F Cancelled
Franco Pippo
S6895023F Cancelled
Abbondo Mimmo
S6912488G Completed
User User
S8592863E Cancelled
Abbondo Mimmo
S6753571E Completed
Franco Pippo
T0139094A In progress
Abbondo Mimmo
S6837583E Cancelled
User User
S7355585Z Created
Abbondo Mimmo
S6249070E Created
Franco Pippo
T0698020H In progress
Abbondo Mimmo
S8096808F In progress
User User
S8433075B Cancelled
Abbondo Mimmo
T0167519I Cancelled
User User
S7273832B Cancelled
Franco Pippo
T0578210J Completed
Abbondo Mimmo
S9287663B Created
Franco Pippo
S8913885Z Cancelled
User User
S6223725B Cancelled
User User
S9058609B Created
Abbondo Mimmo
S7544973I Completed
Abbondo Mimmo
T0793249E Completed
Abbondo Mimmo
T0616976C Cancelled
User User
100 total results

Notes

To customize sorting logic, define the lato_index_order scope inside the model.
Example:

class Product < ApplicationRecord
  belongs_to :lato_user, class_name: 'Lato::User'

  scope :lato_index_order, ->(column, order) do
    return joins(:lato_user).order("lato_users.last_name #{order}, lato_users.first_name #{order}") if column == :lato_user_id

    order("#{column} #{order}")
  end
end

Notes

To set a default sorting column, use the default_sort_by option in the lato_index_collection method.
Example:

def index
  @products_default_sort = lato_index_collection(
    Product.all,
    pagination: true,
    columns: %i[code status lato_user_id],
    sortable_columns: %i[code status lato_user_id],
    default_sort_by: 'code|asc' # 'column|direction'
  )
end

Making columns searchable

def index
  @products_searchable_columns = lato_index_collection(
    Product.all,
    pagination: true,
    columns: %i[code status lato_user_id],
    sortable_columns: %i[code status lato_user_id],
    searchable_columns: %i[code lato_user_id]
  )
end
<%= lato_index @products_searchable_columns %>
Code
Status
Lato user
S9325236E Cancelled
Abbondo Mimmo
S7139673H Created
Abbondo Mimmo
S7490915I Completed
Franco Pippo
T0364491F Cancelled
Franco Pippo
S6895023F Cancelled
Abbondo Mimmo
S6912488G Completed
User User
S8592863E Cancelled
Abbondo Mimmo
S6753571E Completed
Franco Pippo
T0139094A In progress
Abbondo Mimmo
S6837583E Cancelled
User User
S7355585Z Created
Abbondo Mimmo
S6249070E Created
Franco Pippo
T0698020H In progress
Abbondo Mimmo
S8096808F In progress
User User
S8433075B Cancelled
Abbondo Mimmo
T0167519I Cancelled
User User
S7273832B Cancelled
Franco Pippo
T0578210J Completed
Abbondo Mimmo
S9287663B Created
Franco Pippo
S8913885Z Cancelled
User User
S6223725B Cancelled
User User
S9058609B Created
Abbondo Mimmo
S7544973I Completed
Abbondo Mimmo
T0793249E Completed
Abbondo Mimmo
T0616976C Cancelled
User User
100 total results

Notes

To customize search logic, define the lato_index_search scope inside the model.
Example:

class Product < ApplicationRecord
  belongs_to :lato_user, class_name: 'Lato::User'

  scope :lato_index_search, ->(search) do
    joins(:lato_user).where("
      lower(code) LIKE :search OR
      lower(lato_users.first_name) LIKE :search OR
      lower(lato_users.last_name) LIKE :search
    ", search: "%#{search.downcase.strip}%")
  end
end

Skip total count for large tables

For large tables, you can skip the total count to improve performance by setting the :skip_total_count option to true in the lato_index method. This is useful when you don't need the total count of records, such as when displaying a large collection.

<%= lato_index @products_skip_total_count_columns, skip_total_count: true %>
Code
Status
Lato user
S9325236E Cancelled
Abbondo Mimmo
S7139673H Created
Abbondo Mimmo
S7490915I Completed
Franco Pippo
T0364491F Cancelled
Franco Pippo
S6895023F Cancelled
Abbondo Mimmo
S6912488G Completed
User User
S8592863E Cancelled
Abbondo Mimmo
S6753571E Completed
Franco Pippo
T0139094A In progress
Abbondo Mimmo
S6837583E Cancelled
User User
S7355585Z Created
Abbondo Mimmo
S6249070E Created
Franco Pippo
T0698020H In progress
Abbondo Mimmo
S8096808F In progress
User User
S8433075B Cancelled
Abbondo Mimmo
T0167519I Cancelled
User User
S7273832B Cancelled
Franco Pippo
T0578210J Completed
Abbondo Mimmo
S9287663B Created
Franco Pippo
S8913885Z Cancelled
User User
S6223725B Cancelled
User User
S9058609B Created
Abbondo Mimmo
S7544973I Completed
Abbondo Mimmo
T0793249E Completed
Abbondo Mimmo
T0616976C Cancelled
User User
You are offline You are online