How to Create a Custom Select Dropdown using HTML, CSS and JavaScript

custom select dropdown Web development

How to Create a Custom Select Dropdown using HTML, CSS and JavaScript

Creating custom select dropdown component is one of the most common requirements in web development. Select dropdown allows the user to choose one value from a predefined list. Maybe you are wondering:

“Why do I need to create a custom select dropdown component, if there is already HTML select tag? I can apply some css styling and that’s it!”

Unfortunately, styling select dropdown is not so straightforward. Native HTML select element doesn’t allow us styling option tags.

Nowadays, select dropdowns appear in many different variations. You can find various javascript libraries and css frameworks, which have own implementation for custom select dropdowns. They have proven cross-browser functionality and are highly reliable. Many times, you don’t need to use any libraries or frameworks, because in many cases, creating a custom select dropdown is an easy task.

In this blog post, I want to share with you, how you can build functional select dropdown without using any libraries and frameworks. You can apply styling to select options and add custom components there.

Before you start making custom select dropdown, you should ask the following questions:

Will select dropdown have hundreds or thousands of options?

Can users select multiple options?

In both cases, you would need to use a third-party library, because own implementation can take a lot of time. If you need a dropdown with hundreds of options, you should provide users a search feature. If dropdown has thousands of options you should use “windowing” technique. For more information, check Advanced Select Dropdown.

Custom Select Dropdown Example

Here you can see default select element, which can look a bit different depending on the browser you are using.

In this tutorial, we will turn default select element into this:
Tesla
Tesla Volvo Mercedes

The HTML

Traditionally, when you need to create a custom select dropdown, you would use the following structure.


<select>
    <option value="tesla">Tesla</option>
    <option value="volvo">Volvo</option>
    <option value="mercedes">Mercedes</option>
</select">

Since we cannot use select tag, we have more options for choosing html structure. We can use ul and li elements or div with span elements. Let’s opt for div and span structure.


<div class="custom-select-wrapper">
    <div class="custom-select">
        <div class="custom-select__trigger"><span>Tesla</span>
            <div class="arrow"></div>
        </div>
        <div class="custom-options">
            <span class="custom-option selected" data-value="tesla">Tesla</span>
            <span class="custom-option" data-value="volvo">Volvo</span>
            <span class="custom-option" data-value="mercedes">Mercedes</span>
        </div>
    </div>
</div>

The entire component is wrapped in <div class=”.custom-select-wrapper”>. Selected value is rendered in <div class="custom-select__trigger">. <div class="arrow"> content is an HTML-Entity which will produce a nice downwards facing arrow for us. <div class="custom-options"> is wrapper of all options. Each option has data-value attribute, which value represents option’s value. If you don’t know html data attributes, check this article.

The CSS

It is a good idea to start with a css reset, which reduces browser inconsistencies. I opted for Eric Meyer’s css reset:


/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */
 html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
     margin: 0;
     padding: 0;
     border: 0;
     font-size: 100%;
     font: inherit;
     vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
 article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
     display: block;
}
 body {
     line-height: 1;
}
 ol, ul {
     list-style: none;
}
 blockquote, q {
     quotes: none;
}
 blockquote:before, blockquote:after, q:before, q:after {
     content: '';
     content: none;
}
 table {
     border-collapse: collapse;
     border-spacing: 0;
}

Source: https://meyerweb.com/eric/tools/css/reset/


*, *:after, *:before {
     box-sizing: border-box;
}

I prefer using the box-model where width = element width + padding + borders.


.custom-select-wrapper {
     position: relative;
     user-select: none;
     width: 100%;
}
 .custom-select {
     position: relative;
     display: flex;
     flex-direction: column;
     border-width: 0 2px 0 2px;
     border-style: solid;
     border-color: #394a6d;
}
 .custom-select__trigger {
     position: relative;
     display: flex;
     align-items: center;
     justify-content: space-between;
     padding: 0 22px;
     font-size: 20px;
     font-weight: 300;
     color: #3b3b3b;
     height: 60px;
     line-height: 60px;
     background: #ffffff;
     cursor: pointer;
     border-width: 2px 0 2px 0;
     border-style: solid;
     border-color: #394a6d;
}
 .custom-options {
     position: absolute;
     display: block;
     top: 100%;
     left: 0;
     right: 0;
     border: 2px solid #394a6d;
     border-top: 0;
     background: #fff;
     transition: all 0.5s;
     opacity: 0;
     visibility: hidden;
     pointer-events: none;
     z-index: 2;
}
 .custom-select.open .custom-options {
     opacity: 1;
     visibility: visible;
     pointer-events: all;
}
 .custom-option {
     position: relative;
     display: block;
     padding: 0 22px 0 22px;
     font-size: 22px;
     font-weight: 300;
     color: #3b3b3b;
     line-height: 60px;
     cursor: pointer;
     transition: all 0.5s;
}
 .custom-option:hover {
     cursor: pointer;
     background-color: #b2b2b2;
}
 .custom-option.selected {
     color: #ffffff;
     background-color: #305c91;
}

First, we added some basic styling for html elements. <div class=”custom-select”> position is relative, because we need to apply absolute positioning for dropdown list.

The next css is used for dropdown arrow.


.arrow {
     position: relative;
     height: 15px;
     width: 15px;
}
 .arrow::before, .arrow::after {
     content: "";
     position: absolute;
     bottom: 0px;
     width: 0.15rem;
     height: 100%;
     transition: all 0.5s;
}
 .arrow::before {
     left: -5px;
     transform: rotate(45deg);
     background-color: #394a6d;
}
 .arrow::after {
     left: 5px;
     transform: rotate(-45deg);
     background-color: #394a6d;
}
 .open .arrow::before {
     left: -5px;
     transform: rotate(-45deg);
}
 .open .arrow::after {
     left: 5px;
     transform: rotate(45deg);
}

You can see CSS3 properties transition and transform. Transition is used for fade-in and fade-out the dropdown list. Transform is used for animating arrow, which direction represents dropdown state.

The JavaScript

You don’t need to use third-party libraries for dom manipulation. For simple dropdown, you need only basic javascript events.


document.querySelector('.custom-select-wrapper').addEventListener('click', function() {
    this.querySelector('.custom-select').classList.toggle('open');
})

We added click function to the wrapper. Click function toggles class .open, which represents if options list is opened or closed. Inside click function we are searching <div class=”custom-select”> by this.querySelector('.custom-select'). This inside click function represents clicked element. We are not querying whole document by document.querySelector('.custom-select') in case we have more dropdowns inside DOM.


for (const option of document.querySelectorAll(".custom-option")) {
    option.addEventListener('click', function() {
        if (!this.classList.contains('selected')) {
            this.parentNode.querySelector('.custom-option.selected').classList.remove('selected');
            this.classList.add('selected');
            this.closest('.custom-select').querySelector('.custom-select__trigger span').textContent = this.textContent;
        }
    })
}

In the javascript snippet above, we are iterating over all options and adding click function, which changes selected option by adding .selected class. Again, inside click function we are querying within clicked option (this in the snippet), in case we want to change event handlers to support multiple dropdowns on one page.


window.addEventListener('click', function(e) {
    const select = document.querySelector('.custom-select')
    if (!select.contains(e.target)) {
        select.classList.remove('open');
    }
});

The last snippet is optional and allow closing dropdown list, if user clicks out of dropdown.

Multiple select dropdowns on one page

If we want to use multiple select dropdowns on one page, we need to attach click listener to all .custom-select-wrapper elements and close all dropdowns if user clicks out of a dropdown.


// document.querySelector('.custom-select-wrapper').addEventListener('click', function () {
//     this.querySelector('.custom-select').classList.toggle('open');
// })
for (const dropdown of document.querySelectorAll(".custom-select-wrapper")) {
    dropdown.addEventListener('click', function() {
        this.querySelector('.custom-select').classList.toggle('open');
    })
}

// window.addEventListener('click', function (e) {
//     const select = document.querySelector('.custom-select')
//     if (!select.contains(e.target)) {
//         select.classList.remove('open');
//     }
// });

window.addEventListener('click', function(e) {
    for (const select of document.querySelectorAll('.custom-select')) {
        if (!select.contains(e.target)) {
            select.classList.remove('open');
        }
    }
});

Advanced Select Dropdown

If you need a select dropdown with hundreds of options or multiple select, you can check some of the available libraries. In this article you can see some comparison of the most popular libraries.

In case you need to deal with thousands of options, you cannot render a DOM with thousands of option elements. If you cannot use any back-end, you will need to use “windowing” technique.

What is “windowing” technique?

Rather than rendering thousands of elements from a list at once, “windowing” or virtualization focuses on rendering just items visible to the user. You can check this Brian David Vaughn talk about implementing “windowing” technique, which is easy to implement and can provide huge performance gains.

Conclusion

I hope this blog post help you how to build custom select dropdown component. At first, you need to know if select dropdown contains many options or if users can select multiple options. If you need a basic select component, blog post describes whole implementation in HTML, CSS and JavaScript. You can check the source code on Github.

NEED A FULL STACK WEB DEVELOPER? LET'S BUILD SOMETHING.

GET IN TOUCH

SIGN UP TO GET NEW POSTS IN EMAIL

Holler Box

Welcome! Do you mind if I ask you a few questions?

How do you describe what you do for a living? (1/3)

Which of these best describes you? (2/3)

What's your BIGGEST challenge right now? (3/3)

Please tell me more

Please tell me more

Please tell me more

What brings you to this website? (3/3)

Please tell me more

Please tell me more

Name

Company

Email*

Phone Number

Holler Box
%d bloggers like this: