Codementor Events

Create a Dropdown using React JS, Font Awesome and LESS CSS

Published Feb 10, 2015Last updated Apr 12, 2017
Create a Dropdown using React JS, Font Awesome and LESS CSS

Today, I'm going to talk about how to create a dropdown control using React JS, LESS CSS, and Font Awesome. I'm going to assume you're at least halfway familiar with these tools, but if you're not, I suggest you take a look using the links above.

The dropdown itself is a React JS class which is styled using LESS CSS to provide some fancy open and close animations. You can see it here.
I'll split the write-up into a few sections. First, we'll go over the Dropdown React class, including both the view and the event handlers, and then I'll discuss the CSS used to make the fancy animations for the dropdown. This write-up isn't intended for production use by any stretch; it's here more as a tutorial showing how to build a React class instead of any sort of production-ready control.

Note: The CSS shown here might not match up with exactly what you see on the demo page because I want the demo to look at least halfway decent with some pretty styling. For the most part, though, I'm going to try to keep the CSS on the tutorial as bare as possible.

React

var Dropdown = React.createClass({
  getInitialState: function() {
    return {
      listVisible: false
    };
  },
  
  select: function(item) {
    this.props.selected = item;
  },
        
  show: function() {
    this.setState({ listVisible: true });
    document.addEventListener("click", this.hide);
  },
        
  hide: function() {
    this.setState({ listVisible: false });
    document.removeEventListener("click", this.hide);
  },
      
  render: function() {
    return <div className={"dropdown-container" + (this.state.listVisible ? " show" : "")}>
      <div className={"dropdown-display" + (this.state.listVisible ? " clicked": "")} onClick={this.show}>
        <span style={{ color: this.props.selected.hex }}>{this.props.selected.name}</span>
        <i className="fa fa-angle-down"></i>
      </div>
      <div className="dropdown-list">
        <div>
          {this.renderListItems()}
        </div>
      </div>
    </div>;
  },
        
  renderListItems: function() {
    var items = [];
    for (var i = 0; i < this.props.list.length; i++) {
      var item = this.props.list[i];
      items.push(<div onClick={this.select.bind(null, item)}>
        <span style={{ color: item.hex }}>{item.name}</span>
      </div>);
    }
    return items;
  }
});
      
var colours = [{
  name: "Red",
  hex: "#F21B1B"
}, {
  name: "Blue",
  hex: "#1B66F2"
}, {
  name: "Green",
  hex: "#07BA16"
}];

React.render(<Dropdown list={colours} selected={colours[0]} />, document.getElementById("container"));

The first thing we're doing here is creating the Dropdown class by invoking React's createClass method. The getInitialState function sets up the state of the Dropdown when it's first created, namely that the list portion is not currently visible. The class takes two different props when being created: list and selected. The former is the list of items that should be displayed when the user opens up the dropdown, and the latter is the currently selected item.

After that, there are three methods used for event handling: select, show and hide. The select method handles when the user clicks on a list item and sets the selected prop. Show and hide are responsible for showing and hiding the list of items, respectively.

The render method is a required function for a React class and is responsible for building the HTML that gets shown to the client. Here, we're setting various CSS classes (which I'll go over a little later) for styling both the hidden and shown states for the dropdown's list. The click handler is set up on the label container. The renderListItems method is responsible for rendering each individual list item's div. The label for each item has its colour CSS property set, too, to provide some added feedback for which item the user can choose.

Finally, we render the Dropdown class using React's render method, while specifying the list of pre-built colours and the selected colour as props.

LESS CSS

@height:40px;
@spacing:10px;
@select-colour:#2875C7;
@font-size:14px;
@border-colour:#DDD;
      
div.dropdown-container {
  &.show>div.dropdown-list {
    .transform(scale(1, 1));
  }

  @icon-width:14px;
        
  >div.dropdown-display {
    float:left;
    width:100%;
    background:white;
    height:@height;
    cursor:pointer;
    border:solid 1px @border-colour;
    .border-box;

    >* {
      float:left;
      height:100%;
      .vertical-centre(@height);
    }
    
    >span {
      font-size:@font-size;
      width:100%;
      position:relative;
      .border-box;
      padding-right:@icon-width+@spacing*2;
      padding-left:@spacing;
    }
    
    >i {
      position:relative;
      width:@icon-width;
      margin-left:(@spacing+@icon-width)*-1;
      font-size:1.125em;
      font-weight:bold;
      padding-right:@spacing;
      text-align:right;
    }
  }

  >div.dropdown-list {
    float:left;
    width:100%;
    position:relative;
    width:100%;
    .transform(scale(1, 0));
    .transition(-webkit-transform ease 250ms);
    .transition(transform ease 250ms);

    >div {
      position:absolute;
      width:100%;
      z-index:2;
      cursor:pointer;
      background:white;

      >div {
        float:left;
        width:100%;
        padding:0 @spacing;
        font-size:@font-size;
        .border-box;
        border:solid 1px @border-colour;
        border-top:none;

        @icon-width:20px;

        &:hover {
          background:#F0F0F0;
        }

        &.selected {
          background:@select-colour;
          color:white;
        }

        >* {
          .vertical-centre(@height);
        }

        >span {
          float:left;
          width:100%;
        }
      }
    }
  }
}

In the CSS section, we're just setting up the look and feel of the dropdown. The dropdown-list class shows how I'm doing the animation to show and hide the dropdown list, which is a scale operation. I'm animating on the transform instead of height because of mobile compatibility. A browser is really good at animating two things: opacity and transforms (like scale, for instance). On a mobile device, using transition with height will result in poor performance, but transform scale works much, much better.

Conclusion

And that's it! Similar to the ones above, I've also put together an Angular demo page which shows off the dropdown in its final form. You can see it here. Thanks for reading!

Discover and read more posts from Chris Harrington
get started
post commentsBe the first to share your opinion
Dinh Lam Viet
6 years ago

Thanks for make it easier than I thought :D

Vishal Chitnis
6 years ago

Hello Chris,

Nice tutorial.
I do have a suggestion though. props.selected is being mutated in the select() function. props in the reactjs are primarily meant to be pass into a component (ref). I think it would be much better if selected is managed in the state.

var Dropdown = React.createClass({
  getInitialState: function() {
    return {
      listVisible: false,
      selected: this.props.selected
    };
  },
  
  select: function(item) {
    this.setState({ selected: item })
  },
        
  show: function() {
    this.setState({ listVisible: true });
    document.addEventListener("click", this.hide);
  },
        
  hide: function() {
    this.setState({ listVisible: false });
    document.removeEventListener("click", this.hide);
  },
      
  render: function() {
    return <div className={"dropdown-container" + (this.state.listVisible ? " show" : "")}>
      <div className={"dropdown-display" + (this.state.listVisible ? " clicked": "")} onClick={this.show}>
        <span style={{ color: this.state.selected.hex }}>{this.state.selected.name}</span>
        <i className="fa fa-angle-down"></i>
      </div>
      <div className="dropdown-list">
        <div>
          {this.renderListItems()}
        </div>
      </div>
    </div>;
  },
        
  renderListItems: function() {
    var items = [];
    for (var i = 0; i < this.props.list.length; i++) {
      var item = this.props.list[i];
      items.push(<div onClick={this.select.bind(null, item)}>
        <span style={{ color: item.hex }}>{item.name}</span>
      </div>);
    }
    return items;
  }
});
Rick LaMont
7 years ago

The LESS code references 4 Mixins that are undefined. If you try to compile it lessc emits this error:

NameError: .transform is undefined in Dropdown.less on line 9, column 5

I suggest adding the following Mixin definitions to the beginning of the LESS code:

.vertical-centre(@height) {
  height:@height;
  line-height:@height !important;
  display:inline-block;
  vertical-align:middle;
}

.border-box {
  box-sizing:border-box;
  -moz-box-sizing:border-box;
}

.transition(@value1, @value2:X, ...) {
  @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
  -webkit-transition: @value;
  -moz-transition: @value;
  -ms-transition: @value;
  -o-transition: @value;
  transition: @value;
}

.transform(@value1, @value2:X, ...) {
  @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
  transform:@value;
  -ms-transform:@value;
  -webkit-transform:@value;
  -o-transform:@value;
  -moz-transform:@value;
}
Show more replies