Advertisement
  1. Web Design
  2. JavaScript

Membuat Bentuk Interface Multi-Step

Scroll to top
Read Time: 14 min

Indonesian (Bahasa Indonesia) translation by Uady (you can also view the original English article)

Membentuk kegunaan merupakan topik yang sangat penting dalam desain web. Sebagai salah satu masukan utama interface yang disediakan untuk pengguna, kegunaan formulir sangat penting untuk pengalaman pengguna yang baik.

Hari ini, kita akan membuat formulir multi-part, lengkap dengan validasi dan animasi. Kita akan membahasnya dari awal, jadi bersiaplah!


Bentuk Praktik Terbaik Tingkat Tinggi

Bentuk desain interface adalah jebakan dari kendala kegunaan. Sebelum memulai, mari kita bahas beberapa praktik terbaik formulir.

Jangan membuat pengguna Anda berpikir terlalu keras

Formulir umumnya bukan tempat untuk mendorong interaksi unik. Bentuk yang bagus memberikan teknik navigasi yang jelas dan transparansi penuh. Bentuk yang baik diberi label yang baik dan mudah dinavigasi.

Jangan terlalu berlebihan

Penting untuk tidak menyimpang terlalu jauh dari perilaku bentuk bawaan. Ada standar yang menjadi dasar untuk perilaku standar bentuk. Penyimpangan dari standar ini mungkin memiliki efek negatif pada aksesibilitas, jadi pertimbangkan untuk mempertahankan gaya default tersebut jika memungkinkan.

Ingat, pengguna tidak harus tinggal

Pengguna formulir Anda tidak harus tinggal. Mereka memilih untuk tetap tinggal. Mengisi formulir adalah tugas, jadi buatlah mudah. (Jika memungkinkan, buat menjadi menyenangkan!) Jangan membingungkan atau menuntut pengguna; sebagai gantinya, buat dialog di sekitar pertanyaan yang diminta oleh formulir. Bersikap sopan.

Kapan menggunakan teknik multi-bagian

Bentuk multi-part tentu saja merupakan teknik yang bagus, terkadang. Tidak ada jawaban tunggal untuk "berapa banyak input yang dibutuhkan sebelum saya harus membagi formulir". Sebaliknya, selalu pertimbangkan apakah membagi formulir menjadi beberapa bagian akan membantu atau menghambat kegunaan. Kedua, pertimbangkan apakah itu membantu atau menghalangi aspek lain dari desain interaksi.

Jika Anda memiliki formulir dengan bagian yang sangat berbeda, mungkin perlu dipisahkan menjadi beberapa bagian. Proses Checkout adalah contoh umum dari ini. (Info pribadi, info pengiriman, info pembayaran, dan konfirmasi sangat berbeda dan bagian umumnya substansial.)


Merencanakan Interaksi

Kita akan membuat formulir pendaftaran dengan bidang acak. Kita perlu tahu bagian apa yang sedang digunakan, jadi kita memerlukan indikator di bagian atas formulir. Kita ingin mentransisikan bagian formulirnya secara horizontal, meluncur dari kanan ke kiri. Untuk mencapai hal ini, kita akan mengatur bagian-bagian yang berbeda untuk memiliki posisi absolut di dalam bagian "jendela" elemen. Kita juga ingin memiliki dua tombol; satu adalah tombol submit normal. Yang lainnya adalah tombol "bagian selanjutnya".


Markup

Inilah formulirnya:

1
<form id="signup" action="somewhere" method="POST">
2
    <ul id="section-tabs">
3
      <li class="current active"><span>1.</span> Creds</li>
4
      <li><span>2.</span> Deets</li>
5
      <li><span>3.</span> Settings</li>
6
      <li><span>4.</span> Last Words</li>
7
    </ul>
8
  <div id="fieldsets">
9
  <fieldset class="current">
10
    <label for="email">Email:</label>
11
    <input name="email" type="email" class="required email" />
12
    <label name="password" for="password">Password:</label>
13
    <input type="password" minlength="10" class="required">
14
  </fieldset>
15
  <fieldset class="next">
16
    <label for="username">Username:</label>
17
    <input name="username" type="text">
18
    <label for="bio">Short Bio:</label>
19
    <textarea name="bio" class="required"></textarea>
20
  </fieldset>
21
  <fieldset class="next">
22
    <label for="interests">Basic Interests:</label>
23
    <textarea name="bio"></textarea>
24
    <p>Receive newsletter?<br>
25
      <input type="radio" name="newsletter" value="yes"><label for="newsletter">yes</label>
26
      <input type="radio" name="newsletter" value="no"><label for="newsletter">no</label>
27
    </p>
28
  </fieldset>
29
  <fieldset class="next">
30
    <label for="referrer">Referred by:</label>
31
    <input type="text" name="referrer">
32
    <label for="phone">Daytime Phone:</label>
33
    <input type="tel" name="phone">
34
  </fieldset>
35
  <a class="btn" id="next">Next Section ▷</a>
36
  <input type="submit" class="btn">
37
  </div>
38
</form>

Markup ini cukup mudah, tetapi mari kita bicara tentang beberapa bagiannya.

  • Fieldsets: Fieldsets adalah elemen semantik untuk pengelompokan input; ini sesuai dengan situasi kita dengan sempurna.
  • Classnames: jQuery Validate menggunakan kelas untuk menentukan tipe bawaan. Kita akan melihat bagaimana ini bekerja dalam satu menit.
  • Bidangnya acak. Kita telah menutup input radio dalam tag paragraf untuk pemformatan yang lebih mudah.
  • Kirim dan tombol berikutnya: tag anchor dengan kelas tombol akan digunakan untuk pergi ke bagian selanjutnya. Masukan yang dikirimkan akan ditampilkan saat diperlukan melalui JavaScript.

The Styles (LESS-Flavoured)

Ini panjang, bersiaplah..

1
@import url(http://fonts.googleapis.com/css?family=Merriweather+Sans:300);
2
@import url(http://fonts.googleapis.com/css?family=Merriweather+Sans:700);
3
4
5
body {
6
  background: url(http://farm5.staticflickr.com/4139/4825532997\_7a7cd3d640\_b.jpg);
7
  background-size: cover;
8
  height: 100%;
9
  font-family: 'Merriweather Sans', sans-serif;
10
  color: #666;
11
}
12
13
#signup {
14
  width: 600px;
15
  height: auto;
16
  padding: 20px;
17
  background: #fff;
18
  margin: 80px auto;
19
  position: relative;
20
  min-height: 300px;
21
}
22
#fieldsets {
23
  position: absolute;
24
  top: 0px;
25
  left: 0px;
26
  width: 100%;
27
  height: 100%;
28
  padding: 20px;
29
  box-sizing: border-box;
30
  overflow: hidden;
31
}
32
input[type=text],
33
input[type=email],
34
input[type=password],
35
input[type=tel],
36
textarea {
37
  display: block;
38
  -webkit-appearance: none;
39
  -moz-appearance: none;
40
  width: 100%;
41
  box-sizing: border-box;
42
  border: 1px solid #ddd;
43
  padding: 8px;
44
  margin-bottom: 8px;
45
  position: relative;
46
  &:focus {
47
    outline: none;
48
    border: 1px solid darken(#2cbab2,10%);
49
  }
50
}
51
52
input[type=radio]{
53
  margin: 6px;
54
  display: inline-block;
55
}
56
fieldset {
57
  border: none;
58
  position: absolute;
59
  left: -640px;
60
  width: 600px;
61
  padding: 10px 0;
62
  transition: all 0.3s linear;
63
  -webkit-transition: all 0.3s linear;
64
  -moz-transition: all 0.3s linear;
65
  -ms-transition: all 0.3s linear;
66
  opacity: 0;
67
  &.current {
68
    left: 20px;
69
    opacity: 1;
70
  }
71
  &.next {
72
    left: 640px;
73
  }
74
}
75
input[type=submit] {
76
  display: none;
77
  border: none;
78
}
79
#section-tabs {
80
  font-size: 0.8em;
81
  height: 50px;
82
  position: relative;
83
  margin-top: -50px;
84
  margin-bottom: 50px;
85
  padding: 0;
86
  font-weight: bold;
87
  list-style: none;
88
  text-transform: uppercase;
89
  li {
90
    color: #a7a7a7;
91
    span {
92
      color: #bababa;
93
    }
94
    cursor: not-allowed;
95
    &.active {
96
      color: #444;
97
      cursor: pointer;
98
    }
99
    border-left: 1px solid #aaa;
100
    text-decoration: none;
101
    padding: 0 6px;
102
    float: left;
103
    width: 25%;
104
    box-sizing: border-box;
105
    text-align: center;
106
    font-weight: bold;
107
    line-height: 30px;
108
    background: #ddd;
109
    position: relative;
110
    &:after {
111
      content: "";
112
      display: block;
113
      margin-left: 0;
114
      position: absolute;
115
      left: 0;
116
      top: 0;
117
    }
118
    &.current {
119
      opacity: 1;
120
      background: #fff;
121
      z-index: 999;
122
      border-left: none;
123
      &:after {
124
        border: 15px solid transparent;
125
        border-left: 15px solid #2cbab2;
126
      }
127
    }
128
  }
129
}
130
.error {
131
  color: #bf2424;
132
  display: block;
133
}
134
input.error, textarea.error {
135
  border-color: #bf2424;
136
  &:focus {
137
    border-color: #bf2424;
138
  }
139
}
140
label.error {
141
    margin-bottom: 20px;
142
}
143
input.valid {
144
  color: green;
145
}
146
label.valid {
147
  position: absolute;
148
  right: 20px;
149
}
150
input+.valid, textarea+.valid {
151
  display: none;
152
}
153
.valid+.valid {
154
  display: inline;
155
  position: absolute;
156
  right: 10px;
157
  margin-top: -36px;
158
  color: green;
159
}
160
161
.btn {
162
  border: none;
163
  padding: 8px;
164
  background: #2cbab2;
165
  cursor: pointer;
166
  transition: all 0.3s;
167
  -webkit-transition: all 0.3s;
168
  -moz-transition: all 0.3s;
169
  &:hover {
170
     background: darken(#2cbab2, 6%);
171
  }
172
  color: #fff;
173
  position: absolute;
174
  bottom: 20px;
175
  right: 20px;
176
  font-family: 'Merriweather Sans', sans-serif;
177
}

Mari kita melewati bagian penting dari style.

Overview

Bentuknya sendiri diatur ke lebar tertentu, berpusat dengan margin: 0 auto, kemudian set ke position:relative. Pemosisian ini memungkinkan penentuan absolute positioning dari elemen anak untuk menempatkannya secara absolut, relatif terhadap formulir yang dikandung. Formulir yang mengandung memegang tiga jenis elemen utama: tab bagian dan "window" fieldset, serta tombol.

Tab bagian ditempatkan relatif terhadap elemen yang mengandung, dan "ditarik" ke atas dengan margin negatif atas. Kita mengimbangi efek pada sisa tata letak dengan margin bawah yang sama.

Fieldset "window" diatur untuk diposisikan absolut, relatif terhadap elemen form induk. Lebar dan tinggi keduanya diatur hingga 100%. Tujuan dari jendela ini adalah untuk menahan elemen, lalu menyembunyikannya ketika berada di luar tepi dengan overflow: hidden. Kita tidak bisa melakukan ini pada formulir, karena kita ingin mempertahankan tab indikator.

Akhirnya, elemen tombol (tag anchor dan masukan pengiriman) ditata untuk diposisikan secara mutlak ke sudut kanan bawah formulir, diimbangi dengan 20 piksel dari bawah dan kanan. Kita juga telah menambahkan transisi CSS sederhana ke elemen tombol untuk menggelapkan background di hover.

Beberapa Catatan Lagi

  • Fieldsets diatur menjadi position: absolute. Kita memiliki dua kelas untuk dua negara bagian, dan negara default. Keadaan default menarik fieldset ke kiri form; kelas .current menempatkan fieldset di area yang terlihat dari bentuk, dan akhirnya kelas .next mendorong fieldset ke kanan dari form.
  • Kelas saat ini memiliki opacity 1; keadaan default (dan inheren .next) memiliki opacity 0.
  • Kita menganimasikan fieldets antara kelas-kelas ini dengan transisi css sederhana.
  • Kelas .error dan .valid akan membantu dengan styling validasi. Kita menggunakan Merriweather Sans, font yang tersedia secara gratis untuk digunakan melalui Google Webfonts.

JavaScript

Inilah bagian yang membuat semua interaksi bekerja. Beberapa catatan sebelum kita melihat kode: kode ini tergantung pada jQuery dan jQuery Validate. jQuery memvalidasi adalah plugin yang telah ada untuk waktu yang sangat lama, dan oleh karena itu telah diuji dan dibuktikan oleh ribuan orang.

Strategi dasar kita: menyiapkan beberapa aturan validasi untuk formulir, termasuk fungsi khusus untuk memeriksa nomor telepon. Kita ingin pengguna dapat menavigasi kembali melalui bagian yang telah selesai sebelumnya. Kita ingin mereka dapat menggunakan tombol enter untuk pindah ke bagian berikutnya; namun, kita tidak ingin mengizinkan pengguna untuk maju ke bagian selanjutnya hingga bagian sebelumnya telah selesai dan valid.

Jika pengguna mencoba mengklik tab untuk bagian di luar yang telah mereka selesaikan, kita ingin menghindari navigasi ke bagian itu.

Kita ingin mengandalkan kelas (bukan animasi jQuery) untuk transisi antar bagian.

Jadi, dengan memperhatikan hal ini, berikut adalah JavaScript akhir. (Catatan: jika Anda tidak menggunakan jQuery Anda, ini mungkin sedikit menakutkan; namun tetap menggunakannya, dan Anda akan belajar dengan menyelami kode.) Setelah naskah lengkap, kita akan mengambilnya satu bagian di ada waktu dan jelaskan apa yang terjadi.

1
$("#signup").validate({
2
  success : function(label){
3
    label.addClass("valid").text("");
4
  },
5
  error : function(e){
6
  // do nothing, but register this function

7
  },
8
  onsubmit:false,
9
  rules: {
10
    phone: {
11
      required: true,
12
      phoneUS: true
13
    }
14
  }
15
});
16
17
$("body").on("keyup", "form", function(e){
18
  if (e.which == 13){
19
    if ($("#next").is(":visible") && $("fieldset.current").find("input, textarea").valid() ){
20
      e.preventDefault();
21
      nextSection();
22
      return false;
23
    }
24
  }
25
});
26
27
28
$("#next").on("click", function(e){
29
  console.log(e.target);
30
  nextSection();
31
});
32
33
$("form").on("submit", function(e){
34
  if ($("#next").is(":visible") || $("fieldset.current").index() < 3){
35
    e.preventDefault();
36
  }
37
});
38
39
function goToSection(i){
40
  $("fieldset:gt("+i+")").removeClass("current").addClass("next");
41
  $("fieldset:lt("+i+")").removeClass("current");
42
  $("li").eq(i).addClass("current").siblings().removeClass("current");
43
  setTimeout(function(){
44
    $("fieldset").eq(i).removeClass("next").addClass("current active");
45
      if ($("fieldset.current").index() == 3){
46
        $("#next").hide();
47
        $("input[type=submit]").show();
48
      } else {
49
        $("#next").show();
50
        $("input[type=submit]").hide();
51
      }
52
  }, 80);
53
54
}
55
56
function nextSection(){
57
  var i = $("fieldset.current").index();
58
  if (i < 3){
59
    $("li").eq(i+1).addClass("active");
60
    goToSection(i+1);
61
  }
62
}
63
64
$("li").on("click", function(e){
65
  var i = $(this).index();
66
  if ($(this).hasClass("active")){
67
    goToSection(i);
68
  } else {
69
    alert("Please complete previous sections first.");
70
  }
71
});
72
73
74
jQuery.validator.addMethod("phoneUS", function(phone_number, element) {
75
    phone_number = phone_number.replace(/\s+/g, ""); 
76
  return this.optional(element) || phone_number.length > 9 &&
77
    phone_number.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
78
}, "Please specify a valid phone number");

Jadi, mari kita mulai satu-satu.

1
$("#signup").validate({
2
  success : function(label){
3
    label.addClass("valid").text("");
4
  },
5
  error : function(e){
6
  // do nothing, but register this function

7
  },
8
  onsubmit:false,
9
  rules: {
10
    phone: {
11
      required: true,
12
      phoneUS: true
13
    }
14
  }
15
});

Fungsi ini adalah fungsi pengaturan untuk jQuery Validate. Pertama, kita memberi tahu plugin untuk mengambil formulir pendaftaran dan menerapkan validasi untuk itu. Jika validasi berhasil, kita menambahkan kelas valid ke label yang dimasukkan plugin validasi setelah elemen input, dan mengganti teks dengan tanda centang  utf-8.

Kita juga mendaftarkan error callback, meskipun sebenarnya tidak melakukan apa pun dalam fungsi ini. Tidak mendaftarkan fungsi tampaknya memiliki callback yang sama sebagai fungsi keberhasilan pada kesalahan. Kita sedang mengatur hook onsubmit ke false; ini karena menekan enter secara otomatis mengirimkan formulir, yang secara default memicu validasi untuk mencegah pengiriman formulir yang tidak valid. Mencegah perilaku default pengiriman formulir tidak mencegah validasi form. Hasilnya adalah bahwa bidang pada layar "berikutnya" sudah menunjukkan kesalahan validasi, meskipun formulir tidak pernah dikirimkan.

1
$("body").on("keyup", "form", function(e){
2
  if (e.which == 13){
3
    if ($("#next").is(":visible") && $("fieldset.current").find("input, textarea").valid() ){
4
      e.preventDefault();
5
      nextSection();
6
    }
7
  }
8
});

Fungsi ini mendengarkan acara kunci yang dipicu pada formulir. Jika kunci yang dipukul masuk (kode kunci 13), kita kemudian melakukan pemeriksaan berikut. Jika tombol berikutnya masih terlihat dan bagian saat ini valid, mencegah perilaku default dari tombol enter yang sedang ditekan saat berada di formulir, yang merupakan pengiriman formulir.

kita kemudian memakai nextSection(), yang memajukan formulir ke bagian berikutnya. Jika bagian saat ini berisi input yang tidak valid, input tersebut diidentifikasi dan formulir tidak maju. Jika tombol berikutnya tidak terlihat, itu berarti kita berada di bagian akhir (yang akan kita lihat di fungsi berikutnya) dan kita ingin membiarkan perilaku default (pengiriman formulir) terjadi.

1
$("#next").on("click", function(e){
2
  nextSection();
3
});
4
5
$("form").on("submit", function(e){
6
  if ($("#next").is(":visible") || $("fieldset.current").index() < 3){
7
    e.preventDefault();
8
  }
9
});

Fungsi-fungsi ini sangat mudah. Jika Anda menekan tombol dengan id "next", kita ingin maju ke bagian selanjutnya. Ingat fungsi nextSection() berisi semua pemeriksaan yang diperlukan untuk validasi form.

Pada acara kirim di formulir, kita ingin menghindari pengiriman formulir jika tombol berikutnya terlihat atau jika fieldset saat ini bukan yang terakhir.

1
function goToSection(i){
2
  $("fieldset:gt("+i+")").removeClass("current").addClass("next");
3
  $("fieldset:lt("+i+")").removeClass("current");
4
  $("li").eq(i).addClass("current").siblings().removeClass("current");
5
  setTimeout(function(){
6
    $("fieldset").eq(i).removeClass("next").addClass("current active");
7
      if ($("fieldset.current").index() == 3){
8
        $("#next").hide();
9
        $("input[type=submit]").show();
10
      } else {
11
        $("#next").show();
12
        $("input[type=submit]").hide();
13
      }
14
  }, 80);
15
16
}

Fungsi goToSection() adalah pekerja keras di belakang navigasi formulir ini. Dibutuhkan satu argumen - indeks navigasi target. Fungsi mengambil semua fieldsset dengan indeks lebih besar dari yang dilewati dalam parameter indeks dan menghapus kelas current, dan menambahkan kelas next. Ini mendorong elemen di sebelah kanan formulir.

Selanjutnya, kita menghapus kelas saat ini dari semua fieldsset dengan indeks kurang dari yang dilewatkan dalam indeks. Selanjutnya, kita memilih item daftar yang sama dengan indeks yang diteruskan dan menambahkan kelas saat ini.

Selanjutnya kita menghapus kelas saat ini dari semua saudara li lainnya. tenetapkan batas waktu, setelah itu hapus kelas berikutnya dan tambahkan kelas saat ini dan aktif ke fieldset dengan indeks yang sama dengan yang dilewatkan dalam parameter indeks.

Kelas aktif memungkinkan kita mengetahui bahwa pengguna dapat menavigasi ke bagian khusus ini lagi. Jika dilewatkan dalam parameter indeks adalah 3 (fieldset akhir), kita menyembunyikan tombol berikutnya dan menunjukkan tombol submit. Jika tidak, kita ingin memastikan tombol berikutnya terlihat dan tombol kirim disembunyikan. Ini memungkinkan kita untuk menyembunyikan tombol submit kecuali fieldset terakhir terlihat.

1
function nextSection(){
2
  var i = $("fieldset.current").index();
3
  if (i < 3){
4
    $("li").eq(i+1).addClass("active");
5
    goToSection(i+1);
6
  }
7
}
8
9
$("li").on("click", function(e){
10
  var i = $(this).index();
11
  if ($(this).hasClass("active")){
12
    goToSection(i);
13
  } else {
14
    alert("Please complete previous sections first.");
15
  }
16
});

The nextSection() fungsi pada dasarnya adalah pembungkus di sekitar fungsi goToSection(). Ketika nextSection dipanggil, pemeriksaan sederhana dilakukan untuk memastikan bahwa kita tidak berada di bagian terakhir. Asalkan kita tidak, kita pergi ke bagian dengan indeks yang sama dengan indeks dari bagian saat ini, ditambah satu.

Kita mendengarkan acara klik pada daftar item. Fungsi ini memeriksa untuk memastikan item daftar memiliki kelas aktif, yang diterima begitu pengguna awalnya tiba ke bagian formulir tersebut dengan menyelesaikan semua bagian sebelumnya. Jika item daftar memang memiliki kelas, kita memanggil goToSection dan lulus dalam indeks item daftar itu. Jika pengguna mengklik item daftar yang sesuai dengan bagian yang belum dapat mereka akses, browser akan memberi tahu mereka untuk memberi tahu bahwa mereka harus menyelesaikan bagian sebelumnya sebelum maju.

1
jQuery.validator.addMethod("phoneUS", function(phone_number, element) {
2
    phone_number = phone_number.replace(/\s+/g, ""); 
3
  return this.optional(element) || phone_number.length > 9 &&
4
    phone_number.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
5
}, "Please specify a valid phone number");

Akhirnya, kita menambahkan metode yang ditentukan oleh plugin jQuery Validate yang memeriksa input secara manual. Kita tidak akan menghabiskan banyak waktu untuk hal ini, karena fungsi yang tepat ini dapat ditemukan dalam Validasi documentation.

Pada dasarnya, kita memeriksa untuk memastikan teks yang dimasukkan pengguna ke dalam bidang telepon cocok dengan regex (string panjang angka dan simbol). Jika ya, maka inputnya valid. Jika tidak, pesan "Silakan tentukan nomor telepon yang valid" ditambahkan setelah input. Anda dapat menggunakan fungsi yang sama ini untuk memeriksa jenis masukan apa pun (tidak harus menggunakan regex).

Catatan: Jangan gunakan ini sebagai metode otentikasi kata sandi. Ini sangat tidak aman dan siapa saja dapat melihat sumber untuk melihat kata sandi.


Kesimpulan

Kita telah menggunakan semantik HTML dan LESS sederhana yang dikombinasikan dengan beberapa JavaScript minimal untuk membangun interaksi bentuk yang kuat. Metode yang digunakan untuk membangun formulir ini, terutama menggunakan nama-nama kelas untuk mengidentifikasi keadaan dan mendefinisikan fungsi dan animasi, dapat digunakan di hampir semua proyek interaktif. Fungsionalitas yang sama dapat digunakan untuk demonstrasi langkah demi langkah, permainan, dan hal lain yang bergantung pada interaksi sederhana berbasis negara.

Apa lagi yang akan Anda lakukan pada formulir ini? Jenis interaksi apa yang Anda temukan untuk membantu pengguna lebih alami mengalir melalui proses yang membosankan seperti mengisi formulir multi-langkah panjang? Bagikan di komentar!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.