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 input
s (radio buttons specifically), a block of label
s inside a div
and a block of div
s 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">
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>
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>
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;
}
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;
}
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);
}
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;
}
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;
}
}
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 ❤️👾🧠🤖👾🦄🤖
Top comments (1)
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.