Add an 'active' class to all active links in rails?
Asked Answered
A

13

19

Basically, I have a lot of code that looks like this:

link_to t('.profile'), business_path(@business), class: '#{'active' if current_page? business_path(@business)}'

which isn't very DRY.

I was wondering if anyone knows a good way to modify the link_to helper itself to automatically add an 'active' class to all links to the current page.

If it helps, I'm open to using HAML or SLIM.

Adolescence answered 5/9, 2013 at 16:59 Comment(1)
as per rails 6.1 now we have helper for html class name check this and this – Bottle
T
9

This is a good case for writing your own helper that wraps the link_to. In your application_helper.rb you can write a method active_link_to that takes the same params as link_to + current_page, and then just calls link_to like you are doing above.

Tandratandy answered 5/9, 2013 at 17:20 Comment(1)
in rails 6.1 we have helper for this rubydoc.info/docs/rails/… my answer is based on that #18642501 sorry for the comment just wanted everyone to see it – Bottle
D
19

I wrote simple helper method using build in view helper current_page? when you can specify custom class name in html_options hash.

def active_link_to(name = nil, options = nil, html_options = nil, &block)
  active_class = html_options[:active] || "active"
  html_options.delete(:active)
  html_options[:class] = "#{html_options[:class]} #{active_class}" if current_page?(options)
  link_to(name, options, html_options, &block)
end

Examples (when you are on root_path route):

<%= active_link_to "Main", root_path %>
# <a href="/" class="active">Main</a>

<%= active_link_to "Main", root_path, class: "bordered" %>
# <a href="/" class="bordered active">Main</a>

<%= active_link_to "Main", root_path, class: "bordered", active: "disabled" %>
# <a href="/" class="bordered disabled">Main</a>
Dunsinane answered 31/10, 2013 at 11:39 Comment(2)
This breaks when you actually pass a block to your active_link_to due to how rails' link_to works. – Arbitrate
I feel terrible down voting this as it's been the go-to answer for years but unfortunately, it does not work when passing a block. I'd suggest using https://mcmap.net/q/627781/-add-an-39-active-39-class-to-all-active-links-in-rails as an alternative πŸ‘ – Ind
C
17

I faced same requirement and here is my solution.

Create a method within ApplicationHelper

def active_class(link_path)
    current_page?(link_path) ? "active" : ""
end

And inside your view:

    <li class="<%= active_class('/') %>">
      <%= link_to 'HOME', root_path %>
    </li>
Comehither answered 4/6, 2016 at 0:29 Comment(1)
This is the cleanest, simplest, and easiest solution to OPs answer. No need to insert a gem for an application helper in my opinion. – Disembogue
A
14

It's a solved problem, just use active_link_to gem. Your example simplifies to this:

= active_link_to t('.profile'), business_path(@business)
Albanian answered 4/10, 2013 at 18:12 Comment(1)
active_link_to is awesome. It lets you wrap links (e.g. with li) and specify other matching conditions, like with regex. Thanks! – Cisneros
T
9

This is a good case for writing your own helper that wraps the link_to. In your application_helper.rb you can write a method active_link_to that takes the same params as link_to + current_page, and then just calls link_to like you are doing above.

Tandratandy answered 5/9, 2013 at 17:20 Comment(1)
in rails 6.1 we have helper for this rubydoc.info/docs/rails/… my answer is based on that #18642501 sorry for the comment just wanted everyone to see it – Bottle
B
7

as per rails 6.1 now we have helper for html class name

the helper example

class_names("foo", "bar")
 # => "foo bar"
class_names({ foo: true, bar: false })
 # => "foo"
class_names(nil, false, 123, "", "foo", { bar: true })
 # => "123 foo bar"

you could use it like this

<%= link_to 'Home', root_path, class: class_names('nav-link', { active: current_page?(root_path) }) %>

it will produce html like this

<a class="nav-link active" href="/">Home</a>

the doc is here

Bottle answered 27/5, 2021 at 7:50 Comment(1)
link_to automatically calls class_names() on whatever value is sent to the class attribute. So this answer can be simplified to just: link_to 'Home', root_path, class: ['nav-link', { active: current_page?(root_path) }]. This is definitely true in Rails 7, but I suspect it became true in 6.1, along with the new class_names method. – Syncopated
B
4

Great solution if you're using rails 6.1+.

Also works great when passing a block.

def active_link_to(text = nil, path = nil, **opts, &block)
  link = block_given? ? text : path
  opts[:class] = class_names(opts[:class], { active: current_page?(link) })

  if block_given?
    link_to link, opts, &block
  else
    link_to text, path, opts
  end
end
Botsford answered 11/3, 2022 at 14:28 Comment(0)
S
2

Here's the helper I use. I add an optional "match_text" parameter for added flexibility (for instance, if I want to mark a link as active when the actual request path is a child page of the link's destination.)

def link_to_active(text, destination, options = {})
  match_text = options.delete(:match_text)

  classes = options[:class].present? ? options[:class].split(" ") : []
  classes << "active" if request.fullpath.downcase == destination.downcase || (match_text && request.fullpath.downcase.include?(match_text.downcase))

  options = options.except(:class)
  options.merge!(:class => classes.join(" ")) unless classes.empty?

  link_to(text, destination, options)
end
Sicular answered 5/9, 2013 at 19:21 Comment(0)
A
1

I did the same that @egyamado. I needed to use AwesomeIcons too, so:

A helper:

def active_class?(link_path)
    'active' if current_page?(link_path)
end

And it was my view:

 <%= link_to my_controller_page_path,
    :title => "My Controller Page",
    :class => "other_name_class #{active_class?(my_controller_page_path)}" do  %>
                <i class="fa fa-fighter-jet"></i>&nbsp;My Controller Page
<%end%>

In another kind of Link, for example inside a Li.

#In this case I put a extra validation in root_path
<li class="nav-class <%=active_class?(my_controller_page_path)%> <%='active' if current_page?(root_path) %>">
  <%= link_to my_controller_page_path,
      :title => "Page 1",
      :class => "other_name_class" do  %>
      Page 1
  <%end%>
</li>
<li class="nav-class <%=active_class?(my_controller_page_2_path)%>">
  <%= link_to my_controller_page_2_path,
      :title => "Page 2",
      :class => "other_name_class" do  %>
      Page 2
  <%end%>
</li> 

It worked for me.

Aleda answered 24/11, 2020 at 16:26 Comment(0)
E
1

Here's a basic example from which you can expand:

module ApplicationHelper
  def active_link_to(text, path, **opts, &block)
    css_class = opts[:class]

    case opts[:endpoint]
      when String then css_class.concat(' active') if current_page? opts[:endpoint]
      when Array  then css_class.concat(' active') if opts[:endpoint].include? request.path
    end

    if block_given?
      link_to path, class: css_class, &block
    else
      link_to text, path, class: css_class
    end
  end
end
# app/views/layout/_navbar.html.erb

# Using multiple endpoints
<%= active_link_to 'Home',  root_path,  class: 'some-class', endpoint: ['', '/', '/home'] %>

# Using a single endpoint
<%= active_link_to 'Admin', admin_path, class: 'some-class', endpoint: '/admin' %>

# Using a block
<%= active_link_to admin_path, class: 'some-class', endpoint: '/admin' do %>
  <h1>Administration</h1>
<% end %>
Eel answered 5/11, 2021 at 12:35 Comment(0)
K
1

nav.slim:

li class="nav-item"
  = active_link_to 'Users', users_path, "nav-link"

application_helper.rb:

  def active_link_to(title, path, class_css, options = {})
    if current_page?(path)
      class_css = "#{class_css} active"
      options.merge!('aria-current': "page")
    end

    link_to title, path, class: class_css, **options
  end
Know answered 18/11, 2022 at 3:20 Comment(0)
I
1

It's am old question but found a easier way of doing this:

<%= link_to 'Test Page', my_portfolio_path , class: "nav-link  #{ request.path == test_page_path ? 'active' : ''  }"  %> 
Immedicable answered 1/9, 2023 at 12:42 Comment(1)
'active' if request.path == test_page_path works the same way and you don't need a ternary, also, as mentioned previously, this is what Rails' class_names is for. – Prole
F
0

This is my custom method to handle this issue.

  def active_link_to(name = nil, options = nil, html_options = nil, &block)
    if current_page?(options)
      active_class = html_options[:active_class] ? html_options[:active_class] : 'has-text-danger'
      html_options[:class] << "#{html_options[:class]} #{active_class}"
    end

    link_to(name, options, html_options, &block)
  end

html_options[:active_class] is a custom hash.

Now I can dynamically change styles of my active link.

<%= active_link_to "Menu", root_path, class: 'has-text-dark', active_class: 'has-text-danger' %>

Flavone answered 2/6, 2021 at 18:1 Comment(0)
B
-1

Use link_to_unless_current and then give it the look of an active link in CSS.

Bacterium answered 6/9, 2013 at 0:4 Comment(1)
Could you add an example? – Jacquez

© 2022 - 2024 β€” McMap. All rights reserved.