Why Choose Designer Multiselect Over Native Select?

Written by shepelev | Published 2021/10/12
Tech Story Tags: html | css | javascript | web-development | designer-multiselect | multiselect-for-fast-work | native-select | layout-designers

TLDRThe multiselect form is a form that can display multiple items of a single selection. Not all <select> tag elements can be styled out of the box as designers would like to. In this small tutorial, I share my experience in solving this problem based on my knowledge of CSS and vanilla JS. I used CSS for SVG icons, but this wasn’t quite effective and it’s better to keep them as header files. The main thing about the <select tag is that in smartphones, the engines show the multiselection when you focus on the tag.via the TL;DR App

Those layout designers who create signup or feedback forms will surely face the problem of displaying multiple items of a single selection, i.e. multiselect. Unfortunately, not all <select> tag elements can be styled out of the box as designers would like to. In this small tutorial, I would like to share my experience in solving this problem based on my knowledge of CSS and vanilla JS.

What’s Wrong With Native Select?

If a designer creates a user-friendly <select> with 10-15 possible choices, you will most likely decide to take the first available design-like library from Google (which I was hoping for). My pain in this matter was that the project could not use third-party libraries, for example, jQuery, which greatly reduced the number of proposed options.

Small to Great

So, let’s get down to writing a simple multiselect form:
<form name="" action="">
  <div class="form">
    <span class="form__label">Categories</span>
    <div class="form__multiselect">
      <label for="select" class="form__multiselect__label">Select category</label>
      <select id="select" class="form__multiselect__select" name="category" multiple>
        <option value="HTML">HTML</option>
        <option value="CSS">CSS</option>
        <option value="JavaScript">JavaScript</option>
      </select>
    </div>
    <span class="form__error"></span>
  </div>
</form>
There is a section title, a choice of options, <select> with the multiple attributes that make it possible to select several elements at the same time. And of course, there is an error_text field where we will insert our error text if this multiselect form is required.
It seems to be working, but it doesn’t look very good. And the multiselection is not evident… So, let’s improve.
<form name="" action="">
  <div class="form">
    <span class="form__label">Categories</span>
    <div class="form__multiselect">
      <label for="select" class="form__multiselect__label">Select category</label>
      <input id="checkbox" class="form__multiselect__checkbox" type="checkbox">
      <label for="checkbox" class="form__multiselect__checkbox__label"></label>
      <select id="select" class="form__multiselect__select" name="category" multiple>
        <option value="HTML">HTML</option>
        <option value="CSS">CSS</option>
        <option value="JavaScript">JavaScript</option>
      </select>
      <span class="form__multiselect__help">You can select several items by pressing <b>Ctrl (or Command) + Element</b></span>
    </div>
    <span class="form__error"></span>
  </div>
</form>
Let’s add a few styles to make it less scary.
.form {
    position: relative;
    min-height: 88px;
}
.form__label {
    vertical-align: top;
    display: block;
    margin-bottom: 6px;
    font-weight: 500;
    font-size: 12px;
    line-height: 16px;
    letter-spacing: .04em;
    color: #686ea1;
}
.form__label:after {
    content: "*";
    position: relative;
    top: 0;
    font-size: 13px;
    color: red;
}
.form__multiselect__label, 
.form input {
    position: relative;
    width: 100%;
    display: block;
    min-height: 46px;
    border: 1px solid #cdd6f3;
    box-sizing: border-box;
    border-radius: 8px;
    padding: 12px 40px 10px 16px;
    font-size: 14px;
    color: #a8acc9;
    outline-color: #cdd6f3;
}
.form__multiselect__label::placeholder, 
.form input::placeholder {
    color: #a8acc9;
}
.form__multiselect__label:hover, 
.form input:hover {
    box-shadow: 0 0 2px rgba(0, 0, 0, .16);
}
.form__multiselect__label:focus, 
.form input:focus {
    box-shadow: 0 0 2px rgba(0, 0, 0, .16);
}
.form__multiselect__help {
    position: absolute;
    max-width: 100%;
    background-color: #fff;
    top: -48px;
    left: 0;
    opacity: 0;
    display: none;
  
}
.form input.error {
    border-color: #eb5757;
}
.form__error {
    color: #eb5757;
}
.form__multiselect__label {
    padding-right: 60px;
}
.form__multiselect__label:after {
    content: "";
    position: absolute;
    right: 14px;
    top: 15px;
    width: 6px;
    height: 16px;
    background: url("data:image/svg+xml, %3Csvg width='6' height='16' viewBox='0 0 6 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3 0L6 5H0L3 0Z' fill='%23A8ACC9'/%3E%3Cpath d='M3 16L6 11H0L3 16Z' fill='%23A8ACC9'/%3E%3C/svg%3E") 50% 50% no-repeat;
}
.form__multiselect {
    position: relative;
    width: 100%}
.form__multiselect__select {
    position: absolute;
    top: calc(100% - 2px);
    left: 0;
    width: 100%;
    border: 2px solid #cdd6f3;
    border-bottom-right-radius: 2px;
    border-bottom-left-radius: 2px;
    box-sizing: border-box;
    outline-color: #cdd6f3;
    z-index: 6;
}
.form__multiselect__select[multiple] {
    overflow-y: auto;
}
.form__multiselect__select option {
    display: block;
    padding: 8px 16px;
    width: calc(370px - 32px);
    cursor: pointer;
}
.form__multiselect__select option:checked {
    background-color: #eceff3;
}
.form__multiselect__select option:hover {
    background-color: #d5e8fb;
}
.form__multiselect__label button {
    position: relative;
    padding: 7px 34px 7px 8px;
    background: #ebe4fb;
    border-radius: 4px;
    margin-right: 9px;
    margin-bottom: 10px;
}
.form__multiselect__label button:focus, 
.form__multiselect__label button:hover {
    background-color: #dbd1ee;
}
.form__multiselect__label button:after {
    content: "";
    position: absolute;
    top: 7px;
    right: 10px;
    width: 16px;
    height: 16px;
    background: url("data:image/svg+xml, %3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19.4958 6.49499C19.7691 6.22162 19.7691 5.7784 19.4958 5.50504C19.2224 5.23167 18.7792 5.23167 18.5058 5.50504L12.5008 11.5101L6.49576 5.50504C6.22239 5.23167 5.77917 5.23167 5.50581 5.50504C5.23244 5.7784 5.23244 6.22162 5.50581 6.49499L11.5108 12.5L5.50581 18.505C5.23244 18.7784 5.23244 19.2216 5.50581 19.495C5.77917 19.7684 6.22239 19.7684 6.49576 19.495L12.5008 13.49L18.5058 19.495C18.7792 19.7684 19.2224 19.7684 19.4958 19.495C19.7691 19.2216 19.7691 18.7784 19.4958 18.505L13.4907 12.5L19.4958 6.49499Z' fill='%234F5588'/%3E%3C/svg%3E") 50% 50% no-repeat;
    background-size: contain;
}
.form__multiselect__checkbox__label {
    position: absolute;
    top: 1px;
    left: 2px;
    width: 100%;
    height: 44px;
    cursor: pointer;
    z-index: 3;
}
.form__multiselect__select {
    display: none;
}
input.form__multiselect__checkbox {
    position: absolute;
    right: 0;
    top: 0;
    width: 40px;
    height: 40px;
    border: none;
    opacity: 0;
}
.form__multiselect__checkbox:checked~.form__multiselect__select {
    display: block;
}
.form__multiselect__checkbox:checked~.form__multiselect__checkbox__label {
    width: 40px;
    left: initial;
    right: 4px;
    background: #fff url("data:image/svg+xml, %3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19.4958 6.49499C19.7691 6.22162 19.7691 5.7784 19.4958 5.50504C19.2224 5.23167 18.7792 5.23167 18.5058 5.50504L12.5008 11.5101L6.49576 5.50504C6.22239 5.23167 5.77917 5.23167 5.50581 5.50504C5.23244 5.7784 5.23244 6.22162 5.50581 6.49499L11.5108 12.5L5.50581 18.505C5.23244 18.7784 5.23244 19.2216 5.50581 19.495C5.77917 19.7684 6.22239 19.7684 6.49576 19.495L12.5008 13.49L18.5058 19.495C18.7792 19.7684 19.2224 19.7684 19.4958 19.495C19.7691 19.2216 19.7691 18.7784 19.4958 18.505L13.4907 12.5L19.4958 6.49499Z' fill='%234F5588'/%3E%3C/svg%3E") 50% 50% no-repeat;
}
.form__multiselect__checkbox:checked~.form__multiselect__help {
    opacity: 1;
}
Now it is at least pleasant to click on it. I used CSS for SVG icons, but this wasn’t quite effective and it’s better to keep them as header files. I added <label class="form__multiselect__checkbox__label"> and <input type="checkbox" class="form__multiselect__checkbox"> to interact with <select>. This is some kind of magic that implies overlapping layers with z-index and using the:checked CSS pseudo-class to interact with elements. These new elements make it possible to style the tooltips and make the choice easier for users, which will facilitate better interaction with the element. Let’s extend the functionality even more by adding buttons for displaying selected items.
let multiselectBlock = document.querySelectorAll(".form__multiselect");
multiselectBlock.forEach((parent) => {
    let label = parent.querySelector(".form__multiselect__label");
    let text = label.innerHTML;
    let select = parent.querySelector(".form__multiselect__select");
    select.addEventListener("change", function(e) {
        let selectedOptions = this.selectedOptions;
        label.innerHTML = "";
        for (let option of selectedOptions) {
            let button = document.createElement("button");
            button.type = "button";
            button.className = "btn_multiselect";
            button.textContent = option.value;
            button.onclick = () => {
                option.selected = false;
                button.remove();
                if (!select.selectedOptions.length) label.innerHTML = text;
            };
            label.append(button);
        }
    });
});
Now, when selecting <option> inside the <select> element, we will create a button that will show what elements are selected by the user with an option to delete them.
Also, since I’m sure I am not the only one who did not know that <select> uses the pressed Ctrl or Shift, let’s add this hint separately. We could have put it into a tooltip (and use the data attribute to display it), but I thought about the availability and the possibility of translating this hint into other languages.

Ready Multiselect for Fast Work

Here’s what I did. To get the entire picture, I added several elements at once so that it would be clear that you could scale them by simply adding the necessary id and associated <label>. Also, I separated the necessary elements with comments so that you could apply this code in your projects.
It’s not perfect in terms of accessibility, but it is much better than the native select or jQuery library. The main thing about the <select> tag is that in smartphones, the engines show the multiselection when you focus on the tag and you don’t need to think about how to insert 15-20 items without breaking the layout.
I hope this may help someone.

Written by shepelev | Senior Ruby on Rails Developer
Published by HackerNoon on 2021/10/12