DEV Community

Cover image for Creating a Custom and Consistent Selection Menu: Beyond HTML <select>
António Marques
António Marques

Posted on

Creating a Custom and Consistent Selection Menu: Beyond HTML <select>

The HTML element is commonly used to create a menu of options, allowing users to make selections from the available choices. It's a fundamental tool in frontend development for web interfaces. However, there are scenarios where the element has limitations, especially regarding the customization of elements.

For instance, imagine wanting to create an options menu that includes icons. It might seem simple: you can install an icon library and render the component within tags. However, upon doing so, you realize that the icon is not displayed as expected. An alternative would be to insert a Unicode character to represent the icon. However, adopting this approach, you soon realize that you cannot style just the individual Unicode character, as the styling affects the entire .

This article aims to demonstrate innovative thinking and show how to overcome the limitations of the standard HTML selection. We'll explore ways to customize and create unique selections, providing an enhanced user experience. Let's think outside the box and go beyond conventional options.

Requirements
To follow this tutorial, you need to have certain prerequisites installed and configured:

  • React: Make sure you have React installed.
  • FontAwesome: FontAwesome will be used for icons.
  • CSS: We'll create styles to enhance the appearance of the selection menu.

In this post, I'll showcase the creation of a straightforward custom select, illustrating how to handle situations where customization of select options is needed. Our example will be somewhat similar to the following:

Image description

Image description

Organization:

  1. Creating an Array with Objects Representing an Option
  2. Mapping the Transport List and Rendering the Icon and Name
  3. Display the Chosen Option
  4. Update the selected option
  5. display and hide list of options
  6. Styling
  7. Full code
  8. Advantages

1. Creating an Array with Objects Representing an Option
Let's start by creating an array with objects that represent different transportation options. Each object should have attributes for value, name, and icon. This array will represent our transportation options.

// Importing specific icons from Font Awesome
import { faPlane, faCar, faBicycle, faShip } from "@fortawesome/free-solid-svg-icons";

// Importing the FontAwesomeIcon component from Font Awesome 
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 

  // Array of transportation options with their respective values, names, and icons
  const transportList = [
    { value: 'airplane', name: 'airplane', icon: <FontAwesomeIcon icon={faPlane} /> },
    { value: 'car', name: 'car', icon: <FontAwesomeIcon icon={faCar} /> },
    { value: 'bike', name: 'bicycle', icon: <FontAwesomeIcon icon={faBicycle} /> },
    { value: 'ship', name: 'ship', icon: <FontAwesomeIcon icon={faShip} /> },
  ]

Enter fullscreen mode Exit fullscreen mode


2. Mapping the Transport List and Rendering the Icon and Name
Let's map the list of transportation options and render the icon and name for each option.

 <ul>
  {
    transportList.map((item, index) => {
      return (
        <li> {
          <span>{item.icon}</span> {/* Displaying the icon of each option */}
          {item.name} {/* Displaying the name of each option */}
        </li>
      );
    })
  }
</ul>

Enter fullscreen mode Exit fullscreen mode


3. Display the Chosen Option
Often, the first option in the list is displayed as the default selected option.
In this case, we will display the first object (option) by default. Using knowledge about arrays and the useState hook, we have the following:

// Importing useState hook from React
import { useState } from 'react';
Enter fullscreen mode Exit fullscreen mode


// State for the selected transport option
  const [optionSelected, setOptionSelected] = useState(transportList[0])
Enter fullscreen mode Exit fullscreen mode

<div className='containerOptionSelected' onClick={() => handleOptionSelected()}> {/* Displayed selected option */}
          <div className='optionSelected'>
            <div>
              {optionSelected.icon} {/* Displaying the icon of the selected option */}
            </div>
            <span>{optionSelected.name}</span> {/* Displaying the name of the selected option */}
          </div>
          <div>
            <FontAwesomeIcon icon={faAngleDown} /> {/* Icon for the dropdown arrow */}
          </div>
        </div>
Enter fullscreen mode Exit fullscreen mode


4. Update the selected option
Let's create a function that updates the chosen option. The idea is to call the function when an option is clicked and update the transportList array index.

  // Function to handle click on an option and update the selected option
  const handleOptionClick = (index) => {
    setOptionSelected(transportList[index]);
  }          
Enter fullscreen mode Exit fullscreen mode

{/* Event click on li tag calls the function handleOptionClick */}
<ul>
  { 
    transportList.map((item, index) => {
      return (
        <li onClick={() => handleOptionClick(index)}> {/* Click event triggers handleOptionClick */}
          <span>{item.icon}</span> {/* Displaying the icon of each option */}
          {item.name} {/* Displaying the name of each option */}
        </li>
      );
    })
  }
</ul>
Enter fullscreen mode Exit fullscreen mode


5. display and hide list of options
Lastly, we will create an event that hides and displays the list of options. To do this, we create a state that receives false to
hide and true to display. let's do it

// State to toggle display of hidden options
const [showHiddenOption, setShowHiddenOption] = useState(false)
Enter fullscreen mode Exit fullscreen mode

// Function to toggle the display of hidden options
  const handleOptionSelected = () => {
    setShowHiddenOption(!showHiddenOption)
  }
Enter fullscreen mode Exit fullscreen mode

<div className='containerOptionSelected' onClick={() => handleOptionSelected()}> {/* Container for displaying the selected option */}
  <div className='optionSelected'> {/* Displayed selected option */}
    <div>
      {optionSelected.icon} {/* Displaying the icon of the selected option */}
    </div>
    <span>{optionSelected.name}</span> {/* Displaying the name of the selected option */}
  </div>
  <div>
    <FontAwesomeIcon icon={faAngleDown} /> {/* Icon for the dropdown arrow */}
  </div>
</div>

Enter fullscreen mode Exit fullscreen mode

{
  showHiddenOption && ( /* Display hidden options if 'showHiddenOption' is true */
    <ul>
      {
        transportList.map((item, index) => {
          return (
            <li key={item.value} onClick={() => handleOptionClick(index)}>
              <span>{item.icon}</span> {/* Displaying the icon of each option */}
              {item.name} {/* Displaying the name of each option */}
            </li>
          );
        })
      }
    </ul>
  )
}

Enter fullscreen mode Exit fullscreen mode


6. Styling
To enhance the intuitiveness of our option selector, let's apply some CSS to our classes. We'll be writing the CSS code in both index.css and App.css

/*App.css*/

/* CSS for styling the option selector container */
.containerSelect {
  position: relative; /* Positioned relative to its normal position */
  display: inline-block; /* Displayed as an inline-level block container */
  margin-left: 10px; /* Left margin of 10px */
}

/* CSS for styling the selected option container */
.containerOptionSelected {
  display: flex; /* Flexbox: displays items in a row */
  justify-content: space-between; /* Spacing between the items */
  align-items: center; /* Aligns items vertically at the center */
  background-color: white; /* White background color */
  padding: 5px 10px 5px 10px; /* Padding */
  cursor: default; /* Default cursor (non-editable) */
  border-radius: 5px; /* Rounded corners */
  width: 200px; /* Container width */
  height: 30px; /* Container height */
  border: 1px solid inherit; /* Solid border inheriting the border color */
}

/* CSS for styling the selected option content */
.optionSelected {
  display: flex; /* Flexbox: displays items in a row */
}

/* CSS for styling the elements inside the selected option */
.optionSelected div {
  margin: 0 10px 0 0; /* Margin between the elements */
}

/* CSS for styling the unordered list */
ul {
  width: 100%; /* Full width */
  background-color: white; /* White background color */
  display: inline-block; /* Displayed as an inline-level block container */
  padding: 0; /* No padding */
  position: absolute; /* Positioned absolutely */
  margin: 2px 0 0 0; /* top margin */
  border-radius: 5px; /* Rounded corners */
  border: 1px solid inherit; /* Solid border inheriting the border color */
}

/* CSS for styling the list items */
ul li {
  list-style: none; /* No list-style (bullets) */
  padding: 5px 10px 5px 10px; /* Padding for each list item */
  margin: 0 auto; /* Centering the list item */
}

/* CSS for styling the list items on hover */
ul li:hover {
  background-color: darkslategray; /* Background color on hover */
  color: white; /* Text color on hover */
}

/* CSS for styling the span inside list items */
ul li span {
  margin: 0 10px 0 0; /* Margin for the span inside list items */
}

Enter fullscreen mode Exit fullscreen mode

/*index.css*/

/* CSS for styling the body */
body {
  background-color: #F1F5F9; /* Background color using a hex value */
  display: flex; /* Flexbox: displays items in a row by default */
  justify-content: center; /* Centers the content horizontally within the body */
}

Enter fullscreen mode Exit fullscreen mode


7. Full code

import './App.css'; // Importing the CSS file for styling

import { useState } from 'react'; // Importing useState hook from React

import { faPlane, faCar, faBicycle, faShip, faAngleDown } from "@fortawesome/free-solid-svg-icons"; // Importing specific icons from Font Awesome
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; // Importing FontAwesomeIcon component from Font Awesome

function App() {
  // Array of transport options with their respective values, names, and icons
  const transportList = [
    { value: 'airplane', name: 'airplane', icon: <FontAwesomeIcon icon={faPlane} /> },
    { value: 'car', name: 'car', icon: <FontAwesomeIcon icon={faCar} /> },
    { value: 'bike', name: 'bicycle', icon: <FontAwesomeIcon icon={faBicycle} /> },
    { value: 'ship', name: 'ship', icon: <FontAwesomeIcon icon={faShip} /> },
  ]

  const [optionSelected, setOptionSelected] = useState(transportList[0]) // State for the selected transport option
  const [showHiddenOption, setShowHiddenOption] = useState(false) // State to toggle display of hidden options

  // Function to toggle the display of hidden options
  const handleOptionSelected = () => {
    setShowHiddenOption(!showHiddenOption)
  }

  // Function to handle click on an option and update the selected option
  const handleOptionClick = (index) => {
    setOptionSelected(transportList[index]);
  }

  return (
    <div className='App'>
      <div className='containerSelect'> {/* Container for the select element */}
        <div className='containerOptionSelected' onClick={() => handleOptionSelected()}> {/* Displayed selected option */}
          <div className='optionSelected'>
            <div>
              {optionSelected.icon} {/* Displaying the icon of the selected option */}
            </div>
            <span>{optionSelected.name}</span> {/* Displaying the name of the selected option */}
          </div>
          <div>
            <FontAwesomeIcon icon={faAngleDown} /> {/* Icon for the dropdown arrow */}
          </div>
        </div>
        {
          showHiddenOption && ( /* Display hidden options if 'showHiddenOption' is true */
            <ul>
              {
                transportList.map((item, index) => {
                  return (
                    <li onClick={() => handleOptionClick(index)}>
                      <span>{item.icon}</span> {/* Displaying the icon of each option */}
                      {item.name} {/* Displaying the name of each option */}
                    </li>
                  )
                })
              }
            </ul>
          )
        }
      </div>
    </div>
  );
}

export default App; // Exporting the App component as the default export
Enter fullscreen mode Exit fullscreen mode


8. Advantages

Using a custom selection menu instead of the standard HTML element offers several advantages in terms of flexibility, design, and interactivity. Here are some common advantages:

Custom Styling: With a custom selection menu, you have complete control over style and appearance, allowing seamless integration with the overall design of your website or application.

Browser Compatibility: Styles applied to a custom selection menu are more consistent and compatible across different browsers, ensuring a uniform experience for users.

Icons and Graphic Elements: It's easy to add icons, images, or other graphical elements to the custom selection menu, making the interface richer and more informative for users.

Advanced Functionality: Advanced features such as search, filtering, multiple selection, dynamic option loading, etc., can be easily added to enhance usability and efficiency.

Animations and Transitions: Smooth animations and transitions can be incorporated to create a more enjoyable experience during option selection.

Enhanced Accessibility: When building a custom selection menu, you can optimize accessibility, ensuring the interface is usable for people with different needs.

Ease of Implementation: With modern libraries and frameworks, creating a custom selection menu has become easier and faster, saving time and effort in development.

Integration with Libraries and Frameworks: It's possible to integrate the custom selection menu with popular libraries like React, Angular, or Vue.js to further enhance functionality and efficiency.

Improved User Experience: The ability to customize the interface and add specific functionalities can lead to a more intuitive and pleasant user experience.

Thank You.

Top comments (0)