chevron-left chevron-right

Piotr Nalepa Frontend, architecture and AI

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

This version
blog.piotrnalepa.pl/2015/09/27/css-jak-zbudowac-wielokrokowy-formularz-za-pomoca-css
Published
Author
, Staff Software Engineer
Category
CSS
Feedback
Write to me · Post on X

This post is 11 years old. Browsers and tools may have changed since then, so check the code against current documentation before you use it.

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

Section 1Tworzymy 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:

Example 1HTML
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:

Example 2CSS
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

Section 1.11. 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/>).

Section 1.22. 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"]

Section 1.33. 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.

Section 2Podsumowanie

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

Share
Feedback

About the author

Staff Software Engineer

I've been writing this blog since 2009 and building for the web professionally for 14 years. Today I lead the frontend of the Cosmos platform at Bishop Fox, where I've been since day one of the project. Before that I worked at OLX, KUDO, Auto1 and eZ Systems. Here I write about what I've learned in practice: JavaScript, TypeScript, React, CSS and frontend architecture. I've also spoken at conferences across Poland and Europe. Got a question or an idea for a post? Let me know!