Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer. You can expect quick tips, links to interesting tutorials, opinions and packages. Want to learn the cool stuff? Then sign up now!

A modern package to generate html menus

Virtually every website displays some sort of menu. Generating html menus might seem simple, but it can become complex very quickly. Not only do you have to render some basic html, but you also have to manage which item is active. If a menu has a submenu you’ll also want the parents of an active item to be active. Sometimes you want to insert some html between menu items.

There are some packages out there that can help generating menus, but most of them have a messy API or have become victims of feature creep. Thanks why we decided to create our own modern menu package that has a beautiful API to work with. We have provided full documentation containing lots of examples. In this post I’d like to walk you through it.

Generating menus

Though the main package is framework agnostic, we are going to assume in the following examples you are going to use it in a Laravel app. Let’s start simple. Image you want to generate this menu:

    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>

Here’s the code to do that:

$menu = Menu::new()
    ->add(Link::to('/', 'Home'))
    ->add(Link::to('/about', 'About'));

The menu can be rendered by calling render on it. Menu also implements __toString so it’s probably more practically to just output the menu in a view.

// in a blade view
Here is the menu: {!! $menu !!}

Let’s try something more complicated. Image you want to render this.

            <li><a href="/introduction">Introduction</a></li>
            <li><a href="/requirements">Requirements</a></li>
            <li><a href="/installation-setup">Installation and Setup</a></li>
        <h2>Basic Usage</h2>
            <li><a href="/basic-usage/your-first-menu">Your First Menu</a></li>
            <li><a href="/basic-usage/working-with-items">Working With Items</a></li>
            <li><a href="/basic-usage/adding-sub-menus">Adding Sub Menus</a></li>

Yup, that’s menu displayed on the docs of the menu package. Notice that there is a title before the second submenu. That piece of html can be generated by this code:

        ->link('/introduction', 'Introduction')
        ->link('/requirements', 'Requirements')
        ->link('/installation-setup', 'Installation and Setup')
        ->prepend('<h2>Basic Usage</h2>')
        ->link('/your-first-menu', 'Your First Menu')
        ->link('/working-with-items', 'Working With Items')
        ->link('/adding-sub-menus', 'Adding Sub Menus')

If you want an item to be active, just call setActive on it

$menu = Menu::new()
    ->add(Link::to('/', 'Home'))
    ->add(Link::to('/about', 'About')->setActive());

Manually setting items to active will become very tiresome very soon. In most cases is probably better to let the package handle activating items automatically

$menu = Menu::new()
    ->add(Link::to('/', 'Home'))
    ->add(Link::to('/about', 'About'));

Instead of adding link objects you can also use some convenience methods to add items to a menu:

    ->url('/', 'Home')
    ->route('contact', 'Contact')
    ->action([email protected]', 'Acme');

There are a lot more nice things included in the package such as attribute manipulation, conditionally prepending and appending content and support for macros. Learn it all on the documentation site.

Under the hood

Let’s take a quick look on how all this is implemented. Under the hood there are three packages:

When browsing the code you’ll see usages of scalar type hints, return types, anonymous classes, the null coalescing operator, typed arrays and higher order functions. Modern PHP is awesome, don’t let anybody tell you otherwise.

In closing

If you want to learn more about our menu package, head over to the documentation site. We’ve provided a lot of examples to get you started.

All three underlying packages were designed and coded up by my colleague Sebastian. I think he did an amazing job. If you like his work take the time to thank him on Twitter.

As a company Spatie uses a lot of open source software. That’s one of the reasons we try to give as much back as possible. Take a look at our other Laravel, PHP and JavaScript packages to see if we made something useful for you.

Freek Van der Herten is a partner and developer at Spatie, an Antwerp based company that specializes in creating web apps with Laravel. After hours he writes about modern PHP and Laravel on this blog. When not coding he’s probably rehearsing with his kraut rock band.
  • Yet another awesome package! Very useful.

    There’s a small error in your example when you demonstrate the `setActiveFromRequest` method, you forgot to remove `setActive()`.

  • From this post it’s unclear that you may actually link to *actions* and *routes* as well. Me personally, I always name all routes and only use route-name-based url generation pretty much everywhere (including js – thanks to *aaronlord/laroute* package). This way url structure can be easily altered without breaking app – convenient if it wasn’t in specification and suddenly needs to be changed minutes before launching to production (that actually happened).

    Another important thing that is not in documentation – how do you add icons into menu item texts? I often use FontAwesome icons there to make items visually more accessible. From both this post and documentation this seems like it’s not an option.

    Another minor thing is that menus seem to be stictly `ul > li` whereas I prefer `nav > li` instead. Not an option either?

    • The last example of the article clearly shows that you may link to actions and routes.

      I’ll pass your two questions to Sebastian (adding icons, nav > li) to Sebastian. Probably he’ll add some clear examples in the docs.

      If there are other things you find unclear, open up an issue on GitHub.

    • Hi, package author here.

      For the icons: there are multiple ways you can add icons. First off, you can just use raw html in the link texts, e.g. `$menu->link(‘/’, ‘Home ‘);`. If you prefer using css for your icons, you could always add a class or an attribute: `$menu->add(Link::to(‘/’, ‘Home’)->setAttribute(‘data-icon’, ‘home’));`. Will clarify this in the docs soon.

      Regarding the `nav > li`, wrapping `li` elements in anything else than a list is invalid html, so this isn’t an option. In case you meant `nav > ul > li`, that could be handled in the view layer.

      • > First off, you can just use raw html in the link texts, e.g. `$menu->link(‘/’, ‘Home ‘);`

        Disqus helped you on this one. 🙂 But I get it, `link` function parameter name is misleading here. Maybe you could rename in to `$html` for clarity?

        > Regarding the `nav > li`, wrapping `li` elements in anything else than a list is invalid html

        Wow, thanks. This made me check whatwg specification, I was pretty sure before that `nav` is a list element. My bad.

  • thinsoldier

    I went down this path a few times. Regretted it every time. Maybe you put in more work and you covered every edge case that I missed in my attempts. In the end I preferred using the dom via querypath to handle creating html instead of re-inventing the wheel with my own html generating classes. Might not be as fast but that’s what caching is for. Of course can’t really cache the “active” link which is why I use JS to keep track of which link is active. Using the dom also lets me extract sub-menus from my main menu to display them in sidebar menus.