search check home clock-o tag tags chevron-left chevron-right chevron-up chevron-down twitter facebook github rss comment comments terminal code

[CSS] Jak zbudować wielokrokowy formularz za pomocą CSS?

[CSS] Jak zbudować wielokrokowy formularz za pomocą CSS?

W dzisiejszym artykule mam zamiar Ci przedstawić sposób na zbudowanie ciekawego formularza, w którym wszystkie elementy interaktywne będą wykonane tylko za pomocą HTML i CSS. Formularz będzie wielokrokowy/wielostronicowy, to znaczy będą 3 strony formularza między którymi będziemy się przełączać bez użycia JS.

Wielokrokowe formularze można spotkać w wielu miejscach w Internecie, np. podczas rejestracji do serwisów internetowych czy też podczas rejestracji do serwisów zakupowych. Najczęściej się korzysta wtedy z kombinacji HTML + CSS + JS, aby przełączać się między kolejnymi krokami. Po przeczytaniu tego wpisu będziesz w stanie zbudować podobny formularz bez użycia JS.

css-multistep-form-sample

Tworzymy wielokrokowy formularz

Cała magia przełączania się między kolejnymi stronami polega na umiejętnym wykorzystaniu selektorów CSS i kodu HTML. Na początek zacznijmy od kodu HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<form class="form--flex" action="#">
    <input type="radio" id="page-1" name="page" value="1" class="form--flex__page-switch" checked>
    <label for="page-1" class="form--flex__page-switch-label">1</label>
    <input type="radio" id="page-2" name="page" value="2" class="form--flex__page-switch">
    <label for="page-2" class="form--flex__page-switch-label">2</label>
    <input type="radio" id="page-3" name="page" value="3" class="form--flex__page-switch">
    <label for="page-3" class="form--flex__page-switch-label">3</label>
 
    <div class="form--flex__page" data-page="1">
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-1-1">Nazwa użytkownika</label>
            <input class="form--flex__page__group__input" type="text" id="input-1-1" required>
        </div>
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-1-2">Adres e-mail</label>
            <input class="form--flex__page__group__input" type="text" id="input-1-2" required>
        </div>
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-1-3">Hasło</label>
            <input class="form--flex__page__group__input" type="password" id="input-1-3" required>
        </div>
    </div>
    <div class="form--flex__page" data-page="2">
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-2-1">Ulica</label>
            <input class="form--flex__page__group__input" type="text" id="input-2-1" required>
        </div>
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-2-2">Nr domu</label>
            <input class="form--flex__page__group__input" type="text" id="input-2-2" required>
        </div>
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-2-3">Nr mieszkania</label>
            <input class="form--flex__page__group__input" type="text" id="input-2-3">
        </div>
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-2-4">Kod pocztowy</label>
            <input class="form--flex__page__group__input" type="text" id="input-2-4" required>
        </div>
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-2-5">Miasto</label>
            <input class="form--flex__page__group__input" type="text" id="input-2-5" required>
        </div>
    </div>
    <div class="form--flex__page" data-page="3">
        <div class="form--flex__page__group">
            <label class="form--flex__page__group__label" for="input-3-1">Data urodzenia</label>
            <input class="form--flex__page__group__input" type="date" id="input-3-1">
        </div>
        <div class="form--flex__page__group">
            <button type="submit" class="form--flex__page__group__submit">Prześlij dalej</button>
        </div>
    </div>
</form>

Powyższy kod, zawiera 3 strony formularza. Na każdej stronie znajduje się określony zestaw pól. Ponad kodem HTML stron znajdują się pola typu radio, które będą nam służyły do przełączania się między stronami. Interkacja zostanie określona w następującym kodzie CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
.form--flex {
    display: flex;
    width: 100%;
    padding: 1rem;
    flex-direction: row;
    flex-wrap: wrap;
    position: relative;
    justify-content: center;
    height: 14.15rem;
    overflow-x: hidden;
}
 
.form--flex__page-switch {
    margin-right: .5rem;
    margin-top: .29375rem;
    opacity: 0;
}
 
.form--flex__page-switch-label {
    flex: 0 0 2.5rem;
    line-height: 2.5rem;
    height: 2.5rem;
    cursor: pointer;
    border-radius: 50%;
    box-shadow: 0 0 .25rem rgba(0,0,0,0);
    background: #ff530d;
    text-align: center;
    color: #fff;
    transition: all .3s cubic-bezier(.25,.8,.25,1);
}
 
.form--flex__page-switch-label:hover,
.form--flex__page-switch-label:focus {
    background: #e64200;
    box-shadow: 0 0 .25rem rgba(0,0,0,.25);
}
 
.form--flex__page-switch:checked + .form--flex__page-switch-label {
    background: #ff530d;
    box-shadow: 0 0 .25rem rgba(0,0,0,.5);
}
 
.form--flex__page {
    z-index: -1;
    opacity: 0;
    flex: 0 1 100%;
    padding: 1rem;
    background: #eee;
    transform: translateX(100%);
    transition: all .3s cubic-bezier(.25,.8,.25,1);
    position: absolute;
    top: 5rem;
    left: 0;
    width: 100%;
    height: 9rem;
}
 
.form--flex__page-switch[value="1"]:checked ~ .form--flex__page[data-page="1"],
.form--flex__page-switch[value="2"]:checked ~ .form--flex__page[data-page="2"],
.form--flex__page-switch[value="3"]:checked ~ .form--flex__page[data-page="3"] {
    z-index: 1;
    opacity: 1;
    transform: translateX(0);
}
 
.form--flex__page__group {
    display: flex;
}
 
.form--flex__page__group__label {
    white-space: nowrap;
    flex: 0 1 11.25rem;
    text-align: right;
    padding: 0 1rem;
    background: #ddd;
    border-right: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
}
 
.form--flex__page__group__label:after {
    content: ':';
    display: inline-block;
}
 
.form--flex__page__group:first-of-type .form--flex__page__group__label {
    border-radius: .5rem 0 0 0;
}
 
.form--flex__page__group:last-of-type .form--flex__page__group__label {
    border-radius: 0 0 0 .5rem;
}
 
.form--flex__page__group__input {
    display: inline-block;
    flex: 1 0 0;
    border: 0;
    padding: 0 1rem;
    border-bottom: 1px solid #ccc;
}
 
.form--flex__page__group:last-of-type .form--flex__page__group__label,
.form--flex__page__group:last-of-type .form--flex__page__group__input {
    border-bottom: 0;
}

Do określania nazw klas elementów zastosowałem metodologię BEM.

Powyższy kod CSS jest ciekawy z kilku powodów:

  1. Kod wykorzystuje flexbox,
  2. Kod jest odpowiedzialny za obsługę kliknięć, patrz style: .form--flex__page-switch[value="1"]:checked ~ .form--flex__page[data-page="1"],
  3. Kod zawiera jest odpowiedzialny za obsługę animacji przejścia między stronami

1. Wykorzystanie flexbox

Dzięki wykorzystaniu flexbox, jestem w stanie (w jakimś stopniu) określić jak elementy takie jak etykieta pola i pole mają się zachowywać w trakcie zmiany szerokości ekranu. O takim fakcie, jak łatwiejsze układanie elementów wewnątrz już nie wspomnę.

Etykieta pola zawsze ma określoną minimalną szerokość na poziomie 180px i co zostało zapisane w następujący sposób: flex: 0 1 11.25rem;. Dzięki temu zapisowi określiliśmy, że element <label/> zawsze będzie miał minimum 180px szerokości (11,25rem = 11,25 * 16px = 180px) oraz będzie zwiększał swoją szerokość stopniowo w tym samym stopniu co inne elementy znajdujące się w tym samym kontenerze (w tym przypadku, innym elementem jest element <input/>).

2. Wykorzystanie CSS do obsługi kliknięć

Bardzo często twórcy stron myślą, że obsługę kliknięć można zrobić tylko i wyłącznie za pomocą JavaScript, lecz nie mają racji. W naszym przypadku, obsługę kliknięć wykonaliśmy za pomocą CSS, a dokładniej za pomocą określonego selektora: .form--flex__page-switch[value="1"]:checked ~ .form--flex__page[data-page="1"]. Selektor ten składa się z kilku elementów:

  1. Selektor elementu <input/> typu radio posiadający klasę: .form--flex__page-switch i data-atrybut z określoną wartością (dla ścisłości, to nie musi być data-atrybut to może być również klasa CSS) oraz ze stanem zaznaczenia - .form--flex__page-switch[value="1"]:checked,
  2. Selektor każdego następnego elementu występującego w dowolnym miejscu za elementem z selektora powyżej - ~,
  3. Selektor elementu będącego stroną formularza przypisaną do wybranego elementu <input/> typu radio - .form--flex__page[data-page="1"]

3. Animacje przejścia między stanami za pomocą CSS

Animacje wykonałem za pomocą reguły CSS transition, która przyjęła następującą postać: transition: all .3s cubic-bezier(.25,.8,.25,1);. Ona obserwuje zmiany wszystkich własności CSS (trzeba uważać przy korzystaniu tej własności, bo łatwo jest sprawić że płynność działania strony będzie znacznie obniżona) oraz przechodzi płynnie między nimi w czasie 300ms.

Podsumowanie

Mam nadzieję, że zaprezentowany przeze mnie pomysł uznasz za wystarczająco ciekawy aby spróbować go wykorzystać w swoich projektach. Zdaję sobie sprawę z tego, że nie rozwiązuje on wszystkich problemów z formularzami, ale ma wystarczająco duży potencjał aby przy odrobinie pracy w 100% spełnić wymagania dla wybranej strony czy aplikacji.

Rozwiązanie testowano na najnowszych wersjach przeglądarek i we wszystkich działa bez zakłóceń. Demo można zobaczyć tutaj: formularz wielokrokowy - demo

  • Wszystko fajnie, ale… 😉 A co jak chce kumplowi podesłać linka do konkretnego kroku formularza albo po walidacji server-side pokazać userowi błąd w odpowiednim kroku? IMO tutaj bardziej usable byłoby wykorzystanie :target do ogarnięcia kroków. Osobiście zastosowanie :checked widzę w zasadzie tylko wewnątrz jakichś formularzy (pokazanie/ukrycie opcjonalnego pola) lub interaktywnych widgetów, nie do sterowania dużymi obszarami strony. Tutaj jednak możliwość podpięcia kotwicy jest nie do przecenienia

    PS. link do demo się ciut skrzaczył.

  • Ekstra!