Quick Menu
The quick menu displays action buttons in the header area for fast access to common functions.
Buttons Are App-Independent
The quick menu is tenant scoped, not app scoped: it renders the same buttons with the same targets no matter which app is selected in the app bar. A button must therefore never read TenantHelper::getSelectedApp() (directly or through a helper that falls back to it). When a target only makes sense per app, render one button per app the tenant runs — each labelled with that app's title and pinned to it with an explicit argument — instead of one button that changes meaning. App-specific entry points belong in that app's navigation, dashboard or header actions.
File Location
app-configs/quick-menu.ymlConfiguration
buttons:
- app: INVENTORY
component: inventory::quick-menu.low-stock
- app: WEBSITE
component: website::quick-menu.website-linkButton Properties
| Property | Description |
|---|---|
app / apps | Tenant app name (string) or list of names (tenant_apps.name, uppercase) the button belongs to. The button renders only when at least ONE of them is assigned to the selected tenant AND the app permission allows it (AccessHelper::canUseApp()). Set it on every button that opens an app's screens or shows an app's data — it is the primary gate. |
policy | (optional) A host-defined gate ability or policy name for checks beyond app usability (AccessHelper::canPassGate()) |
component | Livewire component to render |
Access Control
Buttons are gated by their app/apps key — a button without one renders for every user. The optional policy key adds a second check for cases app usability cannot express (e.g. a feature only some roles may see): AccessHelper::canPassGate() tries a Gate::define() ability of that name first; if no gate with that name exists, it falls back to the ability on the Tenant model policy. The gate or policy is defined by the host project — noerd ships none.
buttons:
- app: INVENTORY
policy: viewStockLevels
component: inventory::quick-menu.low-stockThe user must use the INVENTORY app AND pass viewStockLevels for this button to appear.
Creating a Quick Menu Button
Components are placed in your module's views directory and referenced by their namespaced Livewire name with the quick-menu. prefix:
app-modules/{module}/resources/views/components/quick-menu/{name}.blade.php → {module}::quick-menu.{name}Example: Low Stock Button
app-modules/inventory/resources/views/components/quick-menu/low-stock.blade.php
<?php
use Livewire\Component;
use Noerd\Helpers\AccessHelper;
use Noerd\Inventory\Models\Item;
new class extends Component {
public int $lowStockCount = 0;
public function mount(): void
{
$this->refreshLowStock();
}
public function refreshLowStock(): void
{
if (! AccessHelper::canUseApp('INVENTORY')) {
return;
}
$this->lowStockCount = Item::query()->where('stock', '<', 5)->count();
}
}; ?>
<div class="hidden lg:flex" wire:poll.15s="refreshLowStock">
<x-noerd::button
variant="pill"
@click="$modalRoute('inventory.items', { filter: 'low-stock' }, null, null, null, { rewriteUrl: false, fallbackComponent: 'inventory::items-list' })"
:class="$lowStockCount > 0 ? '!bg-red-300' : ''"
>
{{ __('Low Stock') }}: {{ $lowStockCount }}
</x-noerd::button>
</div>Registering a Button from a Module Installer
A module's install/update command adds its quick menu button idempotently from ensureModuleSetup() via ensureQuickMenuButton() (Noerd\Commands\Concerns\WritesHostAppConfigs, available to tenant apps and support modules alike) — a new entry is prepended to app-configs/quick-menu.yml; an entry with the same component is replaced by the installer's definition (with the apps lists of both merged):
$this->ensureQuickMenuButton([
'app' => 'INVENTORY',
'component' => 'inventory::quick-menu.low-stock',
]);ensureQuickMenuButton(array $button, array $legacyComponents = []) matches on component; entries still pointing at one of the $legacyComponents names (a renamed component) are rewritten to the new name first.
See Creating Modules for the install-command context.
Key Concepts
- Responsive / live:
hidden lg:flexshows a button only on larger screens;wire:pollkeeps a count current. Many buttons scroll horizontally instead of wrapping - Modals: open records with
$modalRoute(...), dialogs with$modal(...)— see Modal System - Styling:
<x-noerd::button variant="pill">follows the active theme and brand — never hand-roll Tailwind button classes - Tenant switcher: the quick menu row also hosts the tenant switcher — shown while
noerd.features.multi_tenantis on and the user can access more than one tenant, or is an admin withnoerd.features.new_tenantenabled