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 S7477724D Completed
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
2 S9887312J Completed
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
3 S8425501G Created
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
4 S6672791B Created
User User
26/11/2025 2025-11-26 08:53:35 UTC -
5 S7532806J Completed
User User
26/11/2025 2025-11-26 08:53:35 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 S7477724D Completed
2 S9887312J Completed
3 S8425501G Created
4 S6672791B Created
5 S7532806J Completed
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 S7477724D Completed 02 h 51 m
2 S9887312J Completed 02 h 51 m
3 S8425501G Created 02 h 51 m
4 S6672791B Created 02 h 51 m
5 S7532806J Completed 02 h 51 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 S7477724D Completed
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
2 S9887312J Completed
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
3 S8425501G Created
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
4 S6672791B Created
User User
26/11/2025 2025-11-26 08:53:35 UTC -
5 S7532806J Completed
User User
26/11/2025 2025-11-26 08:53:35 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 S7477724D Completed
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
2 S9887312J Completed
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
3 S8425501G Created
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
4 S6672791B Created
User User
26/11/2025 2025-11-26 08:53:35 UTC -
5 S7532806J Completed
User User
26/11/2025 2025-11-26 08:53:35 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 S7477724D Completed 02 h 51 m
2 S9887312J Completed 02 h 51 m
3 S8425501G Created 02 h 51 m
4 S6672791B Created 02 h 51 m
5 S7532806J Completed 02 h 51 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 S7477724D Completed
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
2 S9887312J Completed
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
3 S8425501G Created
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
4 S6672791B Created
User User
26/11/2025 2025-11-26 08:53:35 UTC -
5 S7532806J Completed
User User
26/11/2025 2025-11-26 08:53:35 UTC -
6 S6555498D Created
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
7 T0228628E Completed
User User
26/11/2025 2025-11-26 08:53:35 UTC -
8 T0366735E In progress
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
9 T0417023C Cancelled
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
10 T0221424A Completed
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
11 S9796775Z Cancelled
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
12 S6336653F Completed
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
13 S8532581G Completed
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
14 S7396763E Created
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
15 S7225126A Cancelled
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
16 S9587491F In progress
User User
26/11/2025 2025-11-26 08:53:35 UTC -
17 S6832872A In progress
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
18 S6953631Z Completed
User User
26/11/2025 2025-11-26 08:53:35 UTC -
19 S7961624I Completed
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
20 S6412325D In progress
User User
26/11/2025 2025-11-26 08:53:35 UTC -
21 S9524411D Cancelled
User User
26/11/2025 2025-11-26 08:53:35 UTC -
22 S7170470Z Completed
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
23 S7392069H Cancelled
Abbondo Mimmo
26/11/2025 2025-11-26 08:53:35 UTC -
24 S8582646H Completed
User User
26/11/2025 2025-11-26 08:53:35 UTC -
25 S6149814A In progress
Franco Pippo
26/11/2025 2025-11-26 08:53:35 UTC -
101 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
S7477724D Completed 1
S9887312J Completed 2
S8425501G Created 3
S6672791B Created 4
S7532806J Completed 5
S6555498D Created 6
T0228628E Completed 7
T0366735E In progress 8
T0417023C Cancelled 9
T0221424A Completed 10
S9796775Z Cancelled 11
S6336653F Completed 12
S8532581G Completed 13
S7396763E Created 14
S7225126A Cancelled 15
S9587491F In progress 16
S6832872A In progress 17
S6953631Z Completed 18
S7961624I Completed 19
S6412325D In progress 20
S9524411D Cancelled 21
S7170470Z Completed 22
S7392069H Cancelled 23
S8582646H Completed 24
S6149814A In progress 25
101 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
S7477724D Completed 1
S9887312J Completed 2
S8425501G Created 3
S6672791B Created 4
S7532806J Completed 5
S6555498D Created 6
T0228628E Completed 7
T0366735E In progress 8
T0417023C Cancelled 9
T0221424A Completed 10
101 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
S7477724D Completed
Abbondo Mimmo
S9887312J Completed
Franco Pippo
S8425501G Created
Abbondo Mimmo
S6672791B Created
User User
S7532806J Completed
User User
S6555498D Created
Franco Pippo
T0228628E Completed
User User
T0366735E In progress
Abbondo Mimmo
T0417023C Cancelled
Abbondo Mimmo
T0221424A Completed
Franco Pippo
S9796775Z Cancelled
Abbondo Mimmo
S6336653F Completed
Franco Pippo
S8532581G Completed
Franco Pippo
S7396763E Created
Franco Pippo
S7225126A Cancelled
Abbondo Mimmo
S9587491F In progress
User User
S6832872A In progress
Franco Pippo
S6953631Z Completed
User User
S7961624I Completed
Abbondo Mimmo
S6412325D In progress
User User
S9524411D Cancelled
User User
S7170470Z Completed
Abbondo Mimmo
S7392069H Cancelled
Abbondo Mimmo
S8582646H Completed
User User
S6149814A In progress
Franco Pippo
101 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
S7477724D Completed
Abbondo Mimmo
S9887312J Completed
Franco Pippo
S8425501G Created
Abbondo Mimmo
S6672791B Created
User User
S7532806J Completed
User User
S6555498D Created
Franco Pippo
T0228628E Completed
User User
T0366735E In progress
Abbondo Mimmo
T0417023C Cancelled
Abbondo Mimmo
T0221424A Completed
Franco Pippo
S9796775Z Cancelled
Abbondo Mimmo
S6336653F Completed
Franco Pippo
S8532581G Completed
Franco Pippo
S7396763E Created
Franco Pippo
S7225126A Cancelled
Abbondo Mimmo
S9587491F In progress
User User
S6832872A In progress
Franco Pippo
S6953631Z Completed
User User
S7961624I Completed
Abbondo Mimmo
S6412325D In progress
User User
S9524411D Cancelled
User User
S7170470Z Completed
Abbondo Mimmo
S7392069H Cancelled
Abbondo Mimmo
S8582646H Completed
User User
S6149814A In progress
Franco Pippo
101 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
S7477724D Completed
Abbondo Mimmo
S9887312J Completed
Franco Pippo
S8425501G Created
Abbondo Mimmo
S6672791B Created
User User
S7532806J Completed
User User
S6555498D Created
Franco Pippo
T0228628E Completed
User User
T0366735E In progress
Abbondo Mimmo
T0417023C Cancelled
Abbondo Mimmo
T0221424A Completed
Franco Pippo
S9796775Z Cancelled
Abbondo Mimmo
S6336653F Completed
Franco Pippo
S8532581G Completed
Franco Pippo
S7396763E Created
Franco Pippo
S7225126A Cancelled
Abbondo Mimmo
S9587491F In progress
User User
S6832872A In progress
Franco Pippo
S6953631Z Completed
User User
S7961624I Completed
Abbondo Mimmo
S6412325D In progress
User User
S9524411D Cancelled
User User
S7170470Z Completed
Abbondo Mimmo
S7392069H Cancelled
Abbondo Mimmo
S8582646H Completed
User User
S6149814A In progress
Franco Pippo
You are offline You are online