laravelista/ekko
Laravelista Ekko is a small Laravel helper package for marking navigation links as active. It compares the current route, URL, or action against menu targets, making it easy to add active classes and conditional states to menus and tabs with minimal configuration.
Install it like any Composer utility — there is no service provider, no facade registration, no publish step:
composer require laravelista/ekko
The package autoloads a small set of global helper functions (is_active(), active_class()) backed by an Ekko\Active class that reads the current request URI. That is essentially the whole surface area, so the first place to look is vendor/laravelista/ekko/src/ — it is a couple of hundred lines total and worth reading end-to-end before you rely on it, because the matching rules are the entire product.
The first use case is the obvious one: a nav link that highlights itself.
{{-- resources/views/partials/nav.blade.php --}}
<a href="/" class="{{ active_class('/') }}">Home</a>
<a href="/about" class="{{ active_class('/about') }}">About</a>
<a href="/blog" class="{{ active_class('/blog/*') }}">Blog</a>
active_class('/about') returns 'active' when the current URL matches, and an empty string otherwise, so it drops straight into a class attribute without a ternary. is_active('/about') is the boolean form if you need it for logic (aria-current, conditional markup, etc.). If you are on Laravel, the equivalent built-in is request()->is('about') — the reason to reach for ekko at all is that it works the same in a plain PHP app, a Slim app, or a legacy template that has no framework container.
Pattern matching instead of exact strings. The primary reason to use ekko over $_SERVER['REQUEST_URI'] === '/about' is wildcard support for section highlighting. active_class('/blog/*') keeps the Blog nav item lit while the user is on /blog/some-post, which is exactly the day-to-day behaviour you otherwise hand-roll with str_starts_with.
active_class('/blog/*'); // /blog, /blog/post, /blog/2024/01/post
is_active('/users/*/edit'); // mid-string wildcard
Grouping several paths under one nav item. Pass an array when a single menu entry covers multiple routes:
<a href="/settings" class="{{ active_class(['/settings', '/settings/*', '/account/*']) }}">
Settings
</a>
Custom active class for CSS frameworks. Bootstrap wants active; Tailwind wants utility strings. Pass the second argument rather than mapping in the template:
<a href="/about"
class="nav-link {{ active_class('/about', 'bg-gray-900 text-white') }}"
@if(is_active('/about')) aria-current="page" @endif>
About
</a>
Wrap it once, use it everywhere. Because the helper is global and string-based, the clean Laravel move is a Blade component that centralises the class and aria-current logic so you are not repeating the pattern in twenty links:
// app/View/Components/NavLink.php
class NavLink extends Component
{
public function __construct(public string $href, public string $pattern = '') {}
public function render()
{
return view('components.nav-link', [
'isActive' => is_active($this->pattern ?: $this->href),
]);
}
}
Pointing it at an explicit URL for testing. The Ekko\Active class accepts the URL/URI to evaluate rather than always reading $_SERVER, which is what makes it testable:
$active = new \Ekko\Active('/blog/post');
$this->assertTrue($active->is('/blog/*'));
Use this in unit tests and in any place where the "current" URL isn't the literal request (console commands, queued mailers rendering a nav, preview screens).
Use it as a thin layer, not an architecture. The realistic integration tip is: use ekko to answer "is this link active?" and nothing else. Compute the value in a view composer or component and pass a boolean down, rather than sprinkling helper calls through deeply nested partials — it keeps templates trivially cacheable and makes the dependency one line to delete later.
Query strings will bite you. The helper inspects the request URI, which includes ?page=2. is_active('/blog') will not match /blog?page=2 in the naive case. Either match /blog* or strip the query string before the check. This is the single most common "why isn't my nav highlighting" bug with this package.
Trailing slashes and subdirectory installs. /about/ and /about are different strings; so is /myapp/about when the app is deployed under a subdirectory. Prefer patterns (/about*) or normalise the URI yourself. Nothing in the package knows about your app's base path.
Wildcards are not path-aware. * is a glob, not a segment matcher, so /about* also matches /about-us and /abouting. Order your checks from most specific to least specific, and prefer '/about/*' plus an explicit '/about' when you want a real section boundary.
No request context on the CLI. Cron jobs, queue workers, and artisan commands have no meaningful REQUEST_URI. If a nav partial is rendered from a console context (e.g. a mailable preview or a PDF generator), the helpers will return false — or warn. Pass the URL into Ekko\Active explicitly in those code paths.
Maintenance is the real risk. Last release was March 2021 and there is no active development. It is a ~200-line, zero-dependency utility, so the practical advice is: pin the version, run your test suite on the PHP version you actually deploy, and be prepared to inline the two helper functions if a future PHP release breaks it. Do not build additional abstractions on top of it that would make removal expensive.
Never use it for anything security-adjacent. "Is this URL active" is a presentation concern. Do not let an active state, an active class, or the matched pattern drive authorization, redirects, or feature gating.
Escape the output. active_class() can return a caller-supplied class string; if any part of that string is user- or config-derived, run it through e() in Blade. In practice the values are hard-coded, but the helper does not escape for you.
If you're already on Laravel, consider not adopting it. request()->is('blog/*'), request()->routeIs('blog.*'), and Route::is() cover the same ground, are route-aware (which is more correct than URL matching when routes change), and ship with the framework. Ekko's value is portability — a non-Laravel app, a shared template package, or a mixed codebase — not capability.
How can I help you explore Laravel packages today?