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-headersection 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
hiddenattribute, 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
displaybefore the first keystroke. Both matter: Treeview writes an inlinedisplaywhile sliding a menu open or shut, and that would otherwise outrank.menu-open > .nav-treeviewafter 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.