Gunnar Bittersmann: CSS-Kniffliges zum Wochenende

Vor einiger Zeit hab ich mal dieses responsive Menü gebaut. Nun musste ich erfahren, dass der button ins nav-Element gehört – so wie in diesem Pen. Der funktioniert auch im Firefox, weil der (als einziger) display: contents unterstützt. Hat jemand eine Idee, wie man das auch für andere Browser hinbekommt? (Außer mit Floats?)

LLAP 🖖

--
“When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
  1. Vor einiger Zeit hab ich mal dieses responsive Menü gebaut. Nun musste ich erfahren, dass der button ins nav-Element gehört – so wie in diesem Pen. Der funktioniert auch im Firefox, weil der (als einziger) display: contents unterstützt. Hat jemand eine Idee, wie man das auch für andere Browser hinbekommt? (Außer mit Floats?)

    Meine Entwickelrtools zeigen 5 net::ERR_INSECURE_RESPONSE-Fehler an, liegt vermutlich am fehlenden https. Wenn es daran nicht liegt, was funktioniert nicht wie erwartet?

    Wäre hier nicht eigentlich <details> angebracht?

    <nav id="main-nav">
      <details open>
        <summary aria-controls="main-nav-list" aria-expanded="true">Menu</summary>
        <ul id="main-nav-list">
    	    <li><a href="">fluid grids</a></li>
          <li><a href="">flexible images</a></li>
          <li><a href="">media queries</a></li>
        </ul>
      </details>
    </nav>
    
    1. @@1unitedpower

      Meine Entwickelrtools zeigen 5 net::ERR_INSECURE_RESPONSE-Fehler an, liegt vermutlich am fehlenden https.

      Ja, das hab ich auf dem Schirm. Nicht öffentlich Wein (HTTPS) predigen und heimlich Wasser (HTTP) saufen. Ich muss meinen Kram endlich mal umstellen.

      Wenn es daran nicht liegt

      Nein, die von CodePen nicht mehr eingebundenen Scripte wären für den Backlink vom Codepen hier ins Forum und für hanging punctuation, haben also mit der Sache hier nichts zu tun.

      was funktioniert nicht wie erwartet?

      In Nicht-Füchsen ist das Layout zerschossen: das geöffnete Menü steht (außer bei mittelbreiten Viewports) neben der Überschrift anstatt darunter.

      Wäre hier nicht eigentlich <details> angebracht?

      Dann müsste man aber die Abfrage, ob das Menü initial geöffnet oder geschlossen sein soll (open-Attribut gesetzt oder nicht), ins JavaScript verlagern anstatt per media query im CSS zu machen. Finde ich unschön.

      Und ggfs. noch einen Polyfill für Edge/IE bauen, die details noch nicht unterstützen.

      Die Elementstruktur wäre dieselbe: Steuerelement und Liste als Geschwister im Container.

      <nav>
        <button><button>
        <ul></ul>
      </nav>
      

      vs.

      <details>
        <summary></summary>
        <ul></ul>
      </details>
      

      (Bei letzterem kommt nav noch außenrum.)

      Das Stylingproblem wäre also auch dasselbe.

      Nur dass sich summary vielleicht in Browsern als störrisch erweisen könnte.

      LLAP 🖖

      --
      “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
      1. In Nicht-Füchsen ist das Layout zerschossen: das geöffnete Menü steht (außer bei mittelbreietn Viewports) neben der Überschrift anstatt darunter.;

        Hm, wenn man dem display: flex vom <head> entfernt, und <h1> display: float gibt, dann verhält es sich bei mir in Chrome wie in FF. Aber darauf bist du ja auch schon gekommen, wieso wolltest du die Lösung nochmal vermeiden? Du könntest auch mit @supports (display: contents) eine Feature-Weiche bauen.

        Wäre hier nicht eigentlich <details> angebracht?

        Dann müsste man aber die Abfrage, ob das Menü initial geöffnet oder geschlossen sein soll (open-Attribut gesetzt oder nicht), ins JavaScript verlagern anstatt per media query im CSS zu machen. Finde ich unschön.

        Das musst du auch für aria-expanded machen, im Moment lügt das Attribut noch über die Sichbarkeit der Navigations-Liste. Mit matchMedia hast du in JavaScript die gleichen Möglichkeiten wie mit CSS-MediaQueries.

        Und ggfs. noch einen Polyfill für Edge/IE bauen, die details noch nicht unterstützen.

        Das Polyfill hast du doch schon fast fertig 😉 Du müsstest dein Skript nur auf das Markup abstimmen. Ich würde allerdings zu einem fertigen Polyfill greifen.

        Nur dass sich summary vielleicht in Browsern als störrisch erweisen könnte.

        Edge und IE haben eben kein spezfisches UserAgent-Stylesheet für <summary>, aber ist das wirklich ein Problem? Die DOM-Interfaces sollten von einem guten Polyfill implementiert werden.

        Entscheidend für das Markup sollte letzten Endes doch die Zugänglichkeit sein, nicht der Komfort für den Entwickler. Ich weiß nicht, welche Lösung Nutzer(innen) von assistiver Technolgie hier bevorzugen, müsste man wohl mal Betroffene fragen.

        1. Hallo 1unitedpower,

          <h1> display: float gibt

          float ist kein gültiger Wert der display-Eigenschaft. Das entspricht also genau meinem Vorschlag 😉

          Bis demnächst
          Matthias

          --
          Rosen sind rot.
        2. @@1unitedpower

          Aber darauf bist du ja auch schon gekommen, wieso wolltest du die Lösung nochmal vermeiden?

          Dann wäre wohl auch ein Clearfix fällig … Das wäre bestenfalls ein Hack, keine gute Lösung.

          Mittel- bis langfristig wäre der Weg hier, die fehlende Unterstützung von display: contents in die Bugtracker aller Browser sans Firefox einzutüten. Stelle ich mir nicht besonders kompliziert vor, das zu implementieren. Das Feature ist auch anderswo nützlich/notwendig: Address book.

          Das musst du auch für aria-expanded machen, im Moment lügt das Attribut noch über die Sichbarkeit der Navigations-Liste.

          Nicht wirklich. Bei breiten Viewports ist der Button ja weg, im Sinne von: richtig weg – auch für AT (display: none). Das aria-expanded-Attribut eines Buttons, den es gar nicht gibt, sollte dann irrelevant sein.

          Bei details liegt der Fall etwas anders: das open-Attribut hängt am details-Container-Element, nicht am verschwindenden summary-Element.

          Außerdem: verschwindendes summary-Element — was passiert dann? Die Spec sagt: “If there is no summary child element, a user agent should provide its own legend (e.g. in English "Details" or Spanish "Detalles").” Könnten UAs solch ein eigenes Dingens auch anbieten, wenn man summary { display: none } setzt?

          Entscheidend für das Markup sollte letzten Endes doch die Zugänglichkeit sein, nicht der Komfort für den Entwickler.

          Auf jeden Fall.

          Dabei wäre auch zu bedenken, dass nicht nur Browser details unterstützen müssen, sondern auch AT. Gegenwärtig bleiben womöglich noch mehr Nutzer auf der Strecke als Edge- und IE-Nutzer.

          Ich weiß nicht, welche Lösung Nutzer(innen) von assistiver Technolgie hier bevorzugen, müsste man wohl mal Betroffene fragen.

          Gute Frage; werde ich bei Gelegenheit mal weitergeben.

          LLAP 🖖

          --
          “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
          1. @@Gunnar Bittersmann

            Bei breiten Viewports ist der Button ja weg, im Sinne von: richtig weg – auch für AT (display: none).

            Im Sinne von: für den Nutzer weg. Der Button ist noch im DOM, aber weder auf dem Bildschirm noch im accessibility tree.

            LLAP 🖖

            --
            “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
          2. Nicht wirklich. Bei breiten Viewports ist der Button ja weg, im Sinne von: richtig weg – auch für AT (display: none). Das aria-expanded-Attribut eines Buttons, den es gar nicht gibt, sollte dann irrelevant sein.

            Stimmt auch wieder, klingt in meinen Ohren aber nicht besonders robust. Ändert sich die Anforderung und die willst den Button doch per CSS einblenden, musst du dich daran erinnern auch dein JavaScript anzupassen.

            1. @@1unitedpower

              Nicht wirklich. Bei breiten Viewports ist der Button ja weg, im Sinne von: richtig weg – auch für AT (display: none). Das aria-expanded-Attribut eines Buttons, den es gar nicht gibt, sollte dann irrelevant sein.

              Stimmt auch wieder, klingt in meinen Ohren aber nicht besonders robust. Ändert sich die Anforderung und die willst den Button doch per CSS einblenden, musst du dich daran erinnern auch dein JavaScript anzupassen.

              Äh, nö. Der Button wird ja eingeblendet – bei schmalen Viewports. Und dann greift das CSS und sorgt dafür, dass das Menü in Abhängigkeit von dessen aria-expanded-Attributwert ein- oder ausgeblendet wird.

              Bei breiten Viewports ist das Menü immer zu sehen – und dann will man den Button keinesfalls im UI haben.

              LLAP 🖖

              --
              “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
              1. Bei breiten Viewports ist das Menü immer zu sehen – und dann will man den Button keinesfalls im UI haben.

                Ja, das ist der Ist-Zustand, ich wollte darauf hinweisen dass sich das ändern könnte. Nicht nur mit der Zeit, sondern auch, wenn du die Navigation in einem anderen Projekt wieder verwenden möchtest. Zum Beispiel stelle ich mir gerade eine Seite vor, bei der die Navigation in einem sticky Header liegt. Da möchte ich sie auf breiten Viewports standardmäßig einblenden, beim Scrollen dann aber vielleicht doch hinter einem Hamburger-Button verbergen. Oder vielleicht möchte ich den Hamburger-Button auch auf breiten Viewports anbieten, um eine uniforme Menüführung über verschiedene Viewports hinweg anbieten zu können. Selbst wenn du beides nicht kommen siehst oder für Design-Sünden hälst (was gut sein kann, ich bin kein UI-Designer), dann denk wenigstens an Murphy's law 😉

                1. @@1unitedpower

                  Ja, das ist der Ist-Zustand, ich wollte darauf hinweisen dass sich das ändern könnte … Zum Beispiel stelle ich mir gerade eine Seite vor …

                  Deine Bedenken sind, dass der Button zu sehen wäre/im accessibility tree wäre und nicht das Menü auf- und zuklappen würde; dann also dessen aria-expanded-Attribut lügen würde.

                  Der Fall kann aber gar nicht eintreten[1], weil

                  	[aria-controls="main-nav-list"]
                  	{
                  		display: inline;
                  	}
                  

                  (was den Button erst einblendet – sonst gilt display: none) und

                  	#main-nav [aria-controls="main-nav-list"][aria-expanded="false"] + ul
                  	{
                  		display: none;
                  	}
                  

                  (was bei dessen aria-expanded="false" die Navigationsliste ausblendet[2]) im selben media query stehen, also garantiert gleichzeitig wirken.

                  LLAP 🖖

                  --
                  “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory

                  1. Es sei denn, das Stylesheet wird nicht geladen/nicht interpretiert. ↩︎

                  2. In der neuesten Version ist es

                      #main-nav [aria-controls="main-nav-list"][aria-expanded="false"] + ul
                      {
                    	  visibility: hidden;
                    	  /* or alternatively: border: none; */
                    	  margin: 0;
                      }
                    
                      #main-nav [aria-controls="main-nav-list"][aria-expanded="false"] + ul li
                      {
                      	display: none;
                      }
                    
                    ↩︎
                  1. Der Fall kann aber gar nicht eintreten

                    Im aktuellen Zustand nicht, das habe ich erst falsch gedeutet und dir in dem Punkt ja auch schon Recht gegeben. Meine weiteren Bedenken gelten aber nicht dem status quo deiner Lösung, sondern der Frage, wie sie sich verhält, wenn sich äußere Umstände ändern. Eine robuste Lösung zeichnet sich durch gute Wartbarkeit und hohe Wiederverwendbarkeit aus. Ein Beispiel: wenn ich den Hamburger-Button nun doch auf breiteren Viewports anbieten möchte, reicht es dann den Button per CSS einzublenden und alles funktioniert erwartungsgemäß oder erlebe ich eine Überraschung?

                    Man kann sicherlich nicht alle Umweltfaktoren antizipieren und daher bleibt es Abwägungssache, wogegen man sich schützen möchte. Ich finde, dass eine CSS-Änderungen keine JavaScript-Änderungen hinter sich herziehen sollte und umgekehrt und würde daher die Kopplung auch so gering wie möglich halten, also die Abhängigkeiten zwischen CSS und JS reduzieren. Seperation of Concerns ist meiner Erfahrung nach ein ziemlich effektives Werkzeug, um robuste Lösungen zu bauen, nur dazu wollte ich dir hier raten.

                    Konkret würde ich deine Lösung verbessern, indem ich den expandend-Zustand der Navigation in der CSS-Welt, in der JavaScript-Welt und in der ARIA-Welt miteinander synchronisiere.

                    1. @@1unitedpower

                      Eine robuste Lösung zeichnet sich durch gute Wartbarkeit und hohe Wiederverwendbarkeit aus. Ein Beispiel: wenn ich den Hamburger-Button nun doch auf breiteren Viewports anbieten möchte, reicht es dann den Button per CSS einzublenden und alles funktioniert erwartungsgemäß oder erlebe ich eine Überraschung?

                      Wie gestern gesagt möchtest du das nicht.

                      Wie im Vorposting gesagt, funktioniert alles bestens, wenn die Regeln zum Einblenden des Buttons und eventuellen Ausblenden des Menüs gleichzeitig gelten. Bei gleichem media query oder auch immer – aber s.o.

                      Ich finde, dass eine CSS-Änderungen keine JavaScript-Änderungen hinter sich herziehen sollte und umgekehrt

                      Das ist hier auch nicht der Fall.

                      und würde daher die Kopplung auch so gering wie möglich halten, also die Abhängigkeiten zwischen CSS und JS reduzieren.

                      [x] Done.

                      Konkret würde ich deine Lösung verbessern, indem ich den expandend-Zustand der Navigation in der CSS-Welt, in der JavaScript-Welt und in der ARIA-Welt miteinander synchronisiere.

                      Für „konkret“ ist mir das zu unkonkret.

                      LLAP 🖖

                      --
                      “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
                      1. Wie gestern gesagt möchtest du das nicht.

                        Okay, dann denk an Murphys law. Alles was schief gehen kann, wird schief gehen. Erster Hauptsatz der Software-Entwicklung.

                        Wie im Vorposting gesagt, funktioniert alles bestens, wenn die Regeln […] gleichzeitig gelten.

                        Wenn das Wörtchen „wenn“ nicht wäre. Genau diese Nebenbedingung ist unter leicht veränderten Umständen möglicherweise verletzt und Murphy sagt, dass genau dieser Fall eintreten wird. Und genau da meine ich, dass man die Navigation noch robuster gestalten könnte.

                        Für „konkret“ ist mir das zu unkonkret.

                        Besser? Man achte auf das aria-expanded-Attribut, das jetzt immer mit dem tatsächlichen Zustand der Navigation übereinstimmt. Dass CSS vereinfacht sich insofern, als dass nun nur noch das aria-expandend-Attribut relevant für die Anzeige der Unternavigation ist. JavaScript verkompliziert sich insofern, als dass man jetzt einen zweiten Event-Handler benötigt, ich habe dafür das drumherum ein wenig aufgeräumt, sodass der Code insgesamt hoffentlich trotzdem leichter zu verstehen ist.

        3. @@1unitedpower

          Hm, wenn man dem display: flex vom <head> entfernt, und <h1> display: float gibt

          Sieht dann so aus.

          Der Button wird per #main-nav { text-align: right } nach rechts gerückt;
          der Rest per #main-nav > * { text-align: left } wieder nach links.

          (Dass der Button bei sehr schmalen Viewports rechts ist und nicht wie vorher links, soll nicht weiter stören.)

          Problematisch ist die vertikale Ausrichtung des Buttons, hier müsste man wohl mit einer magic number ran. (Vorher erledigte das innerhalb der Flexbox align-items: center.)

          LLAP 🖖

          --
          “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
          1. Hallo Gunnar Bittersmann,

            Problematisch ist die vertikale Ausrichtung des Buttons, hier müsste man wohl mit einer magic number ran. (Vorher erledigte das innerhalb der Flexbox align-items: center.)

            Er muss nur dasselbe margin-top bzw. -bottom wie die Überschrift bekommen. Dann sollte er vertikal an der Grundlinie ausgerichtet sein.

            Bis demnächst
            Matthias

            --
            Rosen sind rot.
            1. @@Matthias Apsel

              Problematisch ist die vertikale Ausrichtung des Buttons, hier müsste man wohl mit einer magic number ran. (Vorher erledigte das innerhalb der Flexbox align-items: center.)

              Er muss nur dasselbe margin-top bzw. -bottom wie die Überschrift bekommen. Dann sollte er vertikal an der Grundlinie ausgerichtet sein.

              Nein, das sieht hier nur so aus, weil der Button in etwa dieselbe Höhe hat wie die Überschrift.

              Ändere mal die Schriftgröße header h1 { font-size: 3.25em }. Um den Button rechts neben die Überschrift zu bekommen, musst du den media query auf einen größeren Wert setzen, meinetwegen @media (max-width: 124em). Wie willst du den Button zur Überschrift (Float) ausrichten?

              LLAP 🖖

              --
              “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
              1. Hallo Gunnar Bittersmann,

                Nein, das sieht hier nur so aus, weil der Button in etwa dieselbe Höhe hat wie die Überschrift.

                Das wird für ein konkretes Projekt wohl ähnlich sein.

                Wie willst du den Button zur Überschrift (Float) ausrichten?

                Nicht floaten. Zum Beispiel display: inline (-block oder -flex) für h1 und nav in Verbindung mit vertical-align: middle für h1 und button.

                Bis demnächst
                Matthias

                --
                Rosen sind rot.
                1. @@Matthias Apsel

                  Nein, das sieht hier nur so aus, weil der Button in etwa dieselbe Höhe hat wie die Überschrift.

                  Das wird für ein konkretes Projekt wohl ähnlich sein.

                  Das ist der Tod jeder robusten Lösung.

                  Wie willst du den Button zur Überschrift (Float) ausrichten?

                  Nicht floaten. Zum Beispiel display: inline (-block oder -flex) für h1 und nav in Verbindung mit vertical-align: middle für h1 und button.

                  💯 Da bin ich gerade dran (WIP)

                  LLAP 🖖

                  --
                  “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
                  1. @@Gunnar Bittersmann

                    Da bin ich gerade dran (WIP)

                    Funzt in Browsern, die text-align-last: justify unterstützen. Also in so gut wie allen – außer Safari. Meh!

                    EDIT: Zu früh gefreut. Immer noch WIP …

                    EDIT 2: So, jetzt aber.

                    LLAP 🖖

                    --
                    “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
                    1. @@Gunnar Bittersmann

                      Da bin ich gerade dran (WIP)

                      Funzt in Browsern, die text-align-last: justify unterstützen. Also in so gut wie allen – außer Safari. Meh!

                      EDIT 2: So, jetzt aber.

                      So, jetzt auch in Safari. Browser, die text-align-last: justify nicht unterstützen, bekommen ein header::after-Pseudoelement, damit die Zeile mit Überschrift und Button nicht die letzte ist. Die Liste dann nicht display: flex, sondern inline-flex.

                      LLAP 🖖

                      --
                      “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
                      1. @@Gunnar Bittersmann

                        So, jetzt auch in Safari. Browser, die text-align-last: justify nicht unterstützen, bekommen ein header::after-Pseudoelement, damit die Zeile mit Überschrift und Button nicht die letzte ist.

                        Braucht man gar nicht …

                        Die Liste dann nicht display: flex, sondern inline-flex.

                        … wenn die Liste inline-flex bleibt, also zum Ausblenden nicht auf none gesetzt wird. Dann ist die Liste die letzte Zeile und man braucht für die Zeile mit Überschrift und Button kein text-align-last: justify.

                        display: none bekommen die List-Items verpasst. Die Liste wird per visibility: hidden ausgeblended – oder auch gar nicht, dann aber border: none.

                        header { padding-bottom: 0 }; den Abstand bekommt die Liste – fertig!

                        LLAP 🖖

                        --
                        “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
          2. @@Gunnar Bittersmann

            Du könntest auch mit @supports (display: contents) eine Feature-Weiche bauen.

            Hab ich im Pen nachgerüstet.

            Sieht dann so aus.

            LLAP 🖖

            --
            “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
  2. Hallo Gunnar Bittersmann,

    Hat jemand eine Idee, wie man das auch für andere Browser hinbekommt? (Außer mit Floats?)

    kein flex für den header? Oder verstehe ich dich falsch?

    Bis demnächst
    Matthias

    --
    Rosen sind rot.