DEV Community

loading...
Cover image for Scalable "star rating" without JS (and no SVG or image for the star)

Scalable "star rating" without JS (and no SVG or image for the star)

Temani Afif
Another Dev guy who love hacking with CSS
Updated on ・4 min read

Here I am with my "Star rating" system:

✔️ No JavaScript.
✔️ No complex HTML code, only the needed <input> elements for the user interaction and one extra element.
✔️ No hacky CSS code.
✔️ Easily scalable. Simply add more <input> to get more stars. No need to change any CSS code.
✔️ Works with keyboard navigation.
✔️ No SVG, No images. The star shape is built using pure CSS.
✔️ You can easily adjust the size and coloration of the stars.
✔️ Support both ltr and rtl direction

See it in play:

As said, the HTML code is pretty simple. A div that contain our inputs and an extra <i> element. Nothing more!

All you have to do is to add as many inputs as stars you want.

For the CSS part, we have the trivial one as follow:

.stars {
  --s:50px;
  position:relative;
  display:inline-flex;
}
.stars input {
  width:var(--s);
  height:var(--s);
  margin:0;
  opacity:0;
  cursor:pointer;
}
Enter fullscreen mode Exit fullscreen mode

The variable --s will define the size of our inputs that we make invisible using opacity:0. Everything is within a flexbox container (an inline one to easily integrate the star rating like an image or a simple text).

The real trick rely on i

.stars i {
  position:absolute;
  inset:0 0 calc(var(--s)*0.1);
  pointer-events:none;
  /* the star */
  --v1:transparent,#000 0.5deg 108deg,#0000 109deg;
  --v2:transparent,#000 0.5deg  36deg,#0000  37deg;
  -webkit-mask:
    conic-gradient(from 54deg  at calc(var(--s)*0.68) calc(var(--s)*0.57),var(--v1)),
    conic-gradient(from 90deg  at calc(var(--s)*0.02) calc(var(--s)*0.35),var(--v2)),
    conic-gradient(from 126deg at calc(var(--s)*0.5)  calc(var(--s)*0.7) ,var(--v1)),
    conic-gradient(from 162deg at calc(var(--s)*0.5)  0                  ,var(--v2));
  -webkit-mask-size: var(--s) var(--s);
  -webkit-mask-composite: xor,destination-over;
  mask-composite: exclude,add;
  /**/
  background:
    linear-gradient(rgba(255,0,0,var(--o,0.3)) 0 0),  
    linear-gradient(gold                       0 0)
    #ccc;
  background-size:
     calc(var(--l,0)*var(--s)) 100%,
     calc(var(--p,0)*var(--s)) 100%;
  background-repeat:no-repeat;
}
Enter fullscreen mode Exit fullscreen mode

First, we make it an absolute element that will cover all the div and logically all the inputs. We use pointer-events:none; to be able to interact with the inputs but still have the mouse cursor on the <i>.

Second, we apply 3 background layers as follow:

  1. The bottom layer is a grey coloration (#ccc) to indicate the number of stars and the non-selected ones
  2. The middle layer is the gold coloration. Here we use a gradient having a variable size based on the selected stars (controlled with the variable --p)
  3. The top layer is similar to (2) and will respond to the :hover effect (controlled with the variable --l). I will be using a semi transparent color so we can still see the selected stars.

What about all those strange gradients and mask??

This is my personal touch and the crazy part of the work. I have built the star shape using multiple gradients applied to mask so all the background layers are seen through that shape.

To understand how it works refer to my previous post:

The only difference with the above is that I am applying the gradient to a mask. I will be writing another post to talk about mask in detail. Not suitable to do it here.

Finally, the interactive part done using the following code:

.stars:focus-within {
  outline:1px solid;
}
input:active ~ i{--o:1}
input:nth-of-type(N):checked ~ i {--p:N} 
input:nth-of-type(N):hover ~ i {--l:N}
Enter fullscreen mode Exit fullscreen mode

:focus-within will allow me to style the whole div when interacting with the inputs (good for accessibility)

When an input is active (clicked on) I change the semi-transparent color to an opaque one to highlight the click action.

On :checked I update the variable --p based on the input index. We can easily generate the code using SASS/LESS or by doing some copy/past (it only take few seconds to write the code that can cover up to 20 inputs)

On :hover we do the same logic but with the variable --l.

What about the rtl support?

Either we update the background-position based on the direction attribute and we simply add:

[dir="rtl"] .stars i {
    background-position: right;
}
Enter fullscreen mode Exit fullscreen mode

Or I update the code and instead of multiple backgrounds, I rely on pseudo elements that I can easily place using margin-inline-end

The trick is to have both pseudo element above each other (thanks to grid-area:1/1) with the adequate color. Their width will be controlled with the same variables used to control the gradient. Finally by using margin-inline-end:auto; they will get placed either at the left or the right based on the direction.


That's it

A simple non-hacky code and we have a fully interactive "Star rating" that you can easily embed anywhere.


Bonus

If you don't need the interactive part, here a one-div version that you can control using CSS variables:

Discussion (33)

Collapse
madsstoumann profile image
Mads Stoumann

While the gradient-stuff is super-cool, the star could also be done with a simple polygon:

clip-path: polygon(0% 0%, 100% 0%, 100% 75%, 75% 75%, 75% 100%, 50% 75%, 0% 75%);
Enter fullscreen mode Exit fullscreen mode

Then the background can be anything you want.

Collapse
afif profile image
Temani Afif Author

but you cannot repeat a clip-path ;) that's why I am using it as mask. It's one star repeated.

Collapse
madsstoumann profile image
Mads Stoumann • Edited

Ah, yes. I thought you had an <i> after each <input>. What about rtl-support? 😁

Thread Thread
afif profile image
Temani Afif Author

The lazy solution would be to use

[dir="rtl"] .stars i {
    background-position: right;
}
Enter fullscreen mode Exit fullscreen mode

Or I can update the code like this: codepen.io/t_afif/pen/eYWJNBd

Thread Thread
afif profile image
Temani Afif Author • Edited

updated the post to include this. I was writing this fast yesterday and forgot to work on it.

Thread Thread
madsstoumann profile image
Mads Stoumann

Cool!

Collapse
afif profile image
Temani Afif Author

@inhuofficial think twice before saying my code is not accessible !

Collapse
inhuofficial profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
InHuOfficial

Oh dear, I feel strange....urggh no, no not again.

{Angry Rants man] - time for another fucking rant you fuckers, they were meant to be gone but fuck it you have done too much damage. @afif , @madsstoumann prepare for war!

Collapse
madsstoumann profile image
Mads Stoumann

I’m a pacifist 🌱

Thread Thread
inhuofficial profile image
InHuOfficial

Yeah - are you a pacifist like Gandhi in Side Meir's Civilization? Be nice to you and suddenly you just drop a Nuke due to a bug in your code?

Gandhi meme where an overflow bug meant Gandhi would drop a nuke on you if you were too nice to him in Sid Meir's Civilization

Thread Thread
madsstoumann profile image
Mads Stoumann

😂

Collapse
afif profile image
Temani Afif Author

A war we are gonna win!

Collapse
lapstjup profile image
Lakshya Thakur

what have i done

Collapse
afif profile image
Temani Afif Author

you opened the pandora's box ...

Collapse
evelinchamp profile image
EvelinCHamp

These were some really great and easy to follow instructions. I easily followed through it and saw the results. Thank you so much for these. I will be keeping these in mind and use them whenever a coding situation calls for it. It looks good.

Collapse
hasnaindev profile image
Muhammad Hasnain

Dude, please, write a comprehensive guide on, "How to be great at CSS? (like Temani Afif)."

Collapse
afif profile image
Temani Afif Author

haha! that guide will contain one word "practise and parctise and practise" ;) There is no magic in become better.
Also read what Temani Afif is writing 😇

Collapse
hasnaindev profile image
Muhammad Hasnain

I've been a JavaScript instructor and still teach and mentor juniors/interns. It's funny how these advices are actually simple and disappointing as in, there never are any "magic bullets". Thanks!

However, do you recommend any resources? I got a membership in FrontendMentors, I'll be creating the websites they've listed there. I also know of Dribbble but I'd go for it after I'm done with FrontendMentors.

Thread Thread
afif profile image
Temani Afif Author

personally I am not a huge fan of never-ending online courses with their months of tutorial. The best way for me to learn is one trick a day. If each day you spend 10 min reading one or two articles about a new trick, it's fairly enough. Then, you can spend 1h trying that trick on your own to create something with it. That's it!

Building complex website to learn will waste you a lot of time and you will have to deal with a lot of things at the same time and it's hard to keep everything in mind. You may learn some methodologies but the road to become better will be very long and you will get tired rapidly.

find some good folks on twitter that you follow. read articles on DEV here, take a look at css-tricks.com/ and smashingmagazine.com/category/css/. try some of my challenges here: css-challenges.com/, etc

If you manage to learn something new each day and practice at least 1h a day, you will become better.

Thread Thread
hasnaindev profile image
Muhammad Hasnain

This is gold :) Thanks a lot Temani!

Collapse
link2twenty profile image
Andrew Bone

It's never ending 😅

Collapse
aryaziai profile image
aryaziai

Always pushing boundaries. I love this!

Collapse
qm3ster profile image
Mihail Malo

What did you mean when you said?:

  • No hacky CSS code.
Collapse
afif profile image
Temani Afif Author

You know that kind of CSS code where users add the inputs and then try to hide them out of the screen using position:absolute; and small sizes, etc.
Also all the manual CSS code written to cover all the cases where we are obliged to set very specific values to get the intended result which make the code not scalable and each time you need to update something you have to redo a lot of code.

This is what I call hacky and there is nothing of this in my code. My code scale easily, there is no hard-coded value, I am not trying to make the input out of sight, you don't need to touch the CSS code if you want to add more stars or to change the size, etc

Collapse
qm3ster profile image
Mihail Malo

Ah, so no dirty filth, basically.
Ok, that makes sense.

Collapse
afif profile image
Temani Afif Author

Don't judge me on this post. It was a fast one written a very hot Sunday. I cannot stay without competing with the actual "Star rating" posts 😛

Collapse
guscarpim profile image
Gustavo Scarpim

Good Job!

Collapse
dylandelobel profile image
Dylan Delobel

That sick!

Collapse
virejdasani profile image
Virej Dasani

This looks great, might I suggest, the black outline, when clicking the stars, doesn't need to be there

Collapse
afif profile image
Temani Afif Author

you can easily remove it but it's useful for accessibility, when you navigate using keyboard you can easily see where you are. People tend to always remove the outline from buttons and other elements but they are important.

Collapse
siddharthshyniben profile image
Siddharth
Collapse
donalfonsnisnoni profile image
Don Alfons Nisnoni

Great idea! But is that accessible for screen readers?

Collapse
zulvkr profile image
zulvkr

Amazing!