CSS-Kniffliges zum Wochenende
Gunnar Bittersmann
- css
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 🖖
Vor einiger Zeit hab ich mal dieses responsive Menü gebaut. Nun musste ich erfahren, dass der
button
insnav
-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>
@@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 🖖
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.
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
@@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 🖖
@@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 🖖
Nicht wirklich. Bei breiten Viewports ist der Button ja weg, im Sinne von: richtig weg – auch für AT (
display: none
). Dasaria-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.
@@1unitedpower
Nicht wirklich. Bei breiten Viewports ist der Button ja weg, im Sinne von: richtig weg – auch für AT (
display: none
). Dasaria-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 🖖
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 😉
@@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 🖖
Es sei denn, das Stylesheet wird nicht geladen/nicht interpretiert. ↩︎
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;
}
↩︎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.
@@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 🖖
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.
@@1unitedpower
Hm, wenn man dem
display: flex
vom<head>
entfernt, und<h1>
display: float
gibt
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 🖖
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
@@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 🖖
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
@@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ürh1
undnav
in Verbindung mitvertical-align: middle
fürh1
undbutton
.
💯 Da bin ich gerade dran (WIP)
LLAP 🖖
@@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 🖖
@@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 🖖
@@Gunnar Bittersmann
So, jetzt auch in Safari. Browser, die
text-align-last: justify
nicht unterstützen, bekommen einheader::after
-Pseudoelement, damit die Zeile mit Überschrift und Button nicht die letzte ist.
Braucht man gar nicht …
Die Liste dann nicht
display: flex
, sonderninline-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 🖖
@@Gunnar Bittersmann
Du könntest auch mit
@supports (display: contents)
eine Feature-Weiche bauen.
Hab ich im Pen nachgerüstet.
LLAP 🖖
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