Open-source project
comfy/active_link_to avatar
comfy/active_link_to

active_link_to: Rails links that know which page you are on

Rails view helper to manage "active" state of a link

854 stars83 forksRubyMIT

At a glance

What is it?
active_link_to is a small Rails view helper that wraps link_to and adds an active CSS class based on the current URL, with matching modes for inclusive, exclusive and exact paths plus controller, action and params matching. It is MIT licensed, and its behavior is driven entirely by request.original_fullpath.
Who is it for?
Adopt active_link_to if your Rails app has a navigation bar or sidebar whose highlighting logic you currently duplicate across templates, and if matching on request.original_fullpath is acceptable for your routes. Skip it if your active state depends on something other than the URL, such as application state or a query parameter the helper cannot see, or if you want a Turbo Frame friendly design that changes the highlighting without a full navigation.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A link_to wrapper that adds one class

Every Rails application that highlights the current page in a menu solves the same small problem badly, usually with a helper method copied from a blog post and half edited. active_link_to is that helper, packaged. It creates a link tag using a URL built from the options you pass, and it is, in the project's own description, basically a wrapper for link_to, so the documentation for ActionView's link_to still applies to every other option you pass through.

The only thing it adds is the decision about whether to attach an extra CSS class that marks the link as active. The mechanism is worth understanding because it is not based on the route set or on controller names by default. Most of the functionality depends on the current URL, specifically the request.original_fullpath value, and the active state is then decided by comparing that path with the path of the link you are rendering.

That is a deliberate simplification. It means the helper works with any URL, including ones built by hand, and it does not need to know about your routes, but it also means it cannot see anything that is not in the path. Remember that constraint, because it decides where you can and cannot use it.

Inclusive by default, exclusive and exact when you need them

The simplest call has no active option at all, and the default behavior is inclusive matching:

ruby
active_link_to 'Users', '/users'
# => <a href="/users" class="active">Users</a>

That link is active when the page path is /users or any child of it, such as /users/123. The README states that this is exactly the same as passing the mode explicitly:

ruby
active_link_to 'Users', '/users', active: :inclusive
# => <a href="/users" class="active">Users</a>

Exclusive matching is the option you want for a home link, where you want the current page highlighted but not its children:

ruby
# For URL: /users will be active
active_link_to 'Users', users_path, active: :exclusive
# => <a href="/users" class="active">Users</a>
ruby
# But for URL: /users/123 it will not be active
active_link_to 'Users', users_path, active: :exclusive
# => <a href="/users">Users</a>

Exact matching exists for cases where the distinction is carried by a query string rather than by the path, such as a filter applied to a listing page:

ruby
active_link_to 'Users', users_path(role_eq: 'admin'), active: :exact

So the same URL can carry different states depending on which mode you choose, which is exactly the flexibility a navigation menu with a home entry, a section entry and a filtered listing needs.

Matching on regex, controller, action or params

Beyond the three named modes, the active option accepts a regex, a controller and action pair, a hash of params, or a plain boolean. The regex form is the simplest escape hatch and is used to activate links whose URLs begin with a given prefix:

ruby
active_link_to 'Users', users_path, active: /^\/use/

Controller and action matching lets you decide the state from the route rather than from the string. You can pass a list of controllers with a list of actions:

ruby
# For matching multiple controllers and actions:
active_link_to 'User Edit', edit_user_path(@user), active: [['people', 'news'], ['show', 'edit']]

You can also pair one controller with one action, using the hash form:

ruby
# For matching specific controllers and actions:
active_link_to 'User Edit', edit_user_path(@user), active: [people: :show, news: :edit]

Empty arrays open up the other two combinations. An empty action list matches every action under the given controllers, and an empty controller list matches every controller for the given action:

ruby
# for matching all actions under given controllers:
active_link_to 'User Edit', edit_user_path(@user), active: [['people', 'news'], []]
ruby
# for matching all controllers for a particular action
active_link_to 'User Edit', edit_user_path(@user), active: [[], ['edit']]

Finally, params matching keys off query parameters, and a plain true or false sets the state unconditionally:

ruby
active_link_to 'Admin users', users_path(role_eq: 'admin'), active: { role_eq: 'admin' }
ruby
active_link_to 'Users', users_path, active: true

The documented set of active values is Boolean, Symbol with :exclusive, :inclusive or :exact, Regex, a controller and action pair, a controller with a specific action pair, and a Hash of params.

Class names, disabling the link and wrapping tags

Beyond the boolean state, the helper exposes a handful of presentation options. You can override the class used for the active state and the class used when the link is not active:

ruby
active_link_to 'Users', users_path, class_active: 'enabled'
# => <a href="/users" class="enabled">Users</a>
ruby
active_link_to 'News', news_path, class_inactive: 'disabled'
# => <a href="/news" class="disabled">News</a>

The active_disable option swaps the anchor for a span when the link is active, which is how you make a current page non clickable:

ruby
active_link_to 'Users', users_path, active_disable: true
# => <span class="active">Users</span>

For navigation menus, wrap_tag puts the link inside another element and wrap_class styles that wrapper. The documentation shows the wrapping used to build list items in a menu:

ruby
active_link_to 'Users', users_path, wrap_tag: :li
# => <li class="active"><a href="/users">Users</a></li>
ruby
active_link_to 'Users', users_path, wrap_tag: :li, wrap_class: 'nav-item'
# => <li class="nav-item active"><a href="/users">Users</a></li>

Note where the active class lands in the wrapped output: on the wrapper element, not on the anchor. If your stylesheet targets a.active and a:hover, the hover and active rules will not match the same element, which is exactly the kind of detail that costs an hour the first time you hit it.

Defaults set in an initializer, per call overrides always win

Most of the presentation options can be set once for the whole application. The documentation gives the initializer form, with a file at config/initializers/active_link_to.rb:

ruby
ActiveLinkTo.configure do |config|
  config.class_active = 'is-active'
end

The configurable values are class_active with a default of "active", class_inactive with a default of empty string, active_disable with a default of false, wrap_tag with a default of nil, and wrap_class with a default of empty string. The initializer only changes the defaults, and an option passed to a specific call overrides the default, which is the behavior you would expect from any configuration setting.

That split is useful in practice. A project that uses a design system with is-active as the state class can set it once, while a single menu that needs a different treatment can pass class_active inline without touching anyone else's markup. It is worth setting class_active in the initializer rather than passing it at every call site, because a scattered literal is how the same class name ends up spelled two ways across an application.

One gap in the documentation: the README does not describe how the initializer behaves in a multi engine or component setup, and it does not mention a reload strategy for development. If your app renders navigation from a shared engine, test the initializer in that context before relying on it.

Two helper methods you can call directly

The gem exposes the pieces it uses internally, which is handy when you need the state without rendering a link. is_active_link? takes a URL and an active value and returns true or false:

ruby
is_active_link?(users_path, :inclusive)
# => true

And active_link_to_class returns the CSS class that would be used:

ruby
active_link_to_class(users_path, active: :inclusive)
# => 'active'

That second one is the interesting one, because it lets you apply the same logic to something that is not a link. If you are building a custom tab component, a button element or a partial that renders its own markup, you can ask the gem for the class name instead of duplicating the state logic and hoping it stays in sync with the link rendering.

The same openness applies to the rendering options. Because the helper is a wrapper rather than a new abstraction, you can pass anything link_to accepts and it keeps working. The trade-off is that you inherit link_to's behavior along with its conventions, including the fact that the comparison is string based on the original full path, which includes the query string.

A single gem, one dependency tree, MIT terms

The install path is one line in a Gemfile followed by running the bundle install command:

ruby
gem "active_link_to"

There is nothing else to configure, no initializer required to get started, and no JavaScript to include. The repository layout is correspondingly small: lib and spec directories, a Rakefile, a.rubocop.yml, and the gemspec. There is no app directory and no server component, because this is a library rather than an application.

The gem is released under the MIT license, and the README records the copyright as Oleg Khabarov covering 2009 to 2026. MIT means you can use it in commercial applications and modify it, and the practical consequence for this project is that adding a highlight class to links raises no licensing questions for your application.

On maintenance, the release history here is short. The only listed release is v1.1.0 on 2026-10-14, and the last push was on 2026-10-20. The repository is not archived, so the project is not frozen, but a single listed release is thin evidence of a long release history. The source of truth for what changed is the repository itself and the upstream link_to documentation it defers to.

The upgrade cost is low in the sense that matters for a view helper. The public surface is one method plus two predicates, so a version bump is unlikely to force a refactor of your templates. It is worth pinning the gem version in your Gemfile anyway, since a change in the default class name would silently change your styling.

Editorial conclusion

Adopt active_link_to if your Rails app has a navigation bar or sidebar whose highlighting logic you currently duplicate across templates, and if matching on request.original_fullpath is acceptable for your routes. Skip it if your active state depends on something other than the URL, such as application state or a query parameter the helper cannot see, or if you want a Turbo Frame friendly design that changes the highlighting without a full navigation. Verify first that your route helpers produce paths the inclusive and exclusive modes can distinguish, and that a controller and action pair in your app does not collide with the hash form of the active option.

Frequently asked questions

How do I install active_link_to in a Rails app?

Add `gem "active_link_to"` to your Gemfile and run `bundle install`. No initializer is required to start using it, and the helper is available in views immediately.

What is the difference between :inclusive and :exclusive in active_link_to?

Inclusive matching, the default, marks the link active for the path itself and any child path such as /users/123. Exclusive matching marks it active only for the exact path, which is the mode the README recommends for home links.

Can active_link_to decide the active state from params or a controller?

Yes. The active option accepts a hash of params, a regex, a controller and action pair, or a plain boolean, in addition to the :inclusive, :exclusive and :exact symbols.

How do I change the CSS class used for active links in active_link_to?

Set it once in config/initializers/active_link_to.rb with `config.class_active = 'is-active'`, or override it per call with the class_active option. The default is "active".

Does active_link_to work with Rails link_to options?

Yes, the README describes active_link_to as basically a wrapper for link_to, so the ActionView link_to documentation applies to the other options you pass through.

Official sources

  1. comfy/active_link_to on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/comfy-active-link-to.svg)](https://hysenlabs.com/projects/comfy-active-link-to)

Was this useful?