The Keyboard Shortcut Gap in Laravel Applications

There is a clear signal in the most beloved developer tools: they all have excellent keyboard support. GitHub responds to T, G D, G I, ?, and dozens of other shortcuts. Linear uses ⌘K for everything. Notion has a comprehensive keyboard overlay accessible at any time. These are not just nice-to-haves — they are signals of product quality that power users recognise immediately.

Laravel Blade applications have historically lagged here. Not because keyboard shortcuts are technically difficult to add, but because:

  1. There is no standard Laravel convention for shortcut registration
  2. Keyboard event listeners scattered across JavaScript files are hard to maintain
  3. There is no discoverability UI — users cannot see what shortcuts are available
  4. Each shortcut conflict requires manual resolution

The ninja-keys web component solves problems 3 and 4 beautifully: it provides a searchable command overlay that makes every registered shortcut discoverable. Laravel Ninja Keys solves problems 1 and 2 by moving shortcut registration into PHP.

Installing Laravel Ninja Keys

composer require amjadiqbal/laravel-ninja-keys

Publish the configuration:

php artisan vendor:publish --provider="AmjadIqbal\LaravelNinjaKeys\NinjaKeysServiceProvider" --tag="config"

Add the directive to your main layout, just before </body>:

@ninjakeys

That is the minimum viable setup. The overlay appears when the user presses / (configurable) and displays all registered shortcuts.

Registering Shortcuts

The recommended pattern is to register shortcuts in a service provider. This ensures they are available across all pages:

use AmjadIqbal\LaravelNinjaKeys\Facades\NinjaKeys;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        NinjaKeys::section('Navigation')
            ->add('Dashboard',   route('dashboard'),         icon: 'heroicon-o-home',           keys: ['g d'])
            ->add('Projects',    route('projects.index'),    icon: 'heroicon-o-folder',         keys: ['g p'])
            ->add('Blog',        route('blog.index'),        icon: 'heroicon-o-document-text',  keys: ['g b'])
            ->add('Contact',     route('contact.show'),      icon: 'heroicon-o-envelope',       keys: ['g c']);

        NinjaKeys::section('Actions')
            ->add('New Project', route('projects.create'),   icon: 'heroicon-o-plus',           keys: ['n p'])
            ->add('Search',      '#search-input',            icon: 'heroicon-o-magnifying-glass', keys: ['/'], action: 'focus');
    }
}

Page-Specific Shortcuts

For shortcuts that only make sense on a specific page, register them in the controller before returning the view:

class ProjectController extends Controller
{
    public function show(Project $project): View
    {
        NinjaKeys::section('Project Actions')
            ->add('Edit Project',  route('projects.edit', $project),  icon: 'heroicon-o-pencil',  keys: ['e'])
            ->add('Delete Project', '#delete-project-form', icon: 'heroicon-o-trash', keys: ['d'], action: 'submit');

        return view('projects.show', compact('project'));
    }
}

How the Web Component Integration Works

The @ninjakeys Blade directive outputs two things:

  1. The ninja-keys web component JavaScript (from the configured CDN or asset path)
  2. A <ninja-keys> custom element with the registered shortcuts serialised as a data attribute
<!-- Simplified output of @ninjakeys -->
<script type="module" src="https://cdn.jsdelivr.net/npm/ninja-keys"></script>

<ninja-keys>
<script slot="hotkeys">
const hotkeys = [
    { id: "go-home", title: "Dashboard", section: "Navigation", hotkey: "g d", handler: () => window.location.href = "/" },
    { id: "go-projects", title: "Projects", section: "Navigation", hotkey: "g p", handler: () => window.location.href = "/projects" },
    // ...
];
</script>
</ninja-keys>

The ninja-keys web component renders entirely via the Shadow DOM, so it does not conflict with your application's CSS. The overlay appearance is controlled by CSS custom properties that you can override in your stylesheet.

Theming to Match Your Application

ninja-keys {
    --ninja-accent-color: #22d3ee;  /* Match your app's primary colour */
    --ninja-background: #18181b;    /* Dark zinc to match a dark theme */
    --ninja-text-color: #d4d4d8;
    --ninja-secondary-background: #09090b;
    --ninja-border-radius: 0.75rem;
    --ninja-icon-color: #71717a;
}

Accessibility Considerations

The ninja-keys web component ships with built-in ARIA support:

  • The overlay is a role="dialog" with aria-modal="true"
  • The search input is aria-label-ed
  • Results are a role="listbox" with role="option" items
  • Focus is trapped within the overlay when open and restored to the trigger element when closed

For keyboard-only users, the overlay can be opened with the Tab key if the trigger element is in the focus order.

GitHub Repository

Full documentation and configuration reference on GitHub.

Conclusion

Adding keyboard shortcuts to a Laravel application used to mean writing and maintaining scattered JavaScript event listeners with no discoverability story. Laravel Ninja Keys gives you a PHP-first shortcut registry, a searchable command overlay, and a production-ready web component integration in under ten minutes. If your application serves power users, this is the feature they will notice and appreciate.