# metismenu turns a nested list into a menu and leaves the ARIA state to you

> metismenu is an MIT licensed jQuery plugin that takes a plain nested unordered list and turns it into a collapsible navigation menu, with a small set of options for adapting it to another markup system such as Bootstrap 5. It is a mature package with a v3 rewrite, a React wrapper and a vanilla JavaScript variant, and its accessibility model is deliberately explicit rather than automatic.

**onokumus/metismenu** — A collapsible jQuery menu plugin

- Repository: https://github.com/onokumus/metismenu
- Website: https://onokumus.github.io/metismenu/
- Stars: 1,950 · Forks: 474
- Language: JavaScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/onokumus-metismenu

## A nested list is the whole input format

metismenu is described as a collapsible jQuery menu plugin, and the short version is that it turns a nested list into a menu.

The usage steps in the README make the contract obvious. You include the stylesheet, you include jQuery, you include the plugin, you add an `id` attribute to an unordered list, and you call the plugin on it.

```html
<ul id="metismenu">
  <li>
    <a href="#" aria-expanded="false">Menu 1</a>
    <ul>
    ...
    </ul>
  </li>
  ...
</ul>
```

```javascript
$("#metismenu").metisMenu();
```

There is no JSON, no configuration file and no template. The plugin reads the DOM structure you already have and adds the behaviour, which is why it drops into an existing server rendered site without changing how the page is built.

The package is on npm as `metismenu`, and the README gives four installation routes, which is a good sign of how widely it is consumed.

```sh
npm install metismenu
```

```sh
yarn add metismenu
```

```sh
bun add metismenu
```

and a Composer route for PHP projects that vendor their front end assets.

```sh
composer require onokumus/metismenu:dev-master
```

The package manifest describes a v3 package that is an ES module, with `main` pointing at a CommonJS build, `module` at an ESM build and `browser` at the minified one, and an `exports` map that also exposes `./style` for the stylesheet, plus a `types` entry pointing at a hand written declaration file. The source of the stylesheet is shipped as Sass at `src/metisMenu.scss`.

One note from the README header that dates the project honestly. This version does not support any version of IE browser.

## The ARIA attribute is your job, and the plugin maintains it afterwards

The accessibility section is the most useful part of the documentation, and it makes a choice that is worth understanding.

The instruction is to be sure to add `aria-expanded` to the element `a`. The attribute explicitly defines the current state of the collapsible element to screen readers and similar assistive technologies. If the collapsible element is closed by default, it should have a value of `aria-expanded="false"`, and if the parent `li` element is set to be open by default using the `mm-active` class, then `aria-expanded="true"` should be set on the control instead. The plugin will automatically toggle this attribute based on whether or not the collapsible element has been opened or closed.

So the author does the initial state and the plugin keeps it in sync. That is a sensible division, because a plugin that guesses the initial state from a class name would have to hard code a class name, and the class name is exactly what a project using Bootstrap 5 will want to change.

The same principle shows up in the option that stops a submenu from opening. Setting `aria-disabled="true"` on the `<a>` element stops metisMenu opening the menu for that particular list, and the documentation notes this can be changed dynamically and will be obeyed correctly.

```html
<a href="#" aria-expanded="false" aria-disabled="true">List 1</a>
```

That last point, that the change is obeyed correctly when made at runtime, is the kind of detail that usually gets left unstated.

There is a second, older mechanism, since version 2.3.0 the plugin disables list expansion if `aria-disabled` is true on the link element, which is the origin of that behaviour in the first place.

## Options that let it sit inside another markup system

The option set is small, and three of the options exist for one reason, which is to make the plugin work with a navigation system whose markup is not the default.

`triggerElement` is the selector for the clickable control and defaults to `a`. `parentTrigger` is the selector for the parent item and defaults to `li`. `subMenu` is the selector for the nested list and defaults to `ul`. All three are given with a Bootstrap 5 example, where the same menu is built from `.nav-link`, `.nav-item` and `.nav.flex-column`.

```javascript
$("#metismenu").metisMenu({
  triggerElement: '.nav-link' // bootstrap 5
});
```

```javascript
$("#metismenu").metisMenu({
  parentTrigger: '.nav-item' // bootstrap 5
});
```

```javascript
$("#metismenu").metisMenu({
  subMenu: '.nav.flex-column' // bootstrap 5
});
```

The other three options control behaviour. `toggle` is a Boolean defaulting to true and exists for auto collapse support, so passing `toggle: false` stops one open section closing another. `preventDefault` is a Boolean defaulting to true, described as preventing or allowing the dropdowns' onclick events after expanding or collapsing, and it is marked as since version 2.7.0. And `dispose` is a String defaulting to null, described as for stop and destroy metisMenu, which is called as `$("#metismenu").metisMenu('dispose')`.

The `dispose` method is the one most jQuery plugins get wrong, because a plugin that binds events and leaves them bound will respond twice after a re-initialisation. Having an explicit teardown is what lets the menu be re-initialised after the page content changes.

The plugin also fires four events, which follow the Bootstrap convention of a paired before and after name, and the two after events wait for CSS transitions to complete. The `show.metisMenu` event fires immediately when the show method is called, `shown.metisMenu` when the collapsed element has been made visible to the user, `hide.metisMenu` immediately when the hide method is called, and `hidden.metisMenu` when the element has been hidden. The `shown` and `hidden` names are the ones to use if you need to measure the element after the transition, since by then the height is final.

## Migrating to v3 is a class rename and a file swap

The migration guide in the README is two lines long, and that brevity is itself informative about what changed between the v2 and v3 lines.

The instruction is to update the `metisMenu.js` and `metisMenu.css` files, and to change the `active` class to `mm-active`.

The class rename is the part that will bite you, because `active` is a very common class name and a global find and replace across a stylesheet will hit unrelated rules. The rename to `mm-active` is the plugin taking a namespace, which is a reasonable thing to do in a major version and an annoying thing to migrate.

The file swap is the other half, and it connects to the accessibility model above. In v2 the plugin had its own way of marking the open state, and in v3 the open state is expressed with `mm-active` on the `li` and `aria-expanded` on the anchor. So a v3 migration is really two changes, one to the class names in the markup and one to the plugin files themselves, and they have to be done together or the menu will not show which item is open.

The rest of the structure around the plugin is modern enough to be worth noting. The package manifest is `"type": "module"`, the build is Rollup with two separate minification passes, one for the UMD bundle and one for the ESM bundle, both driven by terser with source maps that include the sources. The styles are built with Sass, and there is a separate `css:dev` target that compiles into `docs/assets/css` for the demo page.

Tests are run with Vitest, with `vitest run` for the suite and `vitest` for watch mode, and linting is eslint over the source files. The repository also has a `bun.lock`, a `composer.json`, a `package.js`, a `.versions` file and a `changelog.md`.

For consumers who do not want jQuery at all, the README points at two related projects, `@metismenu/react`, a React component for MetisMenu, and `metismenujs`, MetisMenu with Vanilla JS. The vanilla variant is the one to look at if the jQuery dependency is the problem.

## Where a jQuery menu plugin is the wrong answer

The honest limitation of this project is its dependency and its age, and both are facts about the package rather than opinions.

The dependency is jQuery. The v3 release is an ES module with proper `exports` and TypeScript declarations, and it is bundled with Rollup, but it is still a jQuery plugin, and the usage example in the README still starts by loading jQuery from a CDN. A project that has removed jQuery, or that never had it, is looking at the wrong package. The `metismenujs` variant exists precisely for that case, and it changes what you are depending on rather than making the dependency go away.

The second limitation is scope. This is a collapse and expand behaviour with an ARIA attribute maintained for you. It is not a focus management system, it is not a keyboard navigation implementation, and it is not a menu that traps or moves focus. If your requirement is a full accessible menu with the arrow key behaviour that the ARIA authoring practices describe, this plugin gives you the state attribute and leaves the rest to you.

The third is the version line in the release history. The most recent tags listed are v2.4.2 from 2016, v2.4.0 and v2.3.0 from January 2016, while the package manifest carries version 3.1.1 and the README documents the v3 migration. So the tagged releases on GitHub are a decade old and the published npm version is much newer. The repository is not archived and the last push was on 2026-09-23, so the work continues, but the tag history is not a reliable guide to what is current and the changelog in the repository is the better source.

The alternative approach, and the difference in approach is real, is to use the navigation component that your framework or design system already ships. A Bootstrap 5 project has its own navbar with collapse behaviour and its own ARIA handling, a React project has a menu component, and a modern CSS framework has disclosure patterns built on the same `aria-expanded` idea. metismenu earns its place when you have an existing page that you cannot rebuild, and it is a worse answer than the framework's own component in every case where the framework's component is available.

## Conclusion

metismenu suits a team with an existing Bootstrap or jQuery site that needs a working nested menu this afternoon, and it suits anyone who wants the collapse behaviour without writing the ARIA toggling themselves. It is the wrong choice for a new project without jQuery, because the plugin still depends on it even in its v3 ESM build, and the wrong choice if you need keyboard navigation behaviour the plugin does not provide. Check first which version you are installing, since the v3 release renamed the `active` class to `mm-active` and the README carries a two line migration note for that.

## FAQ

### How do I install metismenu?

With `npm install metismenu`, `yarn add metismenu`, `bun add metismenu`, or `composer require onokumus/metismenu:dev-master` for PHP projects. A zip of the master branch is also linked from the README for manual installation.

### How is metismenu used with a bundler?

Import jQuery and the plugin, then either construct it, `const mm = new MetisMenu("#metismenu")`, or call the jQuery form, `$("#metismenu").metisMenu()`. The package is an ES module and exposes `./style` for the stylesheet.

### What do I need to add for accessibility?

Add `aria-expanded` to the `a` element, with `false` when the item is closed by default and `true` when the parent `li` is open by default using the `mm-active` class. The plugin then toggles the attribute automatically as items are opened and closed.

### Can metismenu work with Bootstrap 5 markup?

Yes, through the `triggerElement`, `parentTrigger` and `subMenu` options, which default to `a`, `li` and `ul` and can be pointed at `.nav-link`, `.nav-item` and `.nav.flex-column`. Adding the `has-arrow` class to the anchor adds the arrow indicator.

## Sources

- [License: MIT](https://github.com/onokumus/metismenu/blob/master/LICENSE)
- [onokumus/metismenu on GitHub](https://github.com/onokumus/metismenu)
- [Project website](https://onokumus.github.io/metismenu/)
- [README](https://github.com/onokumus/metismenu/blob/master/README.md)
- [Releases](https://github.com/onokumus/metismenu/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/onokumus-metismenu
