I’m trying to build a sorting dropdown for my product list in a React app.
The sorting options are things like:

  • Default

  • A - Z

  • Z - A

  • Price: Low - High

  • Price: High - Low

My first approach was to use a regular <select> element with <option> tags, but I’m finding it very hard to style the dropdown properly using CSS/SCSS, browsers apply their own styles and I am unable to style the <option> dropdown.

<select>
  <option value="default">Default</option>
  <option value="az">A → Z</option>
  <option value="za">Z → A</option>
  <option value="low-high">Price: Low → High</option>
  <option value="high-low">Price: High → Low</option>
</select>

I’d like to create a custom-styled dropdown that looks like my design (for example, a styled button or div that shows a list of options when clicked).

I thought using a set of buttons with onClick handlers instead of <select>

What is the most suitable or recommended way to create a custom-styled dropdown for sorting items in React?
Should I:

  • Build a custom dropdown component from scratch (e.g., using divs and onClick handlers)?

  • Or is there a clean way to style the native <select> element?

I’m using React and SCSS.

6 Replies 6

Yeah, native <select> styling is a pain. You can't really style the dropdown options consistently across browsers, so building a custom component is the way to go.

Here's a clean approach:

const {
  useState,
  useRef,
  useEffect
} = React;

const SortDropdown = ({
  value,
  onChange,
  options
}) => {
  const [isOpen, setIsOpen] = useState(false);
  const dropdownRef = useRef(null);

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
        setIsOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);

  const handleSelect = (optionValue) => {
    onChange(optionValue);
    setIsOpen(false);
  };

  const selectedOption = options.find(opt => opt.value === value);

  return ( <
    div className = "sort-dropdown"
    ref = {
      dropdownRef
    } >
    <
    button className = "sort-dropdown__button"
    onClick = {
      () => setIsOpen(!isOpen)
    } >
    <
    span > {
      selectedOption?.label || 'Sort by'
    } < /span> <
    span className = {
      `arrow ${isOpen ? 'open' : ''}`
    } > ▼ < /span> <
    /button>

    {
      isOpen && ( <
        ul className = "sort-dropdown__list" > {
          options.map((option) => ( <
            li key = {
              option.value
            }
            className = {
              option.value === value ? 'selected' : ''
            }
            onClick = {
              () => handleSelect(option.value)
            } >
            {
              option.label
            } <
            /li>
          ))
        } <
        /ul>
      )
    } <
    /div>
  );
};

const App = () => {
  const [sortValue, setSortValue] = useState('default');

  const products = [{
      id: 1,
      name: 'Widget A',
      price: 29.99
    },
    {
      id: 2,
      name: 'Gadget Z',
      price: 49.99
    },
    {
      id: 3,
      name: 'Tool B',
      price: 19.99
    },
    {
      id: 4,
      name: 'Device Y',
      price: 39.99
    },
  ];

  const sortOptions = [{
      value: 'default',
      label: 'Default'
    },
    {
      value: 'az',
      label: 'A → Z'
    },
    {
      value: 'za',
      label: 'Z → A'
    },
    {
      value: 'low-high',
      label: 'Price: Low → High'
    },
    {
      value: 'high-low',
      label: 'Price: High → Low'
    },
  ];

  const sortedProducts = [...products].sort((a, b) => {
    switch (sortValue) {
      case 'az':
        return a.name.localeCompare(b.name);
      case 'za':
        return b.name.localeCompare(a.name);
      case 'low-high':
        return a.price - b.price;
      case 'high-low':
        return b.price - a.price;
      default:
        return 0;
    }
  });

  return ( <
    div className = "container" >
    <
    h2 > Product List < /h2> <
    SortDropdown value = {
      sortValue
    }
    onChange = {
      setSortValue
    }
    options = {
      sortOptions
    }
    /> <
    div className = "product-list" > {
      sortedProducts.map(product => ( <
        div key = {
          product.id
        }
        className = "product-item" >
        <
        span > {
          product.name
        } < /span> <
        span > $ {
          product.price
        } < /span> <
        /div>
      ))
    } <
    /div> <
    /div>
  );
};

ReactDOM.render( < App / > , document.getElementById('root'));
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
  padding: 40px;
  background: #f5f5f5;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  background: white;
  padding: 30px;
  border-radius: 8px;
}

.sort-dropdown {
  position: relative;
  display: inline-block;
  margin-bottom: 20px;
}

.sort-dropdown__button {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  background: white;
  border: 1px solid #ddd;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
}

.sort-dropdown__button:hover {
  border-color: #999;
  background: #fafafa;
}

.arrow {
  transition: transform 0.2s;
  font-size: 10px;
}

.arrow.open {
  transform: rotate(180deg);
}

.sort-dropdown__list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  background: white;
  border: 1px solid #ddd;
  border-radius: 6px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  list-style: none;
  padding: 4px;
  margin: 0;
  z-index: 100;
}

.sort-dropdown__list li {
  padding: 8px 12px;
  cursor: pointer;
  border-radius: 4px;
  white-space: nowrap;
}

.sort-dropdown__list li:hover {
  background: #f5f5f5;
}

.sort-dropdown__list li.selected {
  background: #e6f2ff;
  font-weight: 500;
}

.product-list {
  display: grid;
  gap: 15px;
}

.product-item {
  display: flex;
  justify-content: space-between;
  padding: 15px;
  background: #fafafa;
  border-radius: 6px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

The click-outside handler closes the dropdown when you click elsewhere, which is essential for good UX. If you don't want to build this yourself, Radix UI or Headless UI have unstyled dropdown components that handle all the accessibility stuff for you.

the default HTML <select> element is notoriously difficult to style beyond basic tweaks. To create a fully custom-styled sorting dropdown in React, you should build it from scratch using normal HTML elements (like div, ul, li, button) and manage open/close + selection behavior with React state.

Using native, semantically appropriate elements gives you a lot for free. Critically, this includes usability and accessibility. While it can be tempting to simply spin your own custom form element, 90% of the time that people do this they make sure it looks good and works with a mouse, but omit critical usability and accessibility bits. Will your custom version be keyboard navigable? Will it display the behavior most keyboard users will expect a dropdown to exhibit? Will it report itself appropriately to assistive technologies like screen readers?

To get the best usability, generally native elements are going to be your best bet. Your next best option will be to use a library that handles accessibility for you under the hood. If you find you must spin your own, I'd strongly encourage you to leverage some authority like the WAI's ARIA Authoring Practices Guide, which provides "recipes" for common interactive components and how to implement them so they conform to specs and expectations. The select-only combobox example would probably be applicable to your situation. Good luck, and happy coding!

Why don't you install one of the many existing packages which does that? Why not use MUI?

Anyway, this MDN page showcases how to style a <Select> element

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.