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">
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的滑塊樣式 */ }
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 { /* 滑塊樣式 */ }
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 { /* 滑塊樣式 */ }
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; /* 刪除默認(rèn)瀏覽器樣式 */ }
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">
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; /* 刪除默認(rèn)瀏覽器樣式 */ }</number>
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.
<input type="range">
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
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的滑塊樣式 */ }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 { /* 滑塊樣式 */ }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 { /* 滑塊樣式 */ }We write the following content:
<input type="range">
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的滑塊樣式 */ }
We can also use conical gradients to write gradients differently:
input[type="range"][i]::-webkit-slider-thumb, input[type="range"]::-moz-range-thumb { /* 滑塊樣式 */ }
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 { /* 滑塊樣式 */ }
input[type="range"] { --s: 100px; /* 控制大小 */ height: var(--s); aspect-ratio: 5; appearance: none; /* 刪除默認(rèn)瀏覽器樣式 */ }
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">
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; /* 刪除默認(rèn)瀏覽器樣式 */ }</number>
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; /* 刪除默認(rèn)瀏覽器樣式 */ mask-image: /* ... */; mask-size: var(--s); }</number>
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:
<input type="range">
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的滑塊樣式 */ }
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 { /* 滑塊樣式 */ }
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
- CSS-only Star Rating Components and More! (Part 1)
- 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!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

CSS blocks page rendering because browsers view inline and external CSS as key resources by default, especially with imported stylesheets, header large amounts of inline CSS, and unoptimized media query styles. 1. Extract critical CSS and embed it into HTML; 2. Delay loading non-critical CSS through JavaScript; 3. Use media attributes to optimize loading such as print styles; 4. Compress and merge CSS to reduce requests. It is recommended to use tools to extract key CSS, combine rel="preload" asynchronous loading, and use media delayed loading reasonably to avoid excessive splitting and complex script control.

Autoprefixer is a tool that automatically adds vendor prefixes to CSS attributes based on the target browser scope. 1. It solves the problem of manually maintaining prefixes with errors; 2. Work through the PostCSS plug-in form, parse CSS, analyze attributes that need to be prefixed, and generate code according to configuration; 3. The usage steps include installing plug-ins, setting browserslist, and enabling them in the build process; 4. Notes include not manually adding prefixes, keeping configuration updates, prefixes not all attributes, and it is recommended to use them with the preprocessor.

Theconic-gradient()functioninCSScreatescirculargradientsthatrotatecolorstopsaroundacentralpoint.1.Itisidealforpiecharts,progressindicators,colorwheels,anddecorativebackgrounds.2.Itworksbydefiningcolorstopsatspecificangles,optionallystartingfromadefin

TocreatestickyheadersandfooterswithCSS,useposition:stickyforheaderswithtopvalueandz-index,ensuringparentcontainersdon’trestrictit.1.Forstickyheaders:setposition:sticky,top:0,z-index,andbackgroundcolor.2.Forstickyfooters,betteruseposition:fixedwithbot

The scope of CSS custom properties depends on the context of their declaration, global variables are usually defined in :root, while local variables are defined within a specific selector for componentization and isolation of styles. For example, variables defined in the .card class are only available for elements that match the class and their children. Best practices include: 1. Use: root to define global variables such as topic color; 2. Define local variables inside the component to implement encapsulation; 3. Avoid repeatedly declaring the same variable; 4. Pay attention to the coverage problems that may be caused by selector specificity. Additionally, CSS variables are case sensitive and should be defined before use to avoid errors. If the variable is undefined or the reference fails, the fallback value or default value initial will be used. Debug can be done through the browser developer

ThefrunitinCSSGriddistributesavailablespaceproportionally.1.Itworksbydividingspacebasedonthesumoffrvalues,e.g.,1fr2frgivesone-thirdandtwo-thirds.2.Itenablesflexiblelayouts,avoidsmanualcalculations,andsupportsresponsivedesign.3.Commonusesincludeequal-

Mobile-firstCSSdesignrequiressettingtheviewportmetatag,usingrelativeunits,stylingfromsmallscreensup,optimizingtypographyandtouchtargets.First,addtocontrolscaling.Second,use%,em,orreminsteadofpixelsforflexiblelayouts.Third,writebasestylesformobile,the

To create an intrinsic responsive grid layout, the core method is to use CSSGrid's repeat(auto-fit,minmax()) mode; 1. Set grid-template-columns:repeat(auto-fit,minmax(200px,1fr)) to let the browser automatically adjust the number of columns and limit the minimum and maximum widths of each column; 2. Use gap to control grid spacing; 3. The container should be set to relative units such as width:100%, and use box-sizing:border-box to avoid width calculation errors and center them with margin:auto; 4. Optionally set the row height and content alignment to improve visual consistency, such as row
