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.

There are also some extra parameters to customize the menu item:
- external (default: false): if true, an icon is added to indicate that the link opens in a new tab and a target="_blank" attribute is added to the link.
- children (default: nil): an array of hashes representing submenu items. Each hash should contain the keys :key, :label, and :path. If this parameter is provided, the menu item will have a collapsible submenu.

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

<%= lato_sidebar_nav_item :github, 'https://github.com/Lato-org/lato', external: true do %>
   GitHub
<% end %>

<%= lato_sidebar_nav_item :examples, '#', children: [
  { key: :products, label: 'CRUD Example', path: main_app.products_path },
  { key: :other, label: 'Other Examples soon..', path: '#' },
] do %>
  Examples
<% 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_file_dropzone renders a file input with drag-and-drop support.
  • 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="row">
      <div class="col col-12 mb-3">
        <%= lato_form_item_label form, :file_dropzone, 'File Dropzone' %>
        <%= lato_form_item_input_file_dropzone form, :file_dropzone, required: true %>
      </div>
    </div>

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

Drag and drop files here or click to upload

Nested attributes

Lato provides support for nested attributes in forms using the lato_form_item_input_list component.
This component allows you to easily manage collections of associated records within a parent form.

The lato_form_item_input_list component requires three parameters:
- form: the form builder object.
- key: the attribute name representing the collection of associated records.
- partial_path: the path to the partial that will be used to render each item in the collection.
- partial_params (optional): a hash of additional parameters to pass to the partial.

The lato_form_item_input_list component will render the list of items associated to the form object record and a button to add new items.
Each item will be rendered using the specified partial. The partial will receive the following local variables:
- form: the form builder object for the item.
- *partial_params: any additional parameters passed to the component.

The partial used to render each item should contain only the fields related to the item.
Other hidden fields (such as the item ID) and the remove button are automatically handled by the component.

<%= lato_form_item_input_list(form, :product_items, 'products/input_list_product_item') %>

Example: main form
Example: form item partial
Example: controller params
Example: model configuration


Data

Data badge

The lato_data_badge component allows easy rendering of badges with different styles.

<%= lato_data_badge "badge primary", "primary" %>
<%= lato_data_badge "badge secondary", "secondary" %>
<%= lato_data_badge "badge info", "info" %>
<%= lato_data_badge "badge success", "success" %>
<%= lato_data_badge "badge warning", "warning" %>
<%= lato_data_badge "badge danger", "danger" %>
<%= lato_data_badge "badge light", "light" %>
<%= lato_data_badge "badge dark", "dark" %>
badge primary badge secondary badge info badge success badge warning badge danger badge light badge dark

Data user

The lato_data_user component allows easy rendering of user information with avatar and full name.

<%= lato_data_user("Mr. Wolf") %>
<%= lato_data_user("Ms. White", "https://i.pravatar.cc/40?img=5") %>
Mr. Wolf
Ms. White

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 S8950503H Completed
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
2 S8425788E Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
3 S8221805Z In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
4 S9281622B In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
5 S6571882J Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 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 S8950503H Completed
2 S8425788E Created
3 S8221805Z In progress
4 S9281622B In progress
5 S6571882J Created
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 S8950503H Completed 10 h 41 m
2 S8425788E Created 10 h 41 m
3 S8221805Z In progress 10 h 41 m
4 S9281622B In progress 10 h 41 m
5 S6571882J Created 10 h 41 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 S8950503H Completed
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
2 S8425788E Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
3 S8221805Z In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
4 S9281622B In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
5 S6571882J Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 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 S8950503H Completed
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
2 S8425788E Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
3 S8221805Z In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
4 S9281622B In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
5 S6571882J Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 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 S8950503H Completed 10 h 41 m
2 S8425788E Created 10 h 41 m
3 S8221805Z In progress 10 h 41 m
4 S9281622B In progress 10 h 41 m
5 S6571882J Created 10 h 41 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 S8950503H Completed
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
2 S8425788E Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
3 S8221805Z In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
4 S9281622B In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
5 S6571882J Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
6 S6373562J Created
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
7 S8759199I Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
8 S8335098I In progress
User User
18/12/2025 2025-12-18 19:39:20 UTC -
9 S6236311H Created
User User
18/12/2025 2025-12-18 19:39:20 UTC -
10 T0249997A In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
11 S7326672F Completed
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
12 S8881606D Created
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
13 S6914158G Cancelled
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
14 S8469185B Completed
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
15 S6819189J Completed
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
16 S9715911D Cancelled
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
17 S6833467E In progress
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
18 S7775967J Created
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
19 S9824894C Completed
User User
18/12/2025 2025-12-18 19:39:20 UTC -
20 S8280767E Cancelled
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
21 S8990933C Completed
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
22 S6999684A Completed
Abbondo Mimmo
18/12/2025 2025-12-18 19:39:20 UTC -
23 S7230957Z Cancelled
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
24 S7685046A Completed
Franco Pippo
18/12/2025 2025-12-18 19:39:20 UTC -
25 S7431239Z Completed
User User
18/12/2025 2025-12-18 19:39:20 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
S8950503H Completed 1
S8425788E Created 2
S8221805Z In progress 3
S9281622B In progress 4
S6571882J Created 5
S6373562J Created 6
S8759199I Created 7
S8335098I In progress 8
S6236311H Created 9
T0249997A In progress 10
S7326672F Completed 11
S8881606D Created 12
S6914158G Cancelled 13
S8469185B Completed 14
S6819189J Completed 15
S9715911D Cancelled 16
S6833467E In progress 17
S7775967J Created 18
S9824894C Completed 19
S8280767E Cancelled 20
S8990933C Completed 21
S6999684A Completed 22
S7230957Z Cancelled 23
S7685046A Completed 24
S7431239Z Completed 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
S8950503H Completed 1
S8425788E Created 2
S8221805Z In progress 3
S9281622B In progress 4
S6571882J Created 5
S6373562J Created 6
S8759199I Created 7
S8335098I In progress 8
S6236311H Created 9
T0249997A In progress 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
S8950503H Completed
Abbondo Mimmo
S8425788E Created
Franco Pippo
S8221805Z In progress
Abbondo Mimmo
S9281622B In progress
Abbondo Mimmo
S6571882J Created
Franco Pippo
S6373562J Created
Abbondo Mimmo
S8759199I Created
Franco Pippo
S8335098I In progress
User User
S6236311H Created
User User
T0249997A In progress
Abbondo Mimmo
S7326672F Completed
Abbondo Mimmo
S8881606D Created
Franco Pippo
S6914158G Cancelled
Abbondo Mimmo
S8469185B Completed
Franco Pippo
S6819189J Completed
Franco Pippo
S9715911D Cancelled
Abbondo Mimmo
S6833467E In progress
Abbondo Mimmo
S7775967J Created
Abbondo Mimmo
S9824894C Completed
User User
S8280767E Cancelled
Franco Pippo
S8990933C Completed
Abbondo Mimmo
S6999684A Completed
Abbondo Mimmo
S7230957Z Cancelled
Franco Pippo
S7685046A Completed
Franco Pippo
S7431239Z Completed
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
S8950503H Completed
Abbondo Mimmo
S8425788E Created
Franco Pippo
S8221805Z In progress
Abbondo Mimmo
S9281622B In progress
Abbondo Mimmo
S6571882J Created
Franco Pippo
S6373562J Created
Abbondo Mimmo
S8759199I Created
Franco Pippo
S8335098I In progress
User User
S6236311H Created
User User
T0249997A In progress
Abbondo Mimmo
S7326672F Completed
Abbondo Mimmo
S8881606D Created
Franco Pippo
S6914158G Cancelled
Abbondo Mimmo
S8469185B Completed
Franco Pippo
S6819189J Completed
Franco Pippo
S9715911D Cancelled
Abbondo Mimmo
S6833467E In progress
Abbondo Mimmo
S7775967J Created
Abbondo Mimmo
S9824894C Completed
User User
S8280767E Cancelled
Franco Pippo
S8990933C Completed
Abbondo Mimmo
S6999684A Completed
Abbondo Mimmo
S7230957Z Cancelled
Franco Pippo
S7685046A Completed
Franco Pippo
S7431239Z Completed
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
S8950503H Completed
Abbondo Mimmo
S8425788E Created
Franco Pippo
S8221805Z In progress
Abbondo Mimmo
S9281622B In progress
Abbondo Mimmo
S6571882J Created
Franco Pippo
S6373562J Created
Abbondo Mimmo
S8759199I Created
Franco Pippo
S8335098I In progress
User User
S6236311H Created
User User
T0249997A In progress
Abbondo Mimmo
S7326672F Completed
Abbondo Mimmo
S8881606D Created
Franco Pippo
S6914158G Cancelled
Abbondo Mimmo
S8469185B Completed
Franco Pippo
S6819189J Completed
Franco Pippo
S9715911D Cancelled
Abbondo Mimmo
S6833467E In progress
Abbondo Mimmo
S7775967J Created
Abbondo Mimmo
S9824894C Completed
User User
S8280767E Cancelled
Franco Pippo
S8990933C Completed
Abbondo Mimmo
S6999684A Completed
Abbondo Mimmo
S7230957Z Cancelled
Franco Pippo
S7685046A Completed
Franco Pippo
S7431239Z Completed
User User
You are offline You are online