PHPackages                             tobento/service-menu - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. tobento/service-menu

ActiveLibrary

tobento/service-menu
====================

A simple and fluent menu builder.

2.0(7mo ago)12094MITPHPPHP &gt;=8.4

Since Jun 14Pushed 7mo ago1 watchersCompare

[ Source](https://github.com/tobento-ch/service-menu)[ Packagist](https://packagist.org/packages/tobento/service-menu)[ Docs](https://www.tobento.ch)[ RSS](/packages/tobento-service-menu/feed)WikiDiscussions 2.x Synced 1mo ago

READMEChangelog (10)Dependencies (5)Versions (18)Used By (4)

Menu Service
============

[](#menu-service)

With the Menu Service you can build menus easily.

Table of Contents
-----------------

[](#table-of-contents)

- [Getting started](#getting-started)
    - [Requirements](#requirements)
    - [Highlights](#highlights)
    - [Simple Example](#simple-example)
- [Documentation](#documentation)
    - [Menu](#menu)
        - [Creating menu items](#creating-menu-items)
        - [Creating subitems](#creating-subitems)
        - [Additional menu items](#additional-menu-items)
        - [Icons](#icons)
        - [Badges](#badges)
        - [Sorting items](#sorting-items)
        - [Iterating items](#iterating-items)
        - [On specific item](#on-specific-item)
        - [On parent items](#on-parent-items)
        - [Active item](#active-item)
        - [Get item(s)](#get-items)
        - [Tags](#tags)
        - [Escaping](#escaping)
    - [Menus](#menus)
    - [Examples](#examples)
- [Credits](#credits)

---

Getting started
===============

[](#getting-started)

Add the latest version of the Menu service project running this command.

```
composer require tobento/service-menu

```

Requirements
------------

[](#requirements)

- PHP 8.4 or greater

Highlights
----------

[](#highlights)

- Framework-agnostic, will work with any project
- Decoupled design

Simple Example
--------------

[](#simple-example)

Here is a simple example of how to use the Menu service.

```
use Tobento\Service\Menu\Menu;
use Tobento\Service\Menu\Item;
use Tobento\Service\Menu\Link;

$menu = new Menu('footer')
    ->add(new Item('about us'))
    ->add(new Link('/contact', 'contact'))
    ->add(new Item('team', null, 'about us'));

$menu = new Menu('footer');
$menu->item('about us');
$menu->link('/contact', 'contact');
$menu->item('team')->parent('about us');
```

Render the menu:

```

// or just

```

Both menus from above will produce the following output.

```

    about us

            team

    contact

```

Documentation
=============

[](#documentation)

Menu
----

[](#menu)

### Creating menu items

[](#creating-menu-items)

Creating items with the add() method.

```
use Tobento\Service\Menu\Menu;
use Tobento\Service\Menu\Item;
use Tobento\Service\Menu\Link;
use Tobento\Service\Menu\Html;

$menu = new Menu('footer')
    ->add(new Item('about us'))
    ->add(new Link('/contact', 'contact'))
    ->add(new Html('html')); // must be escaped!
```

Creating items with the build in methods.

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$item = $menu->item('about us');
$linkItem = $menu->link('/contact', 'contact');
$htmlItem = $menu->html('html'); // must be escaped!
```

Creating items with the many() method.

```
use Tobento\Service\Menu\Menu;

$items = [
    [
        'name' => 'about',
    ],
    [
        'name' => 'contact',
    ],
    [
        'name' => 'team',
        'parent' => 'about',
    ],
];

$menu = new Menu('footer')->many($items, function($menu, $item) {

    $menu->link('/'.$item['name'], $item['name'])
         ->parent($item['parent'] ?? null);
});
```

### Creating subitems

[](#creating-subitems)

Creating subitems is done by defining the parent item by its id.

```
use Tobento\Service\Menu\Menu;
use Tobento\Service\Menu\Item;
use Tobento\Service\Menu\Link;

$menu = new Menu('footer')
    ->add(new Item('about us'))
    ->add(new Item('team', parent: 'about us'));

$menu = new Menu('footer');
$menu->item('about us');
$menu->item('team')->parent('about us');

// or by defining an id
$menu = new Menu('footer')
    ->add(new Item('about us', id: 'about'))
    ->add(new Item('team', parent: 'about'));

$menu = new Menu('footer');
$menu->item('about us')->id('about');
$menu->item('team')->parent('about');
```

### Additional menu items

[](#additional-menu-items)

**Link To First Child**

The `LinkToFirstChild` menu item, links to the first child menu link if exists, otherwise it will not be rendered at all.

```
use Tobento\Service\Menu\LinkToFirstChild;
use Tobento\Service\Menu\Menu;

$menu = new Menu('main');
$menu->add(new LinkToFirstChild($menu, 'Settings')->id('settings'));
$menu->link('/locales', 'locales')->parent('settings');
```

### Icons

[](#icons)

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('header');
$menu->link('/login', 'Login')->icon(name: 'login');

// you may define a position for all icons:
$menu->iconPosition('left');
```

**Creating Icons**

By default, icons will not be created at all.

A simple example how to create icons:

```
use Tobento\Service\Menu\Str;

$menu->each(static function($item, $menu) {
    if (!$item->getIcon()) {
        return $item;
    }

    $html = '';

    if ($menu->getIconPosition() === 'right') {
        $item->tag()->append(html: $html);
    } else {
        $item->tag()->prepend(html: $html);
    }

    return $item;
});
```

**Creating Icons With The Icon Service**

You may use the menu icon factory to create icons using the [Icon Service](https://github.com/tobento-ch/service-icon).

```
use Tobento\Service\Menu\MenuIconsFactory;
use Tobento\Service\Icon\IconsInterface;

$menuFactory = new MenuIconsFactory(
    icons: $icons, // IconsInterface
);

$menu = $menuFactory->createMenu(name: 'header');
```

**Render Only Icons**

Use the `onlyIcons` method if you want to render the icons only:

```
$menu->onlyIcons();
```

### Badges

[](#badges)

Badges can be used to add additional information to a menu item:

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('header');
$menu->link('/invoices', 'Invoices')->badge(text: '10', attributes: ['title' => '10 new invoices']);
```

Will output:

```

    Invoices10

```

**Badge If**

You may use the `badgeIf` method which renders badges only if the given `badge` parameter value validates to `true`.

```
use Tobento\Service\Menu\Menu;

$invoiceCount = 10;

$menu = new Menu('header');
$menu->link('/invoices', 'Invoices')->badgeIf(
    badge: $invoiceCount > 0, // bool
    text: (string)$invoiceCount,
    attributes: [],
);
```

### Sorting items

[](#sorting-items)

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team');
$menu->item('about');

$menu->sort(fn ($a, $b) => $a->text()  $b->text());
```

### Iterating items

[](#iterating-items)

By using the filter method:

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team');
$menu->item('about');

$menu->filter(fn($i) => $i->text() === 'team');
```

By using the each method having access to data tree and tags:

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team');
$menu->item('about');

$menu->each(function($item, $menu) {

    $parentTag = $item->parentTag();
    $itemTag = $item->itemTag();
    $treeLevel = $item->getTreeLevel();
    $treeId = $item->getTreeId();
    $treeParent = $item->getTreeParent();
    $treeParentItem = $item->getTreeParentItem();
    $treeChildren = $item->getTreeChildren();

    return $item;
});
```

### On specific item

[](#on-specific-item)

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team');
$menu->item('about');

$menu->on('team', function($item, $menu) {

    $item->itemTag()->class('foo');
    $item->parentTag()->class('bar');

    return $item;
});
```

```

    team
    about

```

### On parent items

[](#on-parent-items)

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team')->parent('about');
$menu->item('about');
$menu->item('contact');

$menu->onParents('team', function($item, $menu) {

    $item->itemTag()->class('foo');

    return $item;
});
```

```

    about

            team

    contact

```

### Active item

[](#active-item)

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team')->parent('about');
$menu->item('about');
$menu->item('contact');
$menu->item('form')->parent('contact');

// set the form item and all its parent items active.
$menu->active('form');
```

```

    about

            team

    contact

            form

```

Render only active tree items:

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team')->parent('about');
$menu->item('about');
$menu->item('contact');
$menu->item('form')->parent('contact');

// set the form item active.
$menu->active('form');

// do not render any inactive tree items.
$menu->subitems(false);
```

```

    about
    contact

            form

```

Set the items active on the item itself:

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team')->parent('about');
$menu->item('about');
$menu->item('contact')->active();
$menu->item('form')->parent('contact')->active();

// do no render any inactive tree items.
$menu->subitems(false);
```

```

    about
    contact

            form

```

### Get item(s)

[](#get-items)

Note: Items tree data and tags are not available yet, except item tag.

Get single item:

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team');

$menu->get('team')->itemTag()->class('active');
```

Get all items:

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team');

$items = $menu->all();
```

### Tags

[](#tags)

With tags you can manage the menu tags being rendered.

#### Menu Tags

[](#menu-tags)

```
use Tobento\Service\Menu\Menu;
use Tobento\Service\Menu\Tag;
use Tobento\Service\Menu\NullTag;

$menu = new Menu('footer');

// add class foo to every ul tag.
$menu->tag('ul')->class('foo');

// add class foo only to every ul tag with depth level 1.
$menu->tag('ul')->level(1)->class('foo');

// add class foo to every li tag.
$menu->tag('li')->class('foo');

// add class foo and bar only to every li tag with depth level 2.
$menu->tag('li')->level(2)->class('foo')->class('bar');

// add any attribute with the attr() method.
$menu->tag('li')->attr('data-foo', '1');

// change every ul tag to ol tag.
$menu->tag('ul')->handle(fn() => new Tag('ol'));

// assign current level so that other
// level tag attributes get assigned.
$menu->tag('ul')->handle(fn(Tag $t) => new Tag('ol')->level($t->getLevel()));

// change every ul tag to div tag.
$menu->tag('ul')->handle(fn() => new Tag('div'));

// change every li tag to a null tag and prepend and append a character.
$menu->tag('li')->handle(fn() => new NullTag()->prepend('[')->append(']'));

// get tag attributes.
$attributes = $menu->tag('li')->attributes();

// check if there are any attributes.
$empty = $attributes->empty();

// check if there is a specific attribute.
$hasClassAttr = $attributes->has('class');

// get any attribute.
$classAttr = $attributes->get('class');

// get any attribute.
$classAttr = $attributes->get('class');

// get all attributes.
$allAttributes = $attributes->all();

// set an attribute.
$attributes->set('data-foo', '1');

// add an attribute (merges).
$attributes->add('data-foo', '1');

// merge an attribute.
$attributes->merge('data-foo', '1');
```

#### Item Tags

[](#item-tags)

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$item = $menu->link('/contact', 'contact');

// The item tag.
$item->itemTag()->class('bar');

// The tag: link items have for instance its own tag. The a tag.
$item->tag()->class('foo');

// The parent tag is not yet available.
var_dump($item->parentTag()); // NULL
// Use each(), on(), onParents() methods if you need to manage the parent tag.
```

### Escaping

[](#escaping)

Html escaping is done for you on rendering the menu. Except on the Tag class you will need to do it by yourself:

```
use Tobento\Service\Menu\Tag;

$tag = new Tag('a', htmlspecialchars('html', ENT_QUOTES, 'UTF-8'));

$tag->content(htmlspecialchars('html', ENT_QUOTES, 'UTF-8'));
$tag->append(htmlspecialchars('html', ENT_QUOTES, 'UTF-8'));
$tag->prepend(htmlspecialchars('html', ENT_QUOTES, 'UTF-8'));
```

Menus
-----

[](#menus)

```
use Tobento\Service\Menu\Menus;
use Tobento\Service\Menu\Menu;

// Create menus.
$menus = new Menus();

// Create menus with a custom menu factory.
$menus = new Menus(new CustomMenuFactory());

// Add a menu.
$menus->add(new Menu('main'));

// Get the main menu. If it does not exist, it returns null.
$menus->get('main');

// Get the main menu. If it does not exist, it creates a new one.
$menus->menu('main')
      ->item('about')
      ->order(1000);
```

Examples
--------

[](#examples)

### Add active class to active item only

[](#add-active-class-to-active-item-only)

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team')->parent('about');
$menu->item('about');
$menu->item('contact');
$menu->item('form')->parent('contact');

$menu->on('form', function($item, $menu) {
    $item->itemTag()->class('active');
    $item->parentTag()->class('active');
    return $item;
});
```

```

    about

            team

    contact

            form

```

### Add active class to active items

[](#add-active-class-to-active-items)

```
use Tobento\Service\Menu\Menu;

$menu = new Menu('footer');
$menu->item('team')->parent('about');
$menu->item('about');
$menu->item('contact');
$menu->item('form')->parent('contact');

$menu->onParents('form', function($item, $menu) {

    $item->itemTag()->class('active');

    if ($item->getTreeLevel() > 0) {
        $item->parentTag()->class('active');
    }

    $item->tag()->class('active');

    return $item;
});

// do only render active tree.
$menu->active('form')
     ->subitems(false);
```

```

    about

        contact

            form

```

Credits
=======

[](#credits)

- [Tobias Strub](https://www.tobento.ch)
- [All Contributors](../../contributors)

###  Health Score

45

—

FairBetter than 92% of packages

Maintenance63

Regular maintenance activity

Popularity15

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity77

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~92 days

Total

18

Last Release

227d ago

Major Versions

1.x-dev → 2.02025-09-25

PHP version history (2 changes)1.0.0PHP &gt;=8.0

2.0PHP &gt;=8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/055d6a1b5c2384bb179c75ab0b55914231d898fdc4dffeb30770f81200e52206?d=identicon)[TOBENTOch](/maintainers/TOBENTOch)

---

Top Contributors

[![tobento-ch](https://avatars.githubusercontent.com/u/16684832?v=4)](https://github.com/tobento-ch "tobento-ch (46 commits)")

---

Tags

packagemenunavigationtobentohtml menu builder

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/tobento-service-menu/health.svg)

```
[![Health](https://phpackages.com/badges/tobento-service-menu/health.svg)](https://phpackages.com/packages/tobento-service-menu)
```

###  Alternatives

[spatie/menu

Html menu generator

7592.9M6](/packages/spatie-menu)[verbb/navigation

Create navigation menus for your site.

90683.7k17](/packages/verbb-navigation)[kartik-v/yii2-widget-sidenav

An enhanced side navigation menu styled for bootstrap (sub repo split from yii2-widgets)

364.0M8](/packages/kartik-v-yii2-widget-sidenav)[akaunting/laravel-menu

Menu and sidebar management package for Laravel

38233.8k](/packages/akaunting-laravel-menu)[belugadigital/kirby-navigation

Kirby 5 field for hierarchical menus with drag &amp; drop level indentation.

8713.4k](/packages/belugadigital-kirby-navigation)[kodicomponents/navigation

The KodiCMS Support package.

12232.6k10](/packages/kodicomponents-navigation)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
