DEV Community

Cover image for Unicode mood selector (star-rating)
Andrew Bone
Andrew Bone

Posted on

Unicode mood selector (star-rating)

There have been several posts written this week about star ratings, as part of the "Star (rating) Wars". I've written a couple of articles first how to make an accessible star rating system and then a follow up about how to make it a little more interesting with animations.

In this article I'll be doing something a little different. I'll take the lessons we've learnt from the past two articles and making an animated mood selector.

the Star (rating) wars

If you're interested, and I think you will be, there are a few posts from different authors worth reading. Check out posts by @inhuofficial , @lapstjup , @madsstoumann , @afif, @siddharthshyniben and @lionelrowe.

The code

I'm going to briefly touch on each part of the component and how it's built but I won't be going into too much depth, that being said if you have any questions, suggestions or want clarification feel free to leave a comment and I'll do my best to answer.

HTML

The HTML will be a little different to my last two posts in that it will be segregated into three sections. The whole things will still be wrapped in a fieldset but within that there will be a block of inputs (radio buttons specifically), a block of labels inside a div and a block of divs that we'll call tooltips as we're going to have a tooltip to show which mood we have selected.

Inputs

Inputs will be easy just a set of inputs with their type set to radio and a shared name, they will also need a unique id so our labels can reference them.

<input name="rating" value="1" type="radio" id="rating1">
Enter fullscreen mode Exit fullscreen mode

Labels

Labels will be described by their tooltip, which means we know our tooltips will need ids, I've added a title so we can see what each mood is meant to be on mouse over.

We also have a span inside the label that contains the unicode character we want to display I've chosen 5 emojis but you could use whatever you like.

<label title="Sad" aria-describedby="SadTooltip" for="rating1">
  <span aria-hidden="true" class="star">😞</span>
</label>
Enter fullscreen mode Exit fullscreen mode

Tooltips

Our tooltips are divs that contain some text to be displayed. We know we're linking them to the labels with aria-describeby so we've added a unique id.

<div class="tooltip" id="SadTooltip">😞 Sad</div>
Enter fullscreen mode Exit fullscreen mode

CSS

Most of the magic happens in the CSS, there are a few simple animations so I'll try and go over anything interesting but the full code will be at the end of the post.

Default label

Each label has some default styles, which aren't that interesting, the only only ones of note are color: transparent; which means our unicode characters will be invisible and transform: scale(0.2); which mean the label will appear tiny.

.emotion-rating .labels>label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: relative;
  line-height: 1em;
  text-align: center;
  transform: scale(0.2);
  color: transparent;
  transition: all 350ms cubic-bezier(0.36, 0.07, 0.19, 0.97);
  background-color: #F08080;
  height: 1.75em;
  width: 1.75em;
  border-radius: 1em;
  user-select: none;
}
Enter fullscreen mode Exit fullscreen mode

Label hover

When we hover over a label we increase the size a little and stop hiding the emoji. We also increase the z index just incase.

.emotion-rating .labels>label:hover {
  transform: scale(0.6);
  color: initial;
  z-index: 1;
}
Enter fullscreen mode Exit fullscreen mode

Default tooltip

We have a bunch of boring styles in here but we also have the opacity and height set to 0 meaning, by default, the tooltip won't take up any space in the dom.

.emotion-rating .tooltip {
  font-size: 0.75em;
  height: 0;
  text-align: left;
  box-sizing: border-box;
  border-radius: 15px;
  background-color: white;
  opacity: 0;
  transform: translateY(-50%);
  position: relative;
  transition-property: opacity, transform;
  transition: 0 cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
Enter fullscreen mode Exit fullscreen mode

Changes based on checked

I've added some very basic labels for the CSS below. With an approach like this, where everything is manual, you gain performance (because there is no JS to wait for) but it does mean you have to write a lot of extra code. There is a block like this for every input.

/* label for input before currently checked */
#rating3:checked~.labels>[for=rating2] {
  transform: scale(1);
  color: initial;
  z-index: 1;
}

/* emoji for input before currently checked */
#rating3:checked~.labels>[for=rating2] .star {
  opacity: 0.8;
}

/* label for input currently checked */
#rating3:checked~.labels>[for=rating3] {
  transform: scale(1.4);
  color: initial;
  z-index: 0;
}

/* label for input after currently checked */
#rating3:checked~.labels>[for=rating4] {
  transform: scale(1);
  color: initial;
  z-index: 1;
}

/* emoji for input after currently checked */
#rating3:checked~.labels>[for=rating4] .star {
  opacity: 0.8;
}

/* tooltip related to checked input */
#rating3:checked~#NeutralTooltip {
  display: block;
  opacity: 1;
  transform: translate(0);
  height: auto;
  padding: 0.4em 0.8em;
  margin-top: 0.8em;
  border: 4px solid #F08080;
  transition-duration: 350ms;
  transition-delay: 150ms;
}
Enter fullscreen mode Exit fullscreen mode

Prefers Reduced Motion

There is a lot of motion in this component and because of that add a prefers-reduced-motion media query is super important.

I've taken out all the animations times that involve moving or growing/shrinking and change the style of the labels to make a little more sense without the motion. I don't think it's worth going through this code but if you have any question leave a comment.

@media (prefers-reduced-motion) {
  .emotion-rating .tooltip {
    transform: translate(0);
  }

  .emotion-rating .labels>label {
    transform: scale(0.6) !important;
    color: initial;
    transition-duration: 0ms;
  }

  .emotion-rating .labels>label .star {
    opacity: 0.8;
  }

  #rating1:checked~.labels>[for=rating1] {
    transform: scale(1) !important;
  }

  #rating2:checked~.labels>[for=rating2] {
    transform: scale(1) !important;
  }

  #rating3:checked~.labels>[for=rating3] {
    transform: scale(1) !important;
  }

  #rating4:checked~.labels>[for=rating4] {
    transform: scale(1) !important;
  }

  #rating5:checked~.labels>[for=rating5] {
    transform: scale(1) !important;
  }
}
Enter fullscreen mode Exit fullscreen mode

The result

Well wasn't that a lot of code, almost 300 lines of CSS, but I think it shows you what is possible without needing JS and also what sort of interactions you can have without sacrificing accessibility.

Fin

Thank you all for reading, I think this was probably my last entry into the Star (rating) wars. Though do let me know if group posts exploring the same topic are useful and I'll have a chat with the other to see if we want to do this more often (no promises).

Thanks again β€οΈπŸ‘ΎπŸ§ πŸ€–πŸ‘ΎπŸ¦„πŸ€–

Oldest comments (1)

Collapse
 
grahamthedev profile image
GrahamTheDev

I love it - but we have gone well outside of star wars now! πŸ˜‹

Could just do with slightly bigger targets for inactive items, from a WCAG perspective 24px by 24px is the new AA minimum when 2.2 comes out (although I still use the 2.1 of 44px by 44px as there are lots of arguments about the massive reduction in tap target size). Might be an easy fix with a :before or :after as you aren't using them so you don't have to change the aesthetic.