SidebarSearch Plugin

SidebarSearch filters the sidebar menu as you type. Matching entries stay, everything else is hidden, and any submenu holding a match is expanded so the result is actually visible. Clearing the field puts the menu back exactly as it was — including which submenus were open.

The search box in this page’s sidebar is a live example.

Usage

This plugin is activated via data attributes.

Data API

Put data-lte-toggle="sidebar-search" on a text input. Place it outside .sidebar-wrapper so it stays put while the menu scrolls under it.

<div class="sidebar-search" role="search">
  <label for="sidebar-search-input" class="visually-hidden">Filter menu</label>
  <input
    type="search"
    id="sidebar-search-input"
    class="form-control form-control-sm"
    placeholder="Filter menu…"
    autocomplete="off"
    data-lte-toggle="sidebar-search"
    data-lte-target="#navigation"
  >
  <p class="fs-7 text-secondary mt-2 mb-0" data-lte-search-empty role="status" hidden>
    No matching pages.
  </p>
</div>
Attribute Description
data-lte-toggle="sidebar-search" Marks the input as a menu filter.
data-lte-target Selector for the menu to filter. Optional — without it, the .sidebar-menu inside the input’s own .app-sidebar is used.
data-lte-search-empty Optional element shown only while a query matches nothing. Give it role="status" so the result is announced.

type="search" is worth using: the browser’s built-in clear button fires input, so it restores the menu for free. ESC in the field does the same.

Matching rules
  • Matching is case-insensitive and matches anywhere in a .nav-link’s text, not just at the start.
  • A parent survives if any descendant matches, and is expanded to reveal it.
  • A parent whose own name matches keeps its entire subtree, so expanding it is never a dead end.
  • .nav-header section labels are hidden while filtering, because the groups they label are no longer intact.
Events
Event Description
filtered.lte.sidebar-search Fired on the input after each filter pass. event.detail is { query, matches }; matches is -1 when the filter was cleared.
document.addEventListener("filtered.lte.sidebar-search", event => {
  console.log(event.detail.query, event.detail.matches)
})
Programmatic API
import { SidebarSearch } from "admin-lte"

const input = document.querySelector('[data-lte-toggle="sidebar-search"]')
const search = SidebarSearch.getOrCreateInstance(input)

search.search("layout")  // filter the menu
search.clear()           // restore it
search.dispose()         // restore, then drop the instance
Methods
Method Returns Description
search(term) void Filters the menu to entries matching term. An empty or whitespace-only term calls clear().
clear() void Un-hides every entry and restores each submenu’s pre-search open state.
dispose() void Clears the filter, then removes the instance from the registry.
SidebarSearch.getInstance(el) SidebarSearch | null The existing instance for an element, if any.
SidebarSearch.getOrCreateInstance(el) SidebarSearch The existing instance, or a new one.

The constructor takes no config — the target menu comes from data-lte-target.

Notes
  • Entries are hidden with the hidden attribute, not a class, so they leave the accessibility tree as well as the layout. Nothing in your CSS has to know about the filter.
  • The plugin snapshots each submenu’s open state and its inline display before the first keystroke. Both matter: Treeview writes an inline display while sliding a menu open or shut, and that would otherwise outrank .menu-open > .nav-treeview after a search.
  • Because a search rewrites .menu-open, don’t drive the same submenus from your own code while a filter is active — clear it first.
  • In the mini sidebar the field is hidden, since a text input can’t render usefully in the rail. It returns on hover along with the labels it filters.
  • Both listeners are delegated on document, so a sidebar rendered after page load — a Turbo Frame, a client-side router — needs no re-initialisation.