I’ve made this custom style <select>
box by following this guide and creating a custom <option>
list using <div>
and <a>
tags for a customized style. I have managed to get it to work to what I want but the problem is that preventing the system default <option>
list still opens.
I know I could just change the elements and CSS style to buttons, divs etc instead of <select>
for a custom style but I don’t really want to change the code around too much. I am aware that using :disabled
or .blur()
is an option but using :disabled
is not possible and .blur()
is the most effective method but I need a smooth transition so it doesn’t show.
// This function checks if HTML Attribute exits.
jQuery.fn.hasAttr = function(name) {
let attr = this.attr(name);
return typeof attr !== "undefined" && attr !== false;
};
$(document).ready(() => {
const selectWrapper = $("div.select-wrapper");
const selectList = $("div.option-list");
const selectTag = $("select#countries");
const selectTagOption = selectTag.children("option");
// create a <a></a> list for the custom list div based on the <option></option> list
selectTagOption.each((i, el) => {
let a = $(document.createElement("a"));
let o = $(el);
a.attr("data-value", o.attr("value"));
a.html(o.html());
a.appendTo(selectList);
});
const anchorList = selectList.children("a");
// --- see .blur() works well but not smooth ---
// it shows it first then it closes it immediately but NOT quick enough that it's not visible.
selectTag.click((e) => {
$(e.target).blur();
e.preventDefault();
});
// click event fires when clicked anywhere inside the .select-wrapper div or the <select> tag.
$(document).on("click", [selectTag, selectWrapper], (e) => {
// toggle custom list to open/close
let div = selectList;
let state = div.attr("data-open");
if (state === "false") {
div.css("display", "block");
div.attr("data-open", "true");
} else {
div.css("display", "none");
div.attr("data-open", "false");
}
});
// close the list when clicked outside of the select box
$(window).click((e) => {
let ele = $(e.target);
if (!(ele.is(selectTag) || ele.is(selectWrapper) || ele.is(selectWrapper) || ele.is(anchorList))) {
if (selectList.attr("data-open") === "true") {
selectList.css("display", "none");
selectList.attr("data-open", "false");
}
}
});
// this event fires when clicking on an <a> tag
// this will set the :selected attribute on the <select> and close it
anchorList.click( (e) => {
let a = $(e.target).attr("data-value");
let o = selectTagOption;
o.each((i, element) => {
let el = $(element);
if (el.hasAttr("selected")) {
el.removeAttr("selected");
}
});
for (let i = 0; i < o.length; i++) {
let option = $(o[i]);
let val = option.attr("value");
if (a === val) {
option.attr("selected", ""); // set selected item when match is found
option.change(); // fire the on change event
// then close it?
selectList.css("display", "none");
selectList.attr("data-open", "false");
break;
}
}
});
selectTag.on("change", (e) => {
let item = selectTag.find(":selected").text();
$("#output").html("Selected Country: " + item);
});
});
:root {
--select-border: black;
--select-bgColor: #dcdcdc;
--select-arrow: var(--select-border);
}
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
select {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
background-color: transparent;
border: none;
padding: 0 1em 0 0;
margin: 0;
width: 100%;
font-family: inherit; /* I have a particular font for this */
font-size: 14px;
cursor: inherit;
line-height: inherit;
outline: none;
}
.select-wrapper {
width: 100%;
height: 10%;
min-width: 15ch;
max-width: 30ch;
border: 1px solid var(--select-border);
cursor: pointer;
line-height: 1.1;
background-color: var(--select-bgColor);
display: grid;
grid-template-areas: "select";
align-items: center;
position: relative;
}
.select-wrapper::after {
content: "";
width: 0.8em;
height: 0.5em;
background-color: var(--select-arrow);
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
justify-self: end;
margin-right: 6px;
}
select, .select-wrapper::after {
grid-area: select;
}
.option-list {
display: none;
position: relative;
width: 100%;
border-top: 1px solid var(--select-border);
font-family: inherit;
font-size: 14px;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.option-list a {
float: left;
text-decoration: none;
color: var(--select-border);
text-align: left;
width: 100%;
line-height: 1.5;
}
.option-list a:hover {
background-color: #c5c5c5;
}
.select-wrapper select, .option-list a {
padding-left: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js" integrity="sha512-3gJwYpMe3QewGELv8k/BX9vcqhryRdzRMxVfq6ngyWXwo03GFEzjsUm8Q7RZcHPHksttq7/GFoxjCVUjkjvPdw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<p> Try clicking the select box a few times. </p>
<br>
<!-- The countries are just an example --->
<label for="countries">Select Country: </label>
<div class="select-wrapper">
<select id="countries">
<option value="">None</option>
<option value="AU">Australia</option>
<option value="JP">Japan</option>
<option value="UK">United Kingdom</option>
<option value="FR">France</option>
<option value="NZ">New Zealand</option>
<option value="US">United States</option>
<option value="CA">Canada</option>
</select>
<div class="option-list" data-open="false">
<!-- list of <a></a> tags go here based on the <option> list -->
<!-- this list also needs to be like a popover not a element that pushes elements -->
</div>
</div>
<br>
<p id="output">Selected Country: </p>
<br>
<p><strong>UPDATE:</strong> For some reason when clicking the custom list it won't close for some reason! It also blocks elements by pushing them out of the way which I don't want.</p>
I don’t want to change the code too much because it looks good in design (unless I have to otherwise), and prevent the <select>
box from opening by default when clicked on so it only shows the custom list but still makes it selectable with a change/input event. I also want to make this into a class
object so the same(-ish) design and functionality can be reused like this:
class SelectBox { /* some code here */ }
let selectBox1 = new SelectBox(/*... items */);
let selectBox2 = new SelectBox(/* ... items */);
// then append the html somehow?
2
Answers
To prevent a select element from showing its drop-down menu when clicked, you can use the
mousedown
event andpreventDefault()
method in JavaScript. Here’s an example code snippet:This code will prevent the default behavior of the
mousedown
event, which is to show the drop-down menu, for allselect
elements on the page.If you want to customize the appearance of the drop-down menu, you can use a library like Select2. Select2 provides several built-in methods that allow programmatic control of the component, including opening and closing the drop-down menu. You can use the
open
method to display the menu and theclose
method to hide it. Here’s an example code snippet:This code will open the drop-down menu for the
select
element with an ID ofmySelect2
.Note that Select2 must be initialized on the
select
element before you can use its methods. You can check whether Select2 has been initialized on a particular DOM element by checking for theselect2-hidden-accessible
class.You can set the CSS display property to
none
. A handy thing to do is to add the option elements as a property to the items in the custom list. Then it is easy to set that option as the selected.