Solved

Creating a custom sort by select box that looks good on all platforms

JunePratt
Shopify Partner
6 0 13

Hey shopify designers I'm working with a client and they've asked me to do something that I think every web designer struggles with. Their ask is to make the collection sort by dropdown look uniform and on brand. The dropdown looks fine on windows but when looking at it on MacOS webkit takes over and the options look like default select options. I've of course tried "-webkit-appearance:none;" but unfortunately that means absolutely nothing when webkit has limited design capabilities of select elements to begin with.

Just when I thought to give up I found what looked like a solution: using div and span elements as well as some javascript. People have tried to do this already and I've successfully implemented it into my theme, but I've run into a problem. I have no idea how shopify changes urls with the default sort by so I'm struggling with making the dropdown actually functional. If anyone has any pointers on how to handle this please let me know.

 

For reference my 'collection-sorting.liquid' looks like this:

 

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

.container {
  margin: 20px;
  max-width: 300px;
}

.custom-select-wrapper {
  position: relative;
  user-select: none;
  width: 100%;
}

.custom-select {
  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;
}

.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);
}
/* 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;
}


</style>
<div class="filter-dropdown__wrapper text-center collection-filters">
  <div class="filter-dropdown" style="float:right;">
    <label class="filter-dropdown__label" for="sortBy">
      <span class="filter-dropdown__label--title" style="font-size: 1em; color:#999999;">Sort by</span>
      <span class="filter-dropdown__label--active"></span>
    </label>
    {% assign sort_by = collection.sort_by | default: collection.default_sort_by %}
        <div class="container">
        <div class="custom-select-wrapper">
            <div class="custom-select">
                <div class="custom-select__trigger"><span>Featured</span>
                    <div class="arrow"></div>
                </div>
                <div class="custom-options">

                    <span class="custom-option selected" data-value="manual">Featured</span>
                    <span class="custom-option" data-value="created-ascending">Date, old to new</span>
                    <span class="custom-option" data-value="created-descending">Date, new to old</span>
                    <span class="custom-option" data-value="price-ascending">Price, low to high</span>
                    <span class="custom-option" data-value="price-descending">Price, high to low</span>
                </div>
            </div>
        </div>
    </div>
  </div>
</div>
    <script>
    // 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');
        })
    }

    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;
            }
        })
    }

    // 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');
            }
        }
    });
    </script>
Accepted Solution (1)

Diofaro
Visitor
1 1 1

This is an accepted solution.

Hi mate! I had to do the same with a client recently and this post was great help! All of this because MAC OSX and webkit browsers like Chrome doesn't let you customize select - option tags. It was a hell, but this post saved my life, just a few more lines of javascript and its working perfectly.
To customize the dropdown select you have to create a custom div like you did, and then you can controll the variant selected with Javascript. 

<script>
    for (const dropdown of document.querySelectorAll(".custom-select-wrapper")) {
        dropdown.addEventListener('click', function () {
            this.querySelector('.custom-select').classList.toggle('open');
        })
    }
                    
    //added this line to capure the original select dropdown --->
const select = document.querySelector(".product-page--cart-form select");
 
    
    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;
              select.value = this.value;   //with this line when you modifie the value of your controller, you will modify the value of the original dropdown select!
            }
        })
    }
                    
    window.addEventListener('click', function (e) {
        for (const select of document.querySelectorAll('.custom-select')) {
            if (!select.contains(e.target)) {
                select.classList.remove('open');
            }
        }
    }); 
  
</script>

View solution in original post

Replies 2 (2)

Diofaro
Visitor
1 1 1

This is an accepted solution.

Hi mate! I had to do the same with a client recently and this post was great help! All of this because MAC OSX and webkit browsers like Chrome doesn't let you customize select - option tags. It was a hell, but this post saved my life, just a few more lines of javascript and its working perfectly.
To customize the dropdown select you have to create a custom div like you did, and then you can controll the variant selected with Javascript. 

<script>
    for (const dropdown of document.querySelectorAll(".custom-select-wrapper")) {
        dropdown.addEventListener('click', function () {
            this.querySelector('.custom-select').classList.toggle('open');
        })
    }
                    
    //added this line to capure the original select dropdown --->
const select = document.querySelector(".product-page--cart-form select");
 
    
    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;
              select.value = this.value;   //with this line when you modifie the value of your controller, you will modify the value of the original dropdown select!
            }
        })
    }
                    
    window.addEventListener('click', function (e) {
        for (const select of document.querySelectorAll('.custom-select')) {
            if (!select.contains(e.target)) {
                select.classList.remove('open');
            }
        }
    }); 
  
</script>
JunePratt
Shopify Partner
6 0 13

This is coming almost half a year late, but thank you! This is more or less what I ended up doing as well.