Springe zum Inhalt
griechisches lambda

Mobile Navigationsmenüs ohne JavaScript

Von Paul Hempel, 25.10.2023

Um unsere Firmen Webseite möglichst barrierefrei zu gestalten, wollten wir zentrale Funktionen ohne JavaScript verfügbar machen. Aus diesem Grund beschäftigten wir uns mit der Frage ob ein mobiles Navigationsmenü durch reines HTML und CSS gelöst werden kann.

Inhaltsverzeichnis

  1. Die zentrale Umsetzung
  2. Ein Hamburger Menü Icon hinzufügen
  3. Animationen
  4. Das Layout anpassen
  5. Visuelle Variationen

Die zentrale Umsetzung

Der vollständige Code ist als Gist auf GitHub zu finden.

Auf HTML Seite halten wir die Navigation recht einfach. Im <nav> Bereich sind zwei Listen, eine davon ist für die mobile Ansicht mit einem ausklappbaren details-Block umschlossen:

<nav>
<!-- desktop -->
<ul>
    <li><a href="/">About</a></li>
    <li><a href="/">Blog</a></li>
    <li><a href="/">Contact</a></li>
</ul>

<!-- mobile -->
<details>
    <summary aria-label="open mobile menu"></summary>
    <ul>
        <li><a href="/">About</a></li>
        <li><a href="/">Blog</a></li>
        <li><a href="/">Contact</a></li>
    </ul>
</details>
</nav>


Auf dem Desktop wollen wir den details-Tag und dessen Inhalte nicht sehen, in der mobilen Ansicht wiederum das Desktop-Menü nicht:

nav>details {
    display: none;
}

@media (max-width: 991px) {
    /* hide desktop menu */
    nav>ul {
        display: none !important;
    }
    
    /* show mobile menu */
    nav>details {
        display: block !important;
    }
}

Fertig?!

Unsere Lösung erfüllt nun alle technischen Anforderungen an ein responsives Menü. Visuell möchten wir im folgenden Teil das Ergebnis noch verbessern.

Ein Hamburger Menü Icon hinzufügen

Meistens sehen wir ein Hamburger Menü Icon und ein Schließen-Icon in mobilen Varianten von Webseiten. Das soll hier natürlich nicht fehlen – und nur mit CSS umgesetzt werden:

/* custom image for mobile menu */
nav > details > summary::after {
    content: '';
    background-image: url(img/menu.svg);
    background-size: 2rem 2rem;
    
    display: inline-block;
    width: 2rem;
    height: 2rem;
}

/* alternative image for open mobile menu */
nav > details[open] > summary::after {
    background-image: url(img/close.svg);
}

/* icons used: https://ionic.io/ionicons */

Das details[open]-Attribut lässt uns prüfen, ob <details> geöffnet oder geschlossen ist. Wenn es offen ist, ersetzen wir in unserem Fall das Hintergrundbild.
Da wir mit dem ::after Pseudoelement arbeiten, wollen wir dafür sorgen, dass das Icon nicht die ganze Seite einnimmt. Mit background-size, display: inline-block;, width: 2rem; und height: 2rem; in Kombination können wir dafür sorgen, dass das Icon unsere gewünschte Größe hat.

Animationen

Da wir mit CSS nicht die Eltern-Knoten auswählen können, greifen wir hier zu einem kleinen Trick, der eine dezente Animation hinzufügt. Animationen sind nicht zwingend notwendig, da es sich hier, meiner Meinung nach, nicht um eine offensichtliche Lösung handelt, wollen wir uns kurz anschauen, was möglich ist:

nav>details>summary {
    [...]
    /* slight transition */
    transition: margin-bottom 150ms ease-out;
}

/* add margin for slight transition */
nav>details[open]>summary {
    margin-bottom: 1rem;
}

Da wir mit CSS Selektoren (noch?) nicht auf ein Eltern-Element zugreifen können um dieses im Ganzen zu animieren, fügen wir im offenen Zustand des dialog einen kleinen margin-bottom hinzu, der mit dem transistion-Attribut von 0rem auf 1rem in 150ms wächst.

Das Layout anpassen

Das genaue Layout unterliegt individuellen Anforderungen. Daher soll in diesem Artikel nicht weiter darauf eingegangen werden. Nichtsdestotrotz haben wir im Gist den Code für eine ordentliche Layout Variante als Beispiel angereichert.

Visuelle Variationen

Ein Fixierter Header

Der bestehende Code lässt die Navigationsleiste mit der gesamten Webseite scrollen. Falls diese jedoch immer sichtbar sein soll, können wir sie wie folgt fixieren:

header {
    background-color: aliceblue;
    display: flex;
    justify-content: space-between;
    
    /** toggle fixed header **/
    position: fixed;
    width: 100%;
}
main {
    /* don't forget to increase the top padding with fixed navbar */
    padding: 4rem 1rem;
}

Ein bildschirmfüllender, fixierter Header 

Bis jetzt ist der header im aufgeklappten Zustand nur so groß wie der Inhalt. Falls das nicht gewünscht ist und er stattdessen die ganze Seite ausfüllen soll, können wir das wie folgt beeinflussen:

nav>details>ul {
    flex-direction: column;
    align-items: end;
    margin: 0;
    
    /** toggle full page menu **/
    height: 100vh;
}

Ein bekanntes Problem

Durch fehlendes, sogenanntes "Focus-Trapping" kann die Seite im Hintergrund weiter gescrollt werden. Das kann ausschließlich durch JavaScript verhindert werden (Stand: 2023). Daher haben wir uns bei unserer Webseite für den fixierten Header entschieden, der nicht bildschirmfüllend ist.

Über den Autor

Paul Hempel

Paul wohnt in Mainz und arbeitet hauptsächlich als Clojure Entwickler an Webseiten und mobilen Apps. Thematisch beschäftigt er sich zusätzlich mit Themen wie barrierefreiem (Web-)Design, dem Lehren von programmieren und was gute Softwareentwicklung bedeuten kann.