Dowiedz się czemu warto czekać na pseudoklasę :has!

Spis treści

  1. Wprowadzenie
  2. Pseudoklasa :has
    1. Jak działa pseudoklasa :has?
    2. Jakie pseudoklasa :has ma wsparcie i jaką biblioteką go zasymulować?
  3. Użycie pseudoklasy :has do stylowania formularzy na bazie walidacji pól
    1. Stworzenie skryptu transformującego CSS
    2. Style odpowiadające za kolorystykę walidacji
    3. Wymagany kod JS
    4. Efekt działania aplikacji
    5. Klikalny przykład na Github Pages
    6. Pełny kod przykładu na Github
  4. Podsumowanie

Wprowadzenie

Stack Overflow pełen jest pytań o to, czy w CSS istnieje „parent selector”. O ile możemy stylować dzieci bazując na tym, kto jest rodzicem – w drugą stronę póki co to nie działa.
Na szczęście w specyfikacji znajduje się pseudoklasa :has – a dzięki sprytnej bibliotece możemy używać jej nawet dziś, gdy jeszcze żadna przeglądarka go nie wspiera.

Zapraszam do przeczytania wpisu o tym, jak za pomocą tego selektora stylować walidację formularzy.

Pseudoklasa :has

Jak działa pseudoklasa :has?

Pseudoklasa :has wybiera element zawierający określona zawartość. Przykładowo:

.class:has(.another-class)

Wybierze element z klasą .class tylko wtedy, gdzie wewnątrz elementu .class znajduje się element z klasą .another-class.

Struktura ta mogłaby wyglądać tak:

<div class="class">
   <p>
     <h2>
       Header
    </h2>
    <div class="another-class">
       Some content
    </div>
  </p>
</div>

Będzie więc mega użyteczny do wszelkiej maści stylowania bazującego na walidacji – gdzie np jeśli mamy element w stanie :valid cała grupa elementów jak opis, label itd ma być zielona.

Jakie pseudoklasa :has ma wsparcie i jaką biblioteką go zasymulować?

W momencie pisania tekstu pseudoklasa nie ma żadnego wsparcia.

Na szczęście do momentu implementacji wspomóc nas może CSS Has Pseudo, plugin do PostCSS i biblioteka w jednym.

Umożliwia ona zamianę pseudoklasy :has na odpowiadający jej selektor atrybutu i wygenerowanie pliku zawierającego te zmiany. Następnie, już w kodzie strony, wywołanie metody cssHasPseudo spowoduje, że to elementów które w CSS mają selektor atrybutu zostanie ten atrybut dodany.

Użycie pseudoklasy :has do stylowania formularzy na bazie walidacji pól

Stworzenie skryptu transformującego CSS

Aby pseudoklasa została przetransformowana do atrybutu, musimy utworzyć odpowiedni skrypt. Skrypt ten zawierać będzie uruchomienie PostCSS wraz z pluginem has-pseudo i zapisanie zmian do pliku:

const postcss = require('postcss');
const postcssHasPseudo = require('css-has-pseudo/postcss');
const fs = require('fs')

fs.readFile('index-to-transform.css', (err, css) => {
  postcss([postcssHasPseudo()])
    .process(css, {
      from: 'index-to-transform.css', 
      to: 'index.css' })
    .then(result => {
      fs.writeFile(
        'index.css', 
        result.css, 
        () => true)
    });
});

Style odpowiadające za kolorystykę walidacji

Nasze style opierać się będą bardzo mocno o CSS Variables – jeśli jeszcze ich nie znasz możesz poczytać o nich tutaj:

Stwórzmy bazowe style, zawierający kolory pod walidację formularza:

html {
  --color-regular: black;
  --color-error: red;
  --color-success: green;
  --validation-color: var(--color-regular);
}

Bazowym elementem do stylowania będzie klasa .form-group, na której ustawiać będziemy kolor walidacji, z którego korzystać będzie kod kolorujący poszczególne elementy:

.form-group {
  width: 200px;
  padding: 8px;
  border: 1px solid var(--validation-color);
  color: var(--validation-color);
}

Podobnie same pola formularza korzystać będą ze zmiennej --validation-color:

input {
  border: 1px solid var(--validation-color);
  color: inherit;
}

I w tym miejscu zaczyna się magia CSS Variables 🙂 Ponieważ zamiast zmieniać kolorystykę wszystkich elementów w reakcji na wynik walidacji, potrzebujemy zmienić tylko wartość jednej zmiennej:

.form-group:has(:invalid) {
  --validation-color: var(--color-error);
}

.form-group:has(:valid) {
  --validation-color: var(--color-success);
}

.form-group.pristine {
  --validation-color: var(--color-regular);
}

Po co potrzebujemy dodatkowej klasy .pristine? Ponieważ CSS nie pozwala nam napisać selektora tak, by obejmował tylko elementy, w których użytkownik już coś wpisał. Domyślnie więc wszystkie pola są w statusie invalid, jeśli są wymagane.

Wymagany kod JS

Kod JS będzie bardzo prosty – najistotniejszym elementem jest zawołanie funkcji bibliotecznej, która stworzy atrybuty na bazie pseudoklas:

cssHasPseudo(document);

W kodzie strony łatwo zauważymy efekt jej wywołania:

Pseudoklasy zmienione na atrybut
Pseudoklasy zmienione na atrybut

Potrzebować jeszcze będziemy obsługi formularza – usunięcia klasy .pristine gdy użytkownik wpisuje coś w pole oraz przywrócenia jej, gdy formularz jest czyszczony po submicie:

form
  .addEventListener('change', e => {
    e.target
      .closest('.form-group')
      .classList
      .remove('pristine');
    });

form
  .addEventListener('submit', e => {
    e.preventDefault();
    form.reset();
      [...document.querySelectorAll('.form-group')]
        .forEach(el => {
          el.classList.add('pristine');
        });
    });

Efekt działania aplikacji

Efektem tak prostego kodu i sprytnego wykorzystania pseudoklasy i CSS Variables jest łatwe w implementacji prezentowanie użytkownikowi stanu walidacji formularza:

:has - efekt użycia do kolorowania walidacji formularza
:has – efekt użycia do kolorowania walidacji formularza

Klikalny przykład na Github Pages

Klikalny przykład znajduje się pod adresem:

https://radek-anuszewski.github.io/has-pseudoclass-demo/

Pełny kod przykładu na Github

Pełny kod aplikacji znajduje się pod adresem:

https://github.com/radek-anuszewski/has-pseudoclass-demo

Podsumowanie

Choć pseudoklasa :has nie jest jeszcze oficjalnie nigdzie wspierana łatwo zobaczyć, że znajdzie się dla niej wiele przypadków użycia. Połączona z nowoczesnymi funkcjonalnościami CSS jak CSS Variables uprości wiele sytuacji, które bez tego wymagałyby osobnych bibliotek czy też dodatkowego kodu JS.

Dlatego warto trzymać rękę na pulsie w sprawie tego, jaka jest przyszłość tej pseudoklasy.