Home > Web Front-end > CSS Tutorial > A CSS-Only Star Rating Component and More! (Part 1)

A CSS-Only Star Rating Component and More! (Part 1)

William Shakespeare
Release: 2025-03-07 16:38:11
Original
305 people have browsed it

A CSS-Only Star Rating Component and More! (Part 1)

Building a star rating component is a classic exercise in web development and has been repeatedly implemented using a variety of techniques. Usually a small amount of JavaScript code is required, but what about implementations using CSS? Yes, yes!

This is a demonstration of the star rating component implemented using CSS only. You can click Update Rating.

It's cool, right? Besides using CSS only, HTML code is just a single element:

<input type="range">
Copy after login
Copy after login
Copy after login
Copy after login

The range input element is ideal because it allows the user to select a numeric value between two boundaries (minimum and maximum). Our goal is to design this native element, convert it into a star rating component, without additional tagging or any scripts! We will also create more components at the end, so stay tuned.

Note: This article only focuses on the CSS part. While I tried my best to think about UI, UX, and accessibility aspects, my components weren't perfect. It may have some drawbacks (errors, accessibility issues, etc.), so use with caution.

<input type="range"> Element

You may know that designing native elements, like input elements, is a bit tricky due to all the default browser styles and different internal structures. For example, if you check the code for a range input element, you will see different HTML between Chrome (or Safari, Edge) and Firefox.

Luckily, we have some parts in common that I will rely on. I'll locate two different elements: main element (input itself) and slider element (that element you use the mouse to swipe to update the value).

Our CSS is mainly as follows:

input[type="range"] {
  /* 主元素样式 */
}

input[type="range"][i]::-webkit-slider-thumb {
  /* Chrome、Safari和Edge的滑块样式 */
}

input[type="range"]::-moz-range-thumb {
  /* Firefox的滑块样式 */
}
Copy after login
Copy after login
Copy after login
Copy after login

The only downside is that we need to repeat the style of the slider element twice. Do not try to do the following:

input[type="range"][i]::-webkit-slider-thumb,
input[type="range"]::-moz-range-thumb {
  /* 滑块样式 */
}
Copy after login
Copy after login
Copy after login
Copy after login

This does not work because the entire selector is invalid. Chrome etc. do not understand the ::-moz-* part, while Firefox does not understand the ::-webkit-* part. For simplicity, in this article I will use the following selector:

input[type="range"]::thumb {
  /* 滑块样式 */
}
Copy after login
Copy after login
Copy after login

But the demo contains real selectors with repeating styles. The introduction is enough, let's start coding!

Main Element Style (Star)

We first define the size:

input[type="range"] {
  --s: 100px; /* 控制大小 */

  height: var(--s);
  aspect-ratio: 5;

  appearance: none; /* 删除默认浏览器样式 */
}
Copy after login
Copy after login

If we think each star is in a square area, for a 5-star rating, we need width equal to five times the height, so use aspect-ratio: 5.

The 5 values ​​are also defined as the value of the max attribute of the input element.

<input type="range" max="5">
Copy after login
Copy after login

So we can rely on the newly enhanced attr() function (currently Chrome only) to read the value instead of manually defining it!

input[type="range"] {
  --s: 100px; /* 控制大小 */

  height: var(--s);
  aspect-ratio: attr(max type(<number>));

  appearance: none; /* 删除默认浏览器样式 */
}</number>
Copy after login
Copy after login

Now you can control the number of stars by simply adjusting the max attribute. This is great because the max attribute is also used internally by the browser, so updating the value will control our implementation and the behavior of the browser.

This enhanced version of attr() is currently only available in Chrome, so all my demos include a fallback scheme to help unsupported browsers.

The next step is to use a CSS mask to create stars. We need the shape to be repeated five times (or more times, depending on the max value), so the mask size should be equal to var(--s) var(--s) or var(--s) 100% or simplified to var(--s) because the height will be equal to 100% by default.

&lt;input type=&quot;range&quot;&gt;
Copy after login
Copy after login
Copy after login
Copy after login

You may ask what is the attribute? I think it's not surprising that I'll tell you it needs some gradient, but it can be an SVG as well. This article is about creating a star rating component, but I want the star part to remain versatile so you can easily replace it with any shape you want. That's why I said "more" in the title of this post. We will see later how to get various different variants using the same code structure. mask-image

Here is a demonstration showing two different ways to implement star shapes. One is to use gradients, the other is to use SVG.

In this case, the SVG implementation looks cleaner and the code is shorter, but remember both methods, because gradient implementations can sometimes do better.

Slider style (selected value)

Let's focus now on the slider element. Use the last demo, then click on the stars and notice the position of the slider.

The good news is that for all values ​​(from minimum to maximum), the slider is always within the region of a given star, but each star has a different position. It would be even better if the position is always the same regardless of the value. Ideally, for consistency, the slider should always be in the center of the star.

This is a picture that illustrates the location and how to update it.

These lines are the slider positions for each value. On the left we have the default position where the slider moves from the left edge of the main element to the right edge. On the right, if we limit the slider position to the smaller area by adding some space on both sides, we will get better alignment. This space is equal to half the size of a star, or

. We can use padding for this: var(--s)/2

input[type="range"] {
  /* 主元素样式 */
}

input[type="range"][i]::-webkit-slider-thumb {
  /* Chrome、Safari和Edge的滑块样式 */
}

input[type="range"]::-moz-range-thumb {
  /* Firefox的滑块样式 */
}
Copy after login
Copy after login
Copy after login
Copy after login
It's better, but not perfect, because I didn't think about the slider size, which means we're not really centering. This is not a problem, as I will use a width equal to 1px to make the slider's size very small.

input[type="range"][i]::-webkit-slider-thumb,
input[type="range"]::-moz-range-thumb {
  /* 滑块样式 */
}
Copy after login
Copy after login
Copy after login
Copy after login
The slider is now a thin line, located in the center of the star. I use red to highlight the position, but in reality I don't need any color as it will be transparent.

You may think we are still far from the end result, but we are almost done! A property is missing to complete this puzzle:

. border-image The

attribute allows us to draw decorations outside the element using its border-image function. To do this, I make the slider smaller and transparent. The coloring will be done using outset. I'm going to use a gradient with two solid colors as the source: border-image

input[type="range"]::thumb {
  /* 滑块样式 */
}
Copy after login
Copy after login
Copy after login
We write the following content:

&lt;input type=&quot;range&quot;&gt;
Copy after login
Copy after login
Copy after login
Copy after login

The above means that we expand the area of ​​border-image by 100px from each side of the element, and the gradient will fill that area. In other words, each color of the gradient will cover half of the area, i.e. 100px.

Do you understand the logic? We created an overflowing shading on each side of the slider – a shading that will logically follow the slider so that every time a star is clicked it slides into place!

Now let's use a very large value instead of 100px:

We are almost done! The coloring fills all the stars, but we don't want it to be in the middle, but on the entire selected star. To do this, we update the gradient a little, instead of using 50%, we use 50% var(--s)/2. We add an offset equal to half the width of the star, which means the first color will take up more space and our star rating component is perfect!

We can still optimize the code slightly instead of defining the height for the slider, we keep it at 0 and consider the vertical border-image of outset to extend the shading.

input[type="range"] {
  /* 主元素样式 */
}

input[type="range"][i]::-webkit-slider-thumb {
  /* Chrome、Safari和Edge的滑块样式 */
}

input[type="range"]::-moz-range-thumb {
  /* Firefox的滑块样式 */
}
Copy after login
Copy after login
Copy after login
Copy after login

We can also use conical gradients to write gradients differently:

input[type="range"][i]::-webkit-slider-thumb,
input[type="range"]::-moz-range-thumb {
  /* 滑块样式 */
}
Copy after login
Copy after login
Copy after login
Copy after login

I know the syntax of border-image is not easy to understand, and I am a little quick to explain. But I have a very detailed article published on Smashing Magazine where I dissect this property with many examples and I invite you to read it for a deeper understanding of how it works.

The complete code of our component is as follows:

input[type="range"]::thumb {
  /* 滑块样式 */
}
Copy after login
Copy after login
Copy after login
input[type="range"] {
  --s: 100px; /* 控制大小 */

  height: var(--s);
  aspect-ratio: 5;

  appearance: none; /* 删除默认浏览器样式 */
}
Copy after login
Copy after login

That's it! With a few lines of CSS code, we get a nice star rating component!

Semi-star rating

How to set the score granularity to half-star? This is very common, and we can complete the previous code by making some tweaks.

First, we update the input element to increment in half step instead of full step:

<input type="range" max="5">
Copy after login
Copy after login

By default, the step size is equal to 1, but we can update it to .5 (or any value) and then also update the minimum value to .5. On the CSS side, we change the padding from var(--s)/2 to var(--s)/4 and do the same for offsets in the gradient.

input[type="range"] {
  --s: 100px; /* 控制大小 */

  height: var(--s);
  aspect-ratio: attr(max type(<number>));

  appearance: none; /* 删除默认浏览器样式 */
}</number>
Copy after login
Copy after login

The difference between the two implementations is a one-half factor, which is also the step size value. This means we can use attr() and create common code that works for both cases.

input[type="range"] {
  --s: 100px; /* 控制大小 */

  height: var(--s);
  aspect-ratio: attr(max type(<number>));

  appearance: none; /* 删除默认浏览器样式 */

  mask-image: /* ... */;
  mask-size: var(--s);
}</number>
Copy after login

This is a demo where modifying the step size is everything you need to do to control the granularity. Don't forget that you can also use the max attribute to control the number of stars.

Use the keyboard to adjust the score

As you know, we can use the keyboard to adjust the value entered by the range slider, so we can also use the keyboard to control the score. This is a good thing, but there is a warning. Due to the use of the mask property, we no longer have a default outline indicating the focus of the keyboard, which is an accessibility issue for users who rely on keyboard input.

For a better user experience and make the components more accessible, it is best to display the outline in focus. The easiest solution is to add an extra wrapper:

&lt;input type=&quot;range&quot;&gt;
Copy after login
Copy after login
Copy after login
Copy after login

This will have an outline when the input inside has focus:

input[type="range"] {
  /* 主元素样式 */
}

input[type="range"][i]::-webkit-slider-thumb {
  /* Chrome、Safari和Edge的滑块样式 */
}

input[type="range"]::-moz-range-thumb {
  /* Firefox的滑块样式 */
}
Copy after login
Copy after login
Copy after login
Copy after login

Try to use the keyboard to adjust these two ratings in the following example:

Another idea is to consider a more complex mask configuration that keeps small areas around the element visible to show the outline:

input[type="range"][i]::-webkit-slider-thumb,
input[type="range"]::-moz-range-thumb {
  /* 滑块样式 */
}
Copy after login
Copy after login
Copy after login
Copy after login

I prefer to use the last method because it keeps the single element implementation, but maybe your HTML structure allows you to add focus on the parent element, and you can keep the mask configuration simple. It's all up to you!

More examples!

As I said before, we made more than just a star rating component. You can easily update the mask value to use any shape you want.

Here is an example, I am using the SVG of the heart instead of the star.

Why isn't it a butterfly?

This time I use PNG images as mask. If you are not used to using SVG or gradients, you can use transparent images. As long as you have SVG, PNG, or gradients, you can do anything in shape with this method.

We can further customize and create a volume control component like the following:

In the last example, instead of repeating the specific shape, I used a complex mask configuration to create the signal shape.

Conclusion

We started with a star rating component and ended up with a lot of cool examples. The title could have been “How to design range input elements” because that’s what we do. We upgraded a native component without any scripts or extra tags and used only a few lines of CSS code.

Where are you? Can you think of another beautiful component that uses the same code structure? Share your examples in the comment section!

Article Series

  1. CSS-only Star Rating Components and More! (Part 1)
  2. CSS-only Star Rating Components and More! (Part 2) - Released on March 7!

The above is the detailed content of A CSS-Only Star Rating Component and More! (Part 1). For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template