EN
Getting started
14 · Menu

JSON menu

The application menu is defined in a single JSON file; the vertical menu, the horizontal menu, the mobile drawer and the command palette search are all generated from that one definition. The active page, badges, external links and submenus up to three levels deep are supported. The menu does no authorization checks; the application decides which items are shown.

acun:menu
01

Basic usage

acun:menu takes its definition from the menu prop: the name of a file under resources/menu, a full file path, a PHP array or a Menu object. The example passes the definition as an array; in an application, use a JSON file. The layouts' nav slot already wraps the menu; when you use it on its own, put it inside a <nav>.

LIVE EXAMPLE
<nav class="flex flex-col gap-0.5">
    <acun:menu :menu="[
        ['section' => 'General'],
        ['title' => 'Dashboard', 'icon' => 'squares-2x2', 'url' => '#'],
        ['title' => 'Orders', 'icon' => 'shopping-bag', 'url' => '#orders'],
        ['title' => 'Reports', 'icon' => 'chart-bar', 'children' => [
            ['title' => 'Sales report', 'url' => '#sales'],
            ['title' => 'Customer report', 'url' => '#customers'],
        ]],
        ['section' => 'Support'],
        ['title' => 'Help center', 'icon' => 'lifebuoy', 'url' => 'https://destek.ornek.com', 'target' => '_blank'],
    ]" />
</nav>
PropValues
menuFile name | full path | array | Menu object
02

JSON file

The file lives under resources/menu/ and lists its items under the menu key. Each item is either a section heading (section) or a link or group with a title. icon is shown only on top-level items.

JSON
{
    "menu": [
        { "section": "General" },
        { "title": "Home", "icon": "home", "route": "home" },
        { "title": "Orders", "icon": "shopping-bag", "route": "orders.index", "active": "orders.*" },
        { "title": "Reports", "icon": "chart-bar", "badge": true, "children": [
            { "title": "Sales", "route": "reports.sales" },
            { "title": "Archive", "children": [
                { "title": "2025", "url": "/reports/archive/2025" }
            ] }
        ] },
        { "section": "Support" },
        { "title": "Help center", "icon": "lifebuoy", "url": "https://destek.ornek.com", "target": "_blank" }
    ]
}
PropValues
sectionSection heading; hidden when no items remain under it
titleDisplay name
iconacun:icon name
04

Active page

Without active, a route item is active while that route is open and a url item is active when the address matches exactly; external addresses are never considered active. To keep an item active on subpages too, give active route names or path patterns starting with / (* is a wildcard). A group that contains the active item is rendered open.

JSON
{
    "menu": [
        { "title": "Orders", "icon": "shopping-bag", "route": "orders.index", "active": "orders.*" },
        { "title": "Reports", "icon": "chart-bar", "url": "/reports", "active": ["/reports", "/reports/*"] }
    ]
}
PropValues
activeRoute name | /path pattern | an array of these
05

Badges and submenus

badge shows a number or short text to the right of the item; on a group, true shows the number of visible child items. children builds nested menus up to three levels deep; the children field of third-level items is ignored. Badges are shown in both the vertical and the horizontal menu, at every level including subgroups.

LIVE EXAMPLE
<nav class="flex flex-col gap-0.5">
    <acun:menu :menu="[
        ['title' => 'Notifications', 'icon' => 'bell', 'url' => '#', 'badge' => 6],
        ['title' => 'Imports', 'icon' => 'arrow-up-tray', 'url' => '#imports', 'badge' => 'New'],
        ['title' => 'Reports', 'icon' => 'chart-bar', 'badge' => true, 'children' => [
            ['title' => 'Sales', 'url' => '#sales'],
            ['title' => 'Customers', 'url' => '#customers'],
            ['title' => 'Archive', 'children' => [
                ['title' => '2025', 'url' => '#2025'],
                ['title' => '2024', 'url' => '#2024'],
            ]],
        ]],
    ]" />
</nav>
PropValues
badgeNumber | text | true (number of child items)
childrenChild items; up to 3 levels
06

Using it in a layout

Put the menu in the layout's nav slot. horizontal renders a horizontal (top) menu: section headings are skipped and groups become dropdown panels. With the horizontal layout, pass the same file to the mobileNav slot as a vertical menu for the mobile drawer. searchItems() returns every link in the menu for the command palette search, so no separate search list is needed.

BLADE
{{-- Vertical layout: menu and search from the same file --}}
<acun:layout.sidebar>
    <acun:slot:nav><acun:menu menu="sidebar" /></acun:slot:nav>
    <acun:slot:search>
        <acun:command-palette :items="\Acun\Ui\Menu\Menu::fromFile('sidebar')->searchItems()" />
    </acun:slot:search>
    …
</acun:layout.sidebar>

{{-- Horizontal layout: horizontal menu on top, vertical menu in the mobile drawer --}}
<acun:layout.horizontal>
    <acun:slot:nav><acun:menu menu="sidebar" horizontal /></acun:slot:nav>
    <acun:slot:mobileNav><acun:menu menu="sidebar" /></acun:slot:mobileNav>
    …
</acun:layout.horizontal>
PropValues
horizontaltrue | false
07

Building it in PHP

The menu can also be built in PHP with the Menu class; the definition is an array and can come from, for example, a database. transform is applied to every item before it is rendered; isActive decides whether an item is active, and when it returns null the default rule applies. The menu of this documentation site is built this way.

PHP
use Acun\Ui\Menu\Menu;

$menu = new Menu(
    $definition,
    // Every item passes through here before it is rendered: turn your own field into a link.
    transform: fn (array $item) => isset($item['module'])
        ? ['url' => '/modules/'.$item['module']] + $item
        : $item,
    // true / false: whether the item is active; null: leave it to the default rule.
    isActive: fn (array $item) => isset($item['module']) ? $item['module'] === $activeModule : null,
);

// Blade: <acun:menu :menu="$menu" />
// Command palette: $menu->searchItems()
PropValues
transformfn (array $item): array
isActivefn (array $item): ?bool
API

Props and slots

The values the component accepts.

PropDefaultDescription
menu — Required; a file name (resources/menu/…json), a full path, an array or a Menu object
horizontalfalseHorizontal (top) menu; section headings are not shown
sectionsautoIn the horizontal menu, true turns each section into a single dropdown (its groups open to the side); false lays the items out side by side; auto uses sections when there are more than 6 items
section — JSON: section heading; top level only
title — JSON: display name
iconnullJSON: acun:icon name; shown on the top level
routenullJSON: route name; if it is not defined, the link is #
params[]JSON: route parameters
urlnullJSON: a relative path or an http, https, mailto or tel address; anything else becomes #
activenullJSON: a route name or a /path pattern (can be an array, * is a wildcard)
badgenullJSON: a number or text; on a group, true = number of child items
targetnullJSON: _blank = new tab, wire:navigate is not used
children — JSON: child items; up to 3 levels
Acun UI · JSON menuDetailed usage and examples