# How To

This page contains the practical tips and examples to get the job done with Pagy. If there is something missing, or some topic that you think should be added, fixed or explained better, please open an issue.

# Control the items per page

You can control the items per page with the limit variable. (Default 20)

You can set its default in the pagy.rb initializer (see How to configure pagy). For example:

pagy.rb (initializer)
Pagy::DEFAULT[:limit] = 25

You can also pass it as an instance variable to the Pagy.new method or to the pagy controller method:

@pagy, @records = pagy(Product.all, limit: 30)

See also a couple of extras that handle the :limit in some special way:

  • gearbox: Automatically change the limit per page depending on the page number
  • limit: Allow the client to request a custom :limit (i.e. records per page) with an optional selector UI

# Control the page links

You can control the number and position of the page links in the navigation through the :size variable or override the series method.

You can set the :size variable to an Integer representing the maximum page/gap slots rendered. The current page will be placed as centered as possible in the series. For that reason, :size works better when it's an odd number.

For example:

# size < 7
pagy = Pagy.new(count: 1000, page: 10, size: 5)
pagy.series
#=> [8, 9, "10", 11, 12]
pagy = Pagy.new(count: 1000, page: 2, size: 5)
pagy.series
#=> [1, "2", 3, 4, 5]
pagy = Pagy.new(count: 1000, page: 99, size: 5)
pagy.series
#=> [96, 97, 98, "99", 100] 

# size >= 7 (first, last and gap added)
pagy = Pagy.new(count: 1000, page: 10, size: 7)
pagy.series
#=> [1, :gap 9, "10", 11, :gap, 100]
pagy = Pagy.new(count: 1000, page: 2, size: 7)
pagy.series
#=> [1, "2", 3, 4, 5, :gap, 100]
pagy = Pagy.new(count: 1000, page: 99, size: 7)
pagy.series
#=> [1, :gap, 96, 97, 98, "99", 100] 

# size >= 7; ends: false (no ends added)
pagy = Pagy.new(count: 1000, page: 10, size: 7, ends: false)
pagy.series
#=> [7, 8, 9, "10", 11, 12, 13]
pagy = Pagy.new(count: 1000, page: 2, size: 7, ends: false)
pagy.series
#=> [1, "2", 3, 4, 5, 6, 7]
pagy = Pagy.new(count: 1000, page: 99, size: 7, ends: false)
pagy.series
#=> [94, 95, 96, 97, 98, "99", 100]

The fast nav uses a simpler and faster algorithm and the series length is more symmetrical and constant, it's cleaner and less confusing to the user. By default, if the size is at least 7, it will insert the first and last pages as first and last links in the bar, also adding the :gaps accordingly.

If you want to remove the first, last and gaps slots and show only a series of contiguous pages around the current one you can set the :ends variable to false. This is especially useful with Calendar nav bars.

See the size extra

If you want to skip the generation of the page links, just set the :size variable to 0:

pagy = Pagy.new count: 1000, size: 0 # etc
pagy.series
#=> []

If changing the :size is not enough for your requirements (e.g. if you need to add intermediate segments or midpoints in place of gaps) you should override the series method. See more details and examples here.

# Pass the page number

You don't need to explicitly pass the page number to the pagy method, because it is pulled in by the pagy_get_page (which is called internally by the pagy method). However you can force a page number by just passing it to the pagy method. For example:

controller
@pagy, @records = pagy(my_scope, page: 3) # force page #3

That will explicitly set the :page variable, overriding the default behavior (which pulls the page number from the params[:page] by default).

# Customize the dictionary

Pagy composes its output strings using standard i18n dictionaries. That can be used to change the language and customize your specific app.

Default en dictionary
# :one_other pluralization (see https://github.com/ddnexus/pagy/blob/master/gem/lib/pagy/i18n.rb)
en:
  pagy:
    aria_label:
      nav:
        one: "Page"
        other: "Pages"
      prev: "Previous"
      next: "Next"
    prev: "&lt;"
    next: "&gt;"
    gap: "&hellip;"
    item_name:
      one: "item"
      other: "items"
    info:
      no_items: "No %{item_name} found"
      single_page: "Displaying %{count} %{item_name}"
      multiple_pages: "Displaying %{item_name} %{from}-%{to} of %{count} in total"
    combo_nav_js: "Page %{page_input} of %{pages}"
    limit_selector_js: "Show %{limit_input} %{item_name} per page"

If you are ok with the default supported locale dictionaries just refer to Pagy::I18n.

If you want to customize the translations or some specific output, you should edit the relevant entries in the pagy dictionary.

If you explicitly use the i18n extra, override the pagy target entries in your own custom dictionary (refer to the I18n official documentation).

If you don't use the above extra (and rely on the pagy faster code) you can copy and edit the dictionary files that your app uses and configure pagy to use them.

pagy.rb (initializer)
# load the "en" and "de" locale defined in the custom files at :filepath:
Pagy::I18n.load({ locale: 'de', filepath: 'path/to/my-custom-de.yml' },
                { locale: 'en', filepath: 'path/to/my-custom-en.yml' })

# Customize the ARIA labels

You can customize the aria-label attributes of all the pagy helpers by passing the :aria_label string ( See pagy_nav)

You can also replace the pagy.aria_label.nav strings in the dictionary, as well as the pagy.aria_label.prev and the pagy.aria_label.next.

See more details in the ARIA attributes Page.

# Customize the page param

Pagy uses the :page_param variable to determine the param it should get the page number from and create the URL for. Its default is set as Pagy::DEFAULT[:page_param] = :page, hence it will get the page number from the params[:page] and will create page URLs like ./?page=3 by default.

You may want to customize that, for example to make it more readable in your language, or because you need to paginate different collections in the same action. Depending on the scope of the customization, you have a couple of options:

  1. Pagy::DEFAULT[:page_param] = :custom_param will be used as the global default
  2. pagy(collection, page_param: :custom_param) or Pagy.new(count:100, page_param: :custom_param) will be used for a single instance (overriding the global default)

You can also override the pagy_get_page if you need some special way to get the page number.

# Customize the link attributes

If you need to customize some HTML attribute of the page links, you may not need to override the pagy_nav* helper. It might be enough to pass some extra attribute string with the :anchor_string keyword argument. For example:

<%== pagy_nav(@pagy, anchor_string: 'data-remote="true"') %>

See more advanced details about The anchor_string argument

# Customize the params

When you need to add some custom param or alter the params embedded in the URLs of the page links, you can set the variable :params to a Hash of params to add to the URL, or a Proc that can edit/add/delete the request params.

If it is a Proc it will receive the key-stringified params hash complete with the page param and it should return a possibly modified version of it.

An example using except and merge!:

controller
@pagy, @records = pagy(collection, params: ->(params) { params.except('not_useful').merge!('custom' => 'useful') })

You can also use the :fragment keyword argument to add a fragment to the URLs of the pages:

view
<%== pagy_nav(@pagy, fragment: '#your-fragment') %>

# Customize the URL

When you need something more radical with the URL than just massaging the params, you should override the pagy_url_for right in your helper.

The following are a couple of examples.

The following is a Rails-specific alternative that supports fancy-routes (e.g. get 'your_route(/:page)' ... that produce paths like your_route/23 instead of your_route?page=23):

controller

def pagy_url_for(pagy, page, absolute: false)
  params = request.query_parameters.merge(pagy.vars[:page_param] => page, only_path: !absolute)
  url_for(params)
end

You may need to POST a very complex search form that would generate an URL potentially too long to be handled by a browser, and your page links may need to use POST and not GET. In that case you can try this simple solution:

controller

def pagy_url_for(_pagy, page, **_)
  page
end

That would produce links that look like e.g. <a href="2">2</a>. Then you can attach a javascript "click" event on the page links. When triggered, the href content (i.e. the page number) should get copied to a hidden "page" input and the form should be posted.

For a broader tutorial about this topic see Handling Pagination When POSTing Complex Search Forms by Ben Koshy.

# Customize the item name

The pagy_info and the pagy_limit_selector_js helpers use the "item"/"items" generic name in their output. You can change that by editing the values of the "pagy.item_name" i18n key in the dictionary files that your app is using.

Besides you can also pass the :item_name by passing an already pluralized string directly to the helper call:

<%== pagy_info(@pagy, item_name: t(`activerecord.model.product`, count: @pagy.count) %>
<%== pagy_limit_selector_js(@pagy, item_name: t('activerecord.model.product', count: @pagy.limit) %>

# Customize CSS styles

For all its own interactive helpers the pagy gem includes a few stylesheets that you can customize.

Besides that, pagy provides a few frontend extras for bootstrap, bulma and tailwind that come with a decent styling provided by their respective framework.

If you need to further customize the styles provided by the extras, you don't necessary need to override the helpers in most of them: here are a few alternatives:

  • Check whether the specific extra offers customization (e.g. bulma)
  • Define the CSS styles to apply to the pagy CSS classes
  • If sass/scss is available: extend the pagy CSS classes with some framework defined class, using the @extend sass/scss directive
  • Use the jQuery addClass method
  • Use a couple of lines of plain javascript

# Override CSS rules in element "style" attribute

In order to get a decent default look, a couple of helpers (i.e. pagy*_combo_nav_js, pagy_limit_selector_js) assign element style attributes to one or more tags. You can override their rules in your own stylesheets by using the attribute [style] selector and !important. Here is an example for overriding the width of the input element:

.pagy input[style] {
  /* This is just for the sake of demo: indeed the width is already calculated and assigned dynamically 
     considering the total number of digits that may appear in the input, so you should not need to override it */
  width: 5rem !important;
}

# Override pagy methods

You include the pagy modules in your controllers and helpers, so if you want to override any of them, you can redefine them right in your code, where you included them.

You can read more details in the nice How to Override pagy methods only in specific circumstances mini-post by Ben Koshy.

Also, consider that you can use prepend if you need to do it globally:


module MyOverridingModule
  def pagy_any_method
    #...
    super
    #...
  end
end
Pagy::Backend.prepend MyOverridingModule
# and/or
Pagy::Frontend.prepend MyOverridingModule

# Override pagy_get_count: use count_documents with Mongoid

# e.g. applicaton_controller.rb
def pagy_get_count(collection, vars)
  collection.respond_to?(:count_documents) ? collection.count_documents : super
end

# Paginate an Array

See the array extra.

# Paginate ActiveRecord collections

Pagy works out of the box with ActiveRecord collections, however here are a few specific cases that might be treated differently:

For better performance of grouped ActiveRecord collection counts, you may want to take a look at the arel extra.

Do it in 2 steps: first get the page of records without decoration, and then apply the decoration to it. For example:

controller
@pagy, records     = pagy(Post.all)
@decorated_records = records.decorate # or YourDecorator.method(records) whatever works

Your scope might become complex and the default pagy collection.count(:all) may not get the actual count. In that case you can get the right count in a couple of ways:

controller
# Passing the right arguments to the internal `collection.count(...)` (See the ActiveRecord documentation for details)
@pagy, @records = pagy(custom_scope, count_args: [:join])

# or directly pass the right count to pagy (that will directly use it skipping its own `collection.count(:all)`)
@pagy, @records = pagy(custom_scope, count: custom_count)

Ransack result returns an ActiveRecord collection, which can be paginated out of the box. For example:

controller
q              = Person.ransack(params[:q])
@pagy, @people = pagy(q.result)

Always order your collections!

# Paginate for generic API clients

When your app is a service that doesn't need to serve any UI, but provides an API to some sort of client, you can serve the pagination metadata as HTTP headers added to your response.

In that case you don't need the Pagy::Frontend nor any frontend extra. You may not even need the standard pagination, but use the Pagy::Keyset pagination. Anyway you may want to use the headers extra and use its helpers to add the headers to your responses, and other useful backend extras like the limit extra and the jsonapi extra.

# Paginate with JSON:API

See the jsonapi extra.

# Paginate for Javascript Frameworks

If your app uses ruby as pure backend and some javascript frameworks as the frontend (e.g. Vue.js, react.js, ...), then you may want to generate the whole pagination UI directly in javascript (with your own code or using some available javascript module).

In that case you don't need the Pagy::Frontend nor any frontend extra. You should only require the metadata extra and pass the pagination metadata in your JSON response.

# Paginate search framework results

Pagy has a few extras dedicated to gems returning search results:

# Paginate by id instead of offset

With particular requirements/environment an id-based pagination might work better than a classical offset-based pagination, You can use an interesting approach proposed here.

# Paginate by date

Use the calendar extra that adds pagination filtering by calendar time unit (year, quarter, month, week, day).

# Paginate multiple independent collections

By default pagy tries to derive parameters and variables from the request and the collection, so you don't have to explicitly pass it to the pagy* method. That is very handy, but assumes you are paginating a single collection per request.

When you need to paginate multiple collections in a single request, you need to explicitly differentiate the pagination objects. You have the following common ways to do so:

By default pagy generates its links reusing the same request_path of the request, however if you want to generate links pointing to a different controller/path, you should explicitly pass the targeted :request_path. For example:

If you're using hotwire (turbo-rails being the Rails implementation), another way of maintaining independent contexts is using separate turbo frames actions. Just wrap each independent context in a turbo_frame_tag and ensure a matching turbo_frame_tag is returned:

  <-- movies/index.html.erb -->
  
  <-- movies#bad_movies -->
  <%= turbo_frame_tag "bad_movies", src: bad_movies_path do %>    
      <%= render "movies_table", locals: {movies: @movies}%>
      <%== pagy_bootstrap_nav(@pagy) %>    
  <% end %>

  <-- movies#good_movies -->
  <%= turbo_frame_tag "good_movies", src: good_movies_path  do %>    
      <%= render "movies_table", locals: {movies: @movies}%>
      <%== pagy_bootstrap_nav(@pagy) %>    
  <% end %>   
  # controller action 
def good
  @pagy, @movies = pagy(Movie.good, limit: 5)
end

def bad
  @pagy, @movies = pagy(Movie.bad, limit: 5)
end 

Consider Benito Serna's implementation of turbo-frames (on Rails) using search forms with the Ransack gem along with a corresponding demo app for a similar implementation of the above logic.

You can also paginate multiple model in the same request by simply using multiple :page_param:


def index # controller action
  @pagy_stars, @stars     = pagy(Star.all, page_param: :page_stars)
  @pagy_nebulae, @nebulae = pagy(Nebula.all, page_param: :page_nebulae)
end

# Wrap existing pagination with pagy_calendar

You can easily wrap your existing pagination with the pagy_calendar method. Here are a few examples adding :year and :month to different existing statements.

controller
# pagy without calendar
@pagy, @record = pagy(collection, any_vars: value, ...)
# wrapped with pagy_calendar
@calendar, @pagy, @records = pagy_calendar(collection, 
                                           year:  {},
                                           month: {},
                                           pagy:  { any_vars: value, ... })

# any other backend constructors (e.g. pagy_searchkick)
@pagy, @record = pagy_searchkick(pagy_search_args, any_vars: value, ...)
# wrapped with pagy_calendar
@calendar, @pagy, @records = pagy_calendar(pagy_search_args, 
                                           year:  {},
                                           month: {},
                                           pagy:  { backend:  :pagy_searchkick, 
                                                    any_vars: value, ... })

Then follow the calendar extra documentation for more details.

# Paginate only max_pages, regardless the count

In order to limit the pagination to a maximum number of pages, you can pass the :max_pages variable.

For example:

@pagy, @records = pagy(collection, max_pages: 50, limit: 20)
@records.size #=> 20   
@pagy.count   #=> 10_000
@pagy.last    #=> 50

@pagy, @records = pagy(collection, max_pages: 50, limit: 20, page: 51)
#=> Pagy::OverflowError: expected :page in 1..50; got 51

If the @pagy.count in the example is 10_000, the pages served without :max_pages would be 500, but with :max_pages: 50 pagy would serve only the first 50 pages of your collection.

That works at the Pagy/Pagy::Countless level, so it works with any combination of collection/extra, including limit, gearbox and search extras, however it makes no sense in Pagy::Calendar unit objects (which ignore it).

# Paginate pre-offset and pre-limited collections

With the other pagination gems you cannot paginate a subset of a collection that you got using offset and limit. With Pagy it is as simple as just adding the :outset variable, set to the initial offset. For example:

controller
subset                   = Product.offset(100).limit(315)
@pagy, @paginated_subset = pagy(subset, outset: 100)

Assuming the :limit default of 20, you will get the pages with the records you are expecting. The first page from record 101 to 120 of the main collection, and the last page from 401 to 415 of the main collection. Besides the from and to attribute readers will correctly return the numbers relative to the subset that you are paginating, i.e. from 1 to 20 for the first page and from 301 to 315 for the last page.

# Paginate non-ActiveRecord collections

The pagy_get_count method works out of the box with ActiveRecord collections; for other collections (e.g. mongoid, etc.) you might want to change the :count_args default to suite your ORM count method:

pagy.rb (initializer)
Pagy::DEFAULT[:count_args] = []

or in extreme cases you may need to override it in your controller.

# Paginate collections with metadata

When your collection is already paginated and contains count and pagination metadata, you don't need any pagy* controller method. For example this is a Tmdb API search result object, but you can apply the same principle to any other type of collection metadata:

#<Tmdb::Result page=1, total_pages=23, total_results=446, results=[#<Tmdb::Movie ..>,#<Tmdb::Movie...>,...]...>

As you can see, it contains the pagination metadata that you can use to setup the pagination with pagy:

controller
# get the paginated collection
tobj = Tmdb::Search.movie("Harry Potter", page: params[:page])
# use its count and page to initialize the @pagy object
@pagy = Pagy.new(count: tobj.total_results, page: tobj.page)
# set the paginated collection records
@movies = tobj.results

# Paginate Any Collection

Pagy doesn't need to know anything about the kind of collection you paginate. It can paginate any collection, because every collection knows its count and has a way to extract a chunk of items given a start/offset and a per-page/limit. It does not matter if it is an Array or an ActiveRecord scope or something else: the simple mechanism is the same:

  1. Create a Pagy object using the count of the collection to paginate
  2. Get the page of items from the collection using the start/offset and the per-page/limit (pagy.offset and pagy.limit)

Here is an example with an array. (Please, notice that this is only a convenient example, but you should use the array extra to paginate arrays).

# paginate an array
arr = (1..1000).to_a

# Create a Pagy object using the count of the collection to paginate
pagy = Pagy.new(count: arr.count, page: 2)
#=> #<Pagy:0x000055e39d8feef0 ... >

# Get the page using `pagy.offset` and `pagy.limit`
paginated = arr[pagy.offset, pagy.limit]
#=> [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]

This is basically what the pagy method included in your controller does for you in one go:

controller
@pagy, @products = pagy(Product.some_scope)

Then of course, regardless the kind of collection, you can render the navigation links in your view:

view
<%== pagy_nav(@pagy) %>

See the Pagy::Backend API documentation for more details.

# Use the pagy_nav* helpers

These helpers take the Pagy object and return the HTML string with the pagination links, which are wrapped in a nav tag and are ready to use in your view. For example:

view
<%== pagy_nav(@pagy) %>

# Skip single page navs

Unlike other gems, Pagy does not decide for you that the nav of a single page of results must not be rendered. You may want it rendered... or maybe you don't. If you don't... wrap it in a condition and use the pagy_nav* only if @pagy.pages > 1 is true. For example:

<%== pagy_nav(@pagy) if @pagy.pages > 1 %>

# Skip page=1 param

By default Pagy generates all the page links including the page param. If you want to remove the page=1 param from the first page link, just require the trim extra.

# Deal with a slow collection COUNT(*)

Every pagination gem needs the collection count in order to calculate all the other variables involved in the pagination. If you use a storage system like any SQL DB, there is no way to paginate and provide a full nav system without executing an extra query to get the collection count. That is usually not a problem if your DB is well organized and maintained, but that may not be always the case.

Sometimes you may have to deal with not very efficient legacy apps/DBs that you cannot totally control. In that case the extra count query may affect the performance of the app quite badly.

You have 2 possible solutions in order to improve the performance.

Depending on the nature of the app, a possible cheap solution would be caching the count of the collection, and Pagy makes that really simple.

Pagy gets the collection count through its pagy_get_count method, so you can override it in your controller. Here is an example using the rails cache:

controller
# override the pagy_get_count method adding
# the Rails.cache wrapper around the count call
def pagy_get_count(collection, _vars)
  cache_key = "pagy-#{collection.model.name}:#{collection.to_sql}"
  Rails.cache.fetch(cache_key, expires_in: 20 * 60) do
    collection.count(:all)
  end
end
model
# reset the cache when the model changes (you may omit the callbacks if your DB is static)
after_create { Rails.cache.delete_matched /^pagy-#{self.class.name}:/ }
after_destroy { Rails.cache.delete_matched /^pagy-#{self.class.name}:/ }

That may work very well with static (or almost static) DBs, where there is not much writing and mostly reading. Less so with more DB writing, and probably not particularly useful with a DB in constant change.

When the count caching is not an option, you may want to use the countless extra, which totally avoids the need for a count query, still providing an acceptable subset of the full pagination features.

If the slowness of the DB is caused by paginating big tables toward the ends of the collection (i.e. when the offset is a big number) then you should use the keyset extra. (See lso the keyset API)

# Maximize Performance

Here are some tips that will help choosing the best way to use Pagy, depending on your requirements and environment.

If you need the pagination bar with links and info, then you have a couple of choices, depending on your environment:

  • Add the oj gem to your gemfile and use any pagy*_nav_js helper (see Javascript). That uses client side rendering and it is faster and lighter than using any pagy*_nav helper (40x faster, 36x lighter and 1,410x more efficient than Kaminari). Notice: the oj gem is not a requirement but helps the performance when it is available.

If you don't have strict requirements but still need to give the user total feedback and control on the page to display, then consider the pagy*_combo_nav_js helpers. They are faster and lighter, and even more when the oj gem is available. That gives you the best performance with nav info and UI (48x faster, 48x lighter and 2,270x more efficient than Kaminari) also saving real estate.

If your requirements allow to use the countless extra (minimal or automatic UI) you can save one query per page, and drastically boost the efficiency eliminating the nav info and almost all the UI. Take a look at the examples in the pagy extra.

You can improve the performance for grouped collections with the arel extra, and queries on big data with fast_page.

# Ignore Brakeman UnescapedOutputs false positives warnings

Pagy output html safe HTML, however, being an agnostic pagination gem it does not use the specific html_safe rails helper on its output. That is noted by the Brakeman gem, that will raise a warning.

You can avoid the warning adding it to the brakeman.ignore file. More details here and here.

# Handle Pagy::OverflowError exceptions

Pass an overflowing :page number and Pagy will raise a Pagy::OverflowError exception.

This often happens because users/clients paginate over the end of the record set or records go deleted and a user went to a stale page.

You can handle the exception by using the overflow extra which provides a few easy and ready to use solutions for a few common cases, or you can rescue the exception manually and do whatever fits you better.

Here are a few options for manually handling the error in apps:

  • Do nothing and let the page render a 500
  • Rescue and render a 404
  • Rescue and redirect to the last known page (Notice: the overflow extra provides the same behavior without redirecting)
controller
rescue_from Pagy::OverflowError, with: :redirect_to_last_page

private

def redirect_to_last_page(exception)
  redirect_to url_for(page: exception.pagy.last), notice: "Page ##{params[:page]} is overflowing. Showing page #{exception.pagy.last} instead."
end

# Test with Pagy

  • Pagy has 100% test coverage.
  • You only need to test pagy if you have overridden methods.

If you need to test pagination, remember:

  • Pagy::DEFAULT should be set by your initializer and be frozen. You can test that your code cannot change it.
  • You can override defaults - i.e. any pagy variable can be passed to a pagy constructor. For example:
@pagy, @books = pagy(Book.all, limit: 10) # the default limit has been overridden

# Using your pagination templates

If you really need to use your own templates, you absolutely can. Here is a static example that doesn't use any other helper nor dictionary file for the sake of simplicity, however feel free to add your dynamic variables and use any helper and dictionary entries as you need:

<%# IMPORTANT: use '<%== ... ' instead of '<%= ... ' if you run this in rails %>

<%# The a variable below is set to a lambda that generates the a tag %>
<%# Usage: a_tag = a.(page_number, text, classes: nil, aria_label: nil) %>
<% a = pagy_anchor(pagy) %>
<nav class="pagy nav" aria-label="Pages">
  <%# Previous page link %>
  <% if pagy.prev %>
    <%= a.(pagy.prev, '&lt;', aria_label: 'Previous') %>
  <% else %>
    <a role="link" aria-disabled="true" aria-label="Previous">&lt;</a>
  <% end %>
  <%# Page links (series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]) %>
  <% pagy.series.each do |item| %>
    <% if item.is_a?(Integer) %>
      <%= a.(item) %>
    <% elsif item.is_a?(String) %>
      <a role="link" aria-disabled="true" aria-current="page" class="current"><%= item %></a>
    <% elsif item == :gap %>
      <a role="link" aria-disabled="true" class="gap">&hellip;</a>
    <% end %>
  <% end %>
  <%# Next page link %>
  <% if pagy.next %>
    <%= a.(pagy.next, '&gt;', aria_label: 'Next') %>
  <% else %>
    <a role="link" aria-disabled="true" aria-label="Next">&lt;</a>
  <% end %>
</nav>

You can use it as usual: just remember to pass the :pagy local set to the @pagy object:

<%== render file: 'nav.html.erb', locals: {pagy: @pagy} %>

You may want to read also the Pagy::Frontend API documentation for complete control over your templates.