skip to Main Content

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


  1. To prevent a select element from showing its drop-down menu when clicked, you can use the mousedown event and preventDefault() method in JavaScript. Here’s an example code snippet:

    $('select').on('mousedown', function(e) {
      e.preventDefault();
    });
    

    This code will prevent the default behavior of the mousedown event, which is to show the drop-down menu, for all select 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 the close method to hide it. Here’s an example code snippet:

    $('#mySelect2').select2('open');
    

    This code will open the drop-down menu for the select element with an ID of mySelect2.

    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 the select2-hidden-accessible class.

    Login or Signup to reply.
  2. 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.

    document.addEventListener('DOMContentLoaded', e => {
      let selectElm = document.forms.form01.countries;
      let labelElm = selectElm.labels[0];
      let ul = document.createElement('UL');
      labelElm.append(ul);
      [...selectElm.options].forEach((option, i) => {
        let li = document.createElement('LI');
        li.innerText = option.innerText;
        li.option = option; // handy hack
        if (i == selectElm.selectedIndex) li.classList.add('selected');
        ul.append(li);
      });
    });
    
    document.forms.form01.addEventListener('click', e => {
      if (e.target.option) {
        let ul = e.target.closest('ul');
        ul.classList.toggle('unfold');
        ul.querySelectorAll('li').forEach(li => li.classList.remove('selected'));
        e.target.classList.add('selected');
        e.target.option.selected = true;
      }
    });
    
    document.forms.form01.addEventListener('submit', e => {
      e.preventDefault();
      let data = new FormData(e.target);
      console.log([...data]);
    });
    select {
      display: none;
    }
    
    label ul {
      margin: 0 0 .5em;
      padding: 0;
      border: thin solid black;
    }
    
    label ul li {
      display: none;
    }
    
    label ul.unfold li {
      display: block;
    }
    
    label ul li.selected {
      display: block;
      background-color: lightgray;
    }
    <form name="form01">
      <label>
      <select name="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>
      </label>
      <button type="submit">Submit</button>
    </form>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search