Element Dialog, czyli modal przyszłości 💭
Kilka porad dotyczących jednej z podstawowych i często zaniedbywanych funkcjonalności w kontekście a11y.
Po co wychodzić poza treść? 🤔
Dodatkowe okienka "wyskakujące" przed główną treść strony są powszechnym elementem internetu. Wykorzystywane są do pokazywania dodatkowych, domyślnie ukrytych informacji, do ostrzeżeń przed podjęciem nieodwracalnej decyzji lub do zalewania użytkownika spamem reklam, newsletterów i zgód na przetwarzanie wszelkich możliwych do zdobycia danych.
Powinniśmy pamiętać, że z powodu, iż modal może całkowicie zakryć i "odciąć" użytkownika od pozostałych elementów interfejsu – jest bardzo potężnym narzędziem. Jak to potężne narzędzie – powinien być wykorzystywany z rozsądkiem, a nie zawsze, kiedy tylko to możliwe.
Jak to jednak zrobić poprawnie technicznie, uwzględniając możliwości współczesnych przeglądarek oraz dobre praktyki w zakresie accessibility i wydajności?
Jaki tag HTML wybrać? 🧱
Przez lata dla wyskakujących okienek stosowany był najczęściej element, który ma najbardziej uniwersalne zastosowanie, czyli <div>
. Tag ten nie ma żadnego znaczenia semantycznego i może być stosowany w jakimkolwiek przypadku, kiedy potrzebujemy zgrupować kilka elementów w jedną całość, a nie mamy do tego bardziej semantycznego znacznika, jak np. <header>
, <section>
czy <nav>
.
Jednak od niedawna, wszystkie współczesne przeglądarki internetowe wspierają już nowy znacznik, którego natywnym przeznaczeniem są wszelkiego rodzaju okienka, prezentowane użytkownikowi ponad resztą interfejsu. Jest to element <dialog>
.
Semantyka i API przeglądarek
Element <dialog>
podobnie jak <div>
jest elementem blokowym, ale poza samą semantyką, w przeciwieństwie do tagów takich, jak np. wspomniane wcześniej header
, section
czy nav
, ma kilka dodatkowych właściwości, które wyróżniają go na tle pozostałych elementów HTML.
Domyślnie element <dialog>
jest ukryty.
Aby wyświetlić go użytkownikowi, powinno się korzystać z dedykowanej do tego metody open
(modelessly) lub openModal
(która wyświetla <dialog>
w postaci modala i rozszerza go o kilka ciekawych właściwości), do zamykania służy natomiast metoda close
.
Element <dialog>
wyposażony jest w dodatkowy atrybut open
, którym również możemy sterować w celu wyświetlania i ukrywania modala. Niestety obecnie jedynie przy wykorzystaniu metody showModal
otrzymujemy wspomniane wcześniej, dodatkowe, unikalne właściwości, takie jak pseudoelement ::backdrop
, który służy jako tło dla modala.
Po za metodą close
, modal możemy zamknąć też submitując form
, który znajduje się w środku elementu <dialog>
i posiada atrybut method
ustawiony na dialog
.
Accessibility
Dialog otwarty metodą showModal
, w przeciwieństwie do innych elementów HTML wyposażony jest out of the box w funkcjonalność, która polega na automatycznym focusowaniu pierwszego "focusowalnego" elementu wewnątrz po otwarciu (więcej na ten temat możesz dowiedzieć się w innym moim poście: Atrakcyjny i funkcjonalny focus ♿️) oraz ma możliwości zamknięcia okienka za pomocą klawisza Esc.
❌ Twoje przeglądarka nie wspiera jeszcze tej funkcjonalności!
ℹ️ Sprawdź wsparcie: caniuse.com/dialog
Pseudoelement ::backdrop
Zarówno dla okienka, jak i dla tła, za które odpowiada pseudolement ::backdrop
można oczywiście dodać w pełni customowe style.
Problem ze scrollem wewnątrz 🖱
Zachowaniem, które często nie jest pożądane, a występuje domyślnie, jest możliwość scrollowania głównej części interfejsu w tle, po otwarciu modala, co czasami może utrudniać scrollowanie wewnątrz okienka.
Rozwiązaniem tego problemu jest warunkowe ustawienie overflow: hidden
dla elementu <html>
, co zablokuje możliwość scrollowania zawartości głównego okna przeglądarki (działa w większości przeglądarek).
Przy wykorzystaniu nowego selektora :has
, można to zrobić bez linijki JavaScriptu.
Pseudoklasa :has
jest bardzo potężną, ale jednak nowością i wspierana jest tylko przez najnowsze Safari oraz Chrome. Rozwiązanie uniwersalne z wykorzystaniem JavaScript oraz specjalnego dla <dialog>
eventu close
:
Warto pamiętać, że blokowanie scrolla w tle nie zawsze musi być dobrym rozwiązaniem, dlatego w większych aplikacjach warto się zastanowić, czy rozwiązanie to powinno być uniwersalne dla wszystkich modali, czy jednak stosowane indywidualnie.
Animowanie elementu <dialog>
✨
Ze względu na fakt, iż widoczność modala sterowana jest za pomocą zmiany właściwości display
, to użycie transition
na opacity
nie wchodzi w grę.
Najprostszy sposób to dodanie animacji za pomocą właściwości animation
, aczkolwiek animacja taka będzie pojawiać się jedynie na wejściu modala, a jego zamykanie będzie odbywać się już natychmiastowo.
Może wydawać się, że jest to tylko częściowy i w pewien sposób wybrakowany efekt, ale według mnie jest naprawdę dobrym kompromisem pomiędzy aspektami wizualnymi a użytecznością.
Pamiętajmy, że animacje interfejsów powinny z zasady być raczej szybkie i subtelne, ponieważ ich zadaniem jest przede wszystkim pomagać użytkownikowi w korzystaniu z interfejsu, a nie przeszkadzać! Zbyt długie, powolne pojawianie się (a tym bardziej znikanie) elementów takich jak właśnie modal, nawet przy efektach zapierających dech w piersiach, może zapierać dech, ale za pierwszym czy drugim razem. Później w sporej większości przypadków, będzie to raczej zachowanie irytujące użytkownika, który chce dostać się do jakiegoś miejsca szybko i sprawnie.
Umiejscowienie w drzewie DOM i Portale 🌳
Dodatkową, ciekawą właściwością <dialog>
(tylko w przypadku korzystania z metody showModal
) jest fakt, iż ignoruje on całkowicie wartości z-index
innych elementów i zawsze jest na samej górze.
Pomimo tego, dobrą praktyką jest, aby element z modalem (niezależnie od tego, czy jest to <dialog>
, czy inny tag HTML) nie był zagnieżdżony głęboko w drzewie DOM.
O ile w przypadku korzystania z czystego HTMLa i JavaScriptu nie jest to szczególnie trudne do osiągnięcia, o tyle w przypadku framework-ów opartych o architekturę komponentów (jak React czy Vue) nie jest to takie proste, aby wyciągnąć jakiś element "poza" zagnieżdżony komponent, w którym jest renderowany i w którym zarządzamy stanem.
Do takich przypadków służą tzn. Portale. Dzięki nim możemy wskazać inny węzeł drzewa DOM, w którym komponent powinien się wyrenderować.
Wspominając wcześniejsze ograniczenia, możemy sterować widocznością modala za pomocą atrybutu open
(bez wszystkich specjalnych funkcjonalności).
Albo posłużyć się hookiem useEffect
, który na podstawie propsów będzie triggerował odpowiednie metody (wersja full).
Podsumowanie 📋
Prawdopodobnie w przyszłości element <dialog>
stanie się powszechnie wykorzystywany, podobnie jak dzisiaj <nav>
czy <footer>
. Na chwilę obecną jednak wsparcie przeglądarek może budzić uzasadnione obawy co do tego, czy jest to dobry moment na korzystanie z tego elementu.
Rozwiązaniem wartym przemyślenia jest skorzystanie z <dialog>
już dzisiaj, posiłkując się odpowiednim pollyfilem, który zadba o fallback w niewspierających przeglądarkach.
Natomiast jeśli z jakiś jeszcze powodów, całkowicie wstrzymujemy się obecnie z korzystania z <dialog>
, to ze względu na dostępność, warto pamiętać o dodaniu atrybutów aria-modal
oraz role
z odpowiednimi wartościami do elementu, który służy za modal.