reveal.js-simplemenu

Simplemenu

Version Downloads

A simple menu for Reveal.js

Screenshot

In Powerpoint you can make slides with a nice bottom- or top bar in which the active menu item is highlighted. This menu works in the same way, but automatically. Simplemenu now also supports the Markdown syntax. Combined with the barhtml option, you don’t have to edit the template in HTML at all.

What it does

Auto mode

Simplemenu can generate the menu, using sections with an attribute of data-name. If you add a menubar (manually or through the barhtml option) and an empty menu, Simplemenu will automatically populate it for you. You can also add such a menu anywhere else in the presentation, to serve as a Table Of Contents or an Agenda.

Manual mode

However, if you write a menu yourself and put list items in it (in a menubar or in a standalone TOC), then Simplemenu goes into “manual” mode. One menu with list items is enough: Simplemenu then fills none of them, and also does not add any menubar. The result: every menu on the page has to be written by you.

You have to take some things into account if you want to make such a menu yourself, to make menu-items actually work and activate with navigation:

Presentation structure

Setup

Simplemenu uses top-level slides for the menu item names. Nested slides in vertical stacks will then also use the name of the whole stack. This way you get chapters through which the user can navigate.

However, sometimes you’ll want to limit your presentation to horizontal slides only. To still use ‘chapters’ with several slides, you can use the flat option. By default, it is set to false, but you can set it to true. Then, when a data-name is set for a slide, any following slides will keep that menu name. See Using the flat option below.

Installation

Regular installation

Copy the simplemenu folder to the plugins folder of the reveal.js folder, like this: plugin/simplemenu.

npm installation

This plugin is published to, and can be installed from, npm.

npm install reveal.js-simplemenu

The Simplemenu plugin folder can then be referenced from node_modules/reveal.js-simplemenu/plugin/simplemenu

Setup

JavaScript

Simplemenu works in setups with multiple Reveal instances.

There are two JavaScript files for Simplemenu, a regular one, simplemenu.js, and a module one, simplemenu.mjs. You only need one of them:

Regular

If you’re not using ES modules, for example, to be able to run your presentation from the filesystem, you can add it like this:

<script type="text/javascript" src="dist/reveal.js"></script>
<script src="plugin/simplemenu/simplemenu.js"></script>
<script>
    Reveal.initialize({
        // ...
        plugins: [ Simplemenu ]
    });
</script>

As a module

If you’re using ES modules, you can add it like this:

<script type="module">
    // This will need a server
    import Reveal from './dist/reveal.esm.js';
    import Simplemenu from './plugin/simplemenu/simplemenu.js';
    Reveal.initialize({
        // ...
        plugins: [ Simplemenu ]
    });
</script>

Styling

The styling of Simplemenu is automatically inserted from the included CSS styles, either loaded through NPM or from the plugin folder.

There are two ways to change how it looks. Most things in the menubar/menu is stylable with CSS variables. If you need to go further than the variables allow, then the whole stylesheet can be replaced instead through the csspath option, or you can just add menubar styles to the theme you are using.

CSS variables

See Styling with CSS variables for a demo that uses CSS variables in the page itself.

:root {
    --simplemenu-bar-background: #004444;
    --simplemenu-item-opacity: 0.5;
    --simplemenu-item-opacity-hover: 0.8;
    --simplemenu-marker-thickness: 3px;
}
The bar
Variable Sets Default
--simplemenu-bar-background The menubar background transparent
--simplemenu-bar-padding Inset for the menubar contents 0
--simplemenu-bar-align How the logo, the menu and the slide number align in the menubar stretch
--simplemenu-bar-z Stacking order 2
--simplemenu-bar-duration How long it takes to slide in and out 0.8s
--simplemenu-bar-easing The easing for the transition cubic-bezier(0.26, 0.86, 0.44, 0.985)
--simplemenu-bar-hidden-transform Where the menubar is while hidden translateY(-100%), or translateY(100%) on a bottom menubar
--simplemenu-bar-hidden-opacity How visible it is while hidden 1
--simplemenu-line-height Line height inside the menubar 1
--simplemenu-font-min Floor of the font-size clamp 16px
--simplemenu-font-max Ceiling of it 80px

The menubar is transparent by default and has a border along the edge next to the slides.

Variable Sets Default
--simplemenu-rule-width Thickness of the border 1px
--simplemenu-rule-color Its colour currentColor
--simplemenu-rule-opacity Opacity for the border 0.3

The <ul class="menu"> and every item inside it can be generated by Simplemenu, so a variable is the only way to change it.

Variable Sets Default
--simplemenu-item-opacity An item at rest 0.33
--simplemenu-item-opacity-hover Hover and keyboard focus 0.75
--simplemenu-item-opacity-active The current chapter 1
--simplemenu-item-color An item’s colour at rest currentColor
--simplemenu-item-color-hover It on hover and keyboard focus unset, so --simplemenu-item-color
--simplemenu-item-color-active The current chapter unset, so --simplemenu-item-color
--simplemenu-item-background An item’s background at rest transparent
--simplemenu-item-background-hover It on hover and keyboard focus unset, so --simplemenu-item-background
--simplemenu-item-background-active The current chapter unset, so --simplemenu-item-background
--simplemenu-item-radius Corner radius of that background 0
--simplemenu-item-decoration text-decoration on a menu link none
--simplemenu-item-decoration-hover It on hover unset, so --simplemenu-item-decoration
--simplemenu-item-padding Padding on each item 0.5em 1em
--simplemenu-item-gap Space between items 0
--simplemenu-item-duration How long a state change takes, opacity, colour and background alike 0.2s
--simplemenu-item-easing The curve it changes on ease-in-out

An item that Simplemenu could not link, because its chapter has no id to point at, is a bare <li> rather than a link. It takes the same colours, background and padding, so that the two kinds of item in one menu still match.

Layout

The menu is a flex row that the bar centres between whatever else it holds. The next 4 vars are about how it is positioned and how much room its items take.

Variable Sets Default
--simplemenu-menu-margin Where the menu itself sits in the menubar 0 auto
--simplemenu-menu-justify Where the items are positioned along the menu center
--simplemenu-item-grow 0 leaves each item the width of its own label, 1 shares the menu’s width out between them equally 0
--simplemenu-item-align An item’s height in a menubar made taller by a logo center

If the (optional) marker under the current item does not line up with the edge of the bar, use --simplemenu-item-align. If you set it to stretch, each item is as high as the bar, so the marker lines up with that line. flex-start and flex-end put the item against the top or the bottom.

Hiding the bar, or hiding the menu

In Reveal, you can use data-state with a classname on slides. That classname is then set on the Reveal.js viewport, so that elements outside the slides can use that CSS.

data-state Does
hide-menubar Slides the whole bar out of view, logo and slide number with it
hide-menu Fades the menu out, and leaves the rest of the bar where it is

hide-menubar uses the bar’s own timing, --simplemenu-bar-duration and --simplemenu-bar-easing from the table above.

By default the bar slides out of view. You can also fade instead of transform, or you can use both at the same time. Fading:

:root {
    --simplemenu-bar-hidden-transform: none;
    --simplemenu-bar-hidden-opacity: 0;
}

The menu has its own timing:

Variable Sets Default
--simplemenu-menu-duration How long the menu takes to fade out and back in 0.2s
--simplemenu-menu-easing The curve it fades on ease-in-out
Optional marker (“tab” line under the active item)

By default, Simplemenu shows inactive chapters with opacity and the active chapter being fully opaque. But you can also give it a ‘tab’ line, a marker, under the active item. There is no marker under it until you give one a size.

Variable Sets Default
--simplemenu-marker-thickness Thickness/size of the marker, and whether it appears at all 0
--simplemenu-marker-color Its colour currentColor
--simplemenu-marker-radius Its corner radius 0
--simplemenu-marker-edge Which edge it is on auto 0
--simplemenu-marker-margin-x Space on the left and the right of it 0, so the whole width

--simplemenu-marker-edge takes two values, the start and the end. auto 0 puts the marker along the item’s bottom edge, and 0 auto puts it along the top. Those two are the values you want; it is an inset-block, so a keyword such as top does not work. A menubar with the bottom class flips it to the top edge on its own, so that the marker faces the slides either way, and setting the variable overrides that.

Simplemenu does not add a logo for you. That would not make any sense. If you want one, then put it in your barhtml and give it the class logo. The Simplemenu CSS styling just makes sure that it is positioned nicely and that it does not get too big.

Variable Sets Default
--simplemenu-logo-width Width of the box around the logo auto
--simplemenu-logo-padding Padding inside that box 0.4em 0
--simplemenu-logo-maxheight Maximum on the image’s height 1.8em
--simplemenu-logo-maxwidth Maximum on its width none
--simplemenu-logo-margin Margin on the image itself 0

A logo is sized by its height by default. If you want to size it by its width instead, then set --simplemenu-logo-width and --simplemenu-logo-maxwidth: 100%.

If you add a menubar manually or through the options, you can also move the slide number into it. These are the CSS vars to style it:

Variable Sets Default
--simplemenu-link-padding For any other link in the bar 0.4em 0
--simplemenu-focus-width Thickness of the keyboard focus ring 2px
--simplemenu-focus-color Its colour currentColor
--simplemenu-focus-radius Its corner radius 0.2em
--simplemenu-slidenumber-size Size of the slide number 0.75em
--simplemenu-slidenumber-minwidth Holds its width as the number changes 4em
--simplemenu-slidenumber-padding Trailing space after it 0 1em 0 0
--simplemenu-slidenumber-opacity The slide number at rest unset, so --simplemenu-item-opacity, then 0.75
--simplemenu-slidenumber-opacity-hover It on hover unset, so --simplemenu-item-opacity-hover, then 1

The two opacities for the slide number are unset, so the number uses the same values as the menu items and matches them without a second edit. If you set them, the number uses your values instead. These only apply to a slide number that you have moved into the bar. A slide number left in its normal place is not styled by Simplemenu at all.

Where the stylesheet comes from

Simplemenu finds and loads its own stylesheet, so most decks never set anything here. If it cannot find it, maybe because the plugin is in a bundle, or it is somewhere the plugin cannot work out, then use csspath.

simplemenu: {
    csspath: "plugin/simplemenu/simplemenu.css"
}

If you import the stylesheet yourself, then set csspath: false so that Simplemenu does not load a second copy. A stylesheet of your own can also say so, which is useful when you cannot reach the plugin’s options:

:root {
    --cssimported-simplemenu: true;
}

csspath loads that file instead of Simplemenu’s own. If you do write your own CSS, then make sure to use simplemenu-bar and simplemenu-menu for the bar and the menu respectively.

HTML

It is easy to set up your HTML structure for Simplemenu. To keep the Simplemenu on every slide, put it outside of the .slides. Simplemenu can automatically do this for you if you use the barhtml option, so that you do not need to edit the template.

The auto way

Start by giving data-names to your sections:

<div class="slides">
    <section data-name="Menu item one">
        //...
    </section>
    <section data-name="Menu item two">
        //...
    </section>
    <section data-name="Menu item three">
        //...
    </section>
</div>

That is already enough for a menubar: Simplemenu adds a header bar with an empty menu in it, and fills that menu with links to your sections.

If you want a different bar, you can set your own through the options like this (yes, even when you use Markdown, you have to write a small piece of HTML here):

Reveal.initialize({
    // ...
    simplemenu: {
        // ...
        barhtml: {
            header: "<nav class='menubar'><ul class='menu'></ul></nav>",
            footer: ""
        }
    },
    plugins: [ Simplemenu ]
});

…or manually in your markup like this:

<nav class="menubar">
    <ul class="menu"></ul> <!-- Keep this empty -->
</nav>
<div class="slides">
    <section data-name="Menu item one">
        //...
    </section>
    <section data-name="Menu item two">
        //...
    </section>
    <section data-name="Menu item three">
        //...
    </section>
</div>

A bar you supply yourself is recognised, and Simplemenu then does not add one of its own. It counts as yours when it has the menubarclass, or when there is a menu inside it for Simplemenu to fill. A menu inside the slides counts as a table of contents rather than a bar, so Simplemenu still fills it with section links, and the menubar stays as well.

A bar that Simplemenu generates gets an id: simplemenu-headerbar for the header, simplemenu-footerbar for the footer. A bar you write yourself keeps the id you gave it, and an id in your own barhtml is kept too.

Plugin order

A bar from barhtml does not exist until Simplemenu has run. If you use the barhtml entry, then make sure to load Simplemenu before any other plugin that needs to know about the rendered content of the menubar.

The manual way

<nav class="menubar">
    <ul class="menu">
        <!-- Here's the menu -->
        <li><a href="#/firstchapter">First chapter</a></li>
        <li><a href="#/secondchapter">Second chapter</a></li>
        <li><a href="#/thirdchapter">Third chapter</a></li>
    </ul>
</nav>
<div class="slides">
    ...
</div>

The top-level sections (that should be in the menu) need to have an ID:

<div class="slides">
    <section id="firstchapter">
        <section>
            <h2>This is 1</h2>
        </section>
        <section>
            <h4>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h4>
        </section>
    </section>
    <section id="secondchapter">
        <h2>This is 2, no child slides</h2>
    </section>
    <section id="thirdchapter">
        <section>
            <h2>This is 3</h2>
        </section>
        <section>
            <h4>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h4>
        </section>
    </section>
</div>

This manual way can also use the data-name of the sections, which will then link if the link text is exactly the same as the data-name. Set the selectby: "data-name" option if you set it up like that.

Markdown

Simplemenu also supports Markdown, but you need to consider how you add data-names to your sections. Because of the way how Reveal generates vertical stacks, you can’t directly add a data-name to those. The workaround is to add a data-stack-name to the first vertical slide in those stacks:

# Simplemenu
### for Reveal.js
Using Markdown
---
### Table of Contents
<ul class="menu"><ul>
---
<!-- .slide: data-name="Regular slide" -->
## Slide 1
A paragraph with some text and a [link](http://hakim.se).
---
<!-- .slide: data-stack-name="Vertical" -->
## Vertical slide 1
----
## Vertical slide 2

The above example is from an externally loaded Markdown file, with the vertical separator specifically set to \n----\n, but any setup will work.

If you use Quarto, the syntax of the Markdown is a little bit different:

## Table of Contents
<ul class="menu"><ul>

# Slide 1 {data-name="Regular slide"}
A paragraph with some text and a [link](http://hakim.se).

# Vertical slide 1 {data-stack-name="Vertical"}

## Vertical slide 2

Moving the slide number to a menubar

If you add a menubar manually or through the options, you can also move the slide number into it. If a div with the class slide-number is found within a menubar, it is removed from the root Reveal element, and used in that menubar. This functionality is similar to the RelativeNumber plugin. You will need to adjust the CSS yourself, like making the elements relative instead of absolute.

Reveal.initialize({
    // ...
    simplemenu: {
        // ...
        barhtml: {
            header: "<nav class='menubar'><ul class='menu'></ul><div class='slide-number'></div></nav>",
            footer: ""
        }
    },
    plugins: [ Simplemenu ]
});

Using the flat option

Sometimes you’ll want to limit your presentation to horizontal slides only. To still use ‘chapters’ with several slides, you can use the flat option. By default, it is set to false, but you can set it to true. Then, when a data-name is set for a slide, any following slides will keep that menu name. Whenever a slide is encountered with data-sm="false", the inheritance will stop.

<nav class="menubar">
    <ul class="menu"></ul> <!-- Keep this empty -->
</nav>
<div class="slides">
    <section data-name="Chapter 1">
        //... (Chapter 1 will be active)
    </section>
    <section>
        //... (Chapter 1 will be active)
    </section>
    <section data-name="Chapter 2">
        //... (Chapter 2 will be active)
    </section>
    <section>
        //... (Chapter 2 will be active)
    </section>
    <section data-sm="false">
        //... (No menu item will be active)
    </section>
</div>

Configuration

There are a few options that you can change from the Reveal.js options. The values below are default and do not need to be set if they are not changed.

Reveal.initialize({
    // ...
    simplemenu: {
        menubarclass: "menubar",
        menuclass: "menu",
        activeclass: "active",
        activeelement: "li",
        selectby: "id",
        barhtml: {
            header: "<nav class='menubar'><ul class='menu'></ul></nav>",
            footer: ""
        },
        flat: false,
        scale: 0.67,
        cssautoload: true,
        csspath: ""
   },
    plugins: [ Simplemenu ]
});

Like it?

If you like it, please star this repo!

And if you want to show off what you made with it, please do :-)

License

MIT licensed

Copyright (C) 2026 Martijn De Jongh (Martino)