Saturday, March 1, 2025
HomeProgrammingA CSS-Solely Star Score Part And Extra! (Half 1)

A CSS-Solely Star Score Part And Extra! (Half 1)


Making a star score element is a traditional train in net improvement. It has been completed and re-done many occasions utilizing totally different methods. We often want a small quantity of JavaScript to drag it collectively, however what a few CSS-only implementation? Sure, it’s potential!

Here’s a demo of a CSS-only star score element. You possibly can click on to replace the score.

Cool, proper? Along with being CSS-only, the HTML code is nothing however a single component:

<enter kind="vary" min="1" max="5">

An enter vary component is the proper candidate right here because it permits a person to pick out a numeric worth between two boundaries (the min and max). Our aim is to model that native component and remodel it right into a star score element with out further markup or any script! We will even create extra parts on the finish, so observe alongside.

Be aware: This text will solely deal with the CSS half. Whereas I attempt my finest to contemplate UI, UX, and accessibility facets, my element isn’t excellent. It might have some drawbacks (bugs, accessibility points, and many others), so please use it with warning.

The <enter> component

You most likely realize it however styling native parts reminiscent of inputs is a bit difficult as a consequence of all of the default browser types and likewise the totally different inner constructions. If, for instance, you examine the code of an enter vary you will note a distinct HTML between Chrome (or Safari, or Edge) and Firefox.

DevTools inspecting an input element, showing the shadow root element parts.

Fortunately, we’ve got some frequent components that I’ll depend on. I’ll goal two totally different parts: the predominant component (the enter itself) and the thumb component (the one you slide together with your mouse to replace the worth).

Our CSS will primarily appear to be this:

enter[type="range"] {
  /* styling the primary component */
}

enter[type="range" i]::-webkit-slider-thumb {
  /* styling the thumb for Chrome, Safari and Edge */
}

enter[type="range"]::-moz-range-thumb {
  /* styling the thumb for Firefox */
}

The one downside is that we have to repeat the types of the thumb component twice. Don’t attempt to do the next:

enter[type="range" i]::-webkit-slider-thumb,
enter[type="range"]::-moz-range-thumb {
  /* styling the thumb */
}

This doesn’t work as a result of the entire selector is invalid. Chrome & Co. don’t perceive the ::-moz-* half and Firefox doesn’t perceive the ::-webkit-* half. For the sake of simplicity, I’ll use the next selector for this text:

enter[type="range"]::thumb {
  /* styling the thumb */
}

However the demo accommodates the true selectors with the duplicated types. Sufficient introduction, let’s begin coding!

Styling the primary component (the star form)

We begin by defining the dimensions:

enter[type="range"] {
  --s: 100px; /* management the dimensions*/
  
  top: var(--s);
  aspect-ratio: 5;
  
  look: none; /* take away the default browser types */
}

If we take into account that every star is positioned inside a sq. space, then for a 5-star score we want a width equal to 5 occasions the peak, therefore the usage of aspect-ratio: 5.

That 5 worth can also be the worth outlined because the max attribute for the enter component.

<enter kind="vary" min="1" max="5">

So, we will depend on the newly enhanced attr() operate (Chrome-only in the meanwhile) to learn that worth as an alternative of manually defining it!

enter[type="range"] {
  --s: 100px; /* management the dimensions*/
  
  top: var(--s);
  aspect-ratio: attr(max kind(<quantity>));
  
  look: none; /* take away the default browser types */
}

Now you’ll be able to management the variety of stars by merely adjusting the max attribute. That is nice as a result of the max attribute can also be utilized by the browser internally, so updating that worth will management our implementation in addition to the browser’s conduct.

This enhanced model of attr() is solely accessible in Chrome for now so all my demos will include a fallback to assist with unsupported browsers.

The subsequent step is to make use of a CSS masks to create the celebrities. We’d like the form to repeat 5 occasions (or extra relying on the max worth) so the masks dimension ought to be equal to var(--s) var(--s) or var(--s) 100% or just var(--s) since by default the peak can be equal to 100%.

enter[type="range"] {  
  --s: 100px; /* management the dimensions*/
  
  top: var(--s);
  aspect-ratio: attr(max kind(<quantity>));
  
  look: none; /* take away the default browser types */

  mask-image: /* ... */;
  mask-size: var(--s);
}

What concerning the mask-image property you may ask? I believe it’s no shock that I inform you it’ll require a number of gradients, nevertheless it is also SVG as an alternative. This text is about making a star-rating element however I want to preserve the star half type of generic so you’ll be able to simply exchange it with any form you need. That’s why I say “and extra” within the title of this submit. We are going to see later how utilizing the identical code construction we will get a wide range of totally different variations.

Here’s a demo exhibiting two totally different implementations for the star. One is utilizing gradients and the opposite is utilizing an SVG.

On this case, the SVG implementation appears cleaner and the code can also be shorter however preserve each approaches in your again pocket as a result of a gradient implementation can do a greater job in some conditions.

Styling the thumb (the chosen worth)

Let’s now deal with the thumb component. Take the final demo then click on the celebrities and see the place of the thumb.

The nice factor is that the thumb is at all times inside the space of a given star for all of the values (from min to max), however the place is totally different for every star. It could be good if the place is at all times the identical, whatever the worth. Ideally, the thumb ought to at all times be on the heart of the celebrities for consistency.

Here’s a determine for example the place and easy methods to replace it.

The strains are the place of the thumb for every worth. On the left, we’ve got the default positions the place the thumb goes from the left edge to the precise fringe of the primary component. On the precise, if we prohibit the place of the thumb to a smaller space by including some areas on the perimeters, we get a lot better alignment. That house is the same as half the dimensions of 1 star, or var(--s)/2. We are able to use padding for this:

enter[type="range"] {  
  --s: 100px; /* management the dimensions */
  
  top: var(--s);
  aspect-ratio: attr(max kind(<quantity>));
  padding-inline: calc(var(--s) / 2);
  box-sizing: border-box;
  
  look: none; /* take away the default browser types */

  mask-image: ...;
  mask-size: var(--s);
}

It’s higher however not excellent as a result of I’m not accounting for the thumb dimension, which suggests we don’t have true centering. It’s not a problem as a result of I’ll make the dimensions of the thumb very small with a width equal to 1px.

enter[type="range"]::thumb {
  width: 1px;
  top: var(--s);  

  look: none; /* take away the default browser types */ 
}

The thumb is now a skinny line positioned on the heart of the celebrities. I’m utilizing a crimson coloration to focus on the place however in actuality, I don’t want any coloration as a result of it is going to be clear.

You could suppose we’re nonetheless removed from the ultimate outcome however we’re nearly completed! One property is lacking to finish the puzzle: border-image.

The border-image property permits us to attract decorations exterior a component because of its outset characteristic. For that reason, I made the thumb small and clear. The coloration can be completed utilizing border-image. I’ll use a gradient with two strong colours because the supply:

linear-gradient(90deg, gold 50%, gray 0);

And we write the next:

border-image: linear-gradient(90deg, gold 50%, gray 0) fill 0 // 0 100px;

The above signifies that we prolong the world of the border-image from both sides of the component by 100px and the gradient will fill that space. In different phrases, every coloration of the gradient will cowl half of that space, which is 100px.

Do you see the logic? We created a type of overflowing coloration on both sides of the thumb — a coloration that may logically observe the thumb so every time you click on a star it slides into place!

Now as an alternative of 100px let’s use a really massive worth:

We’re getting shut! The coloration is filling all the celebrities however we don’t need it to be within the center however reasonably throughout your complete chosen star. For this, we replace the gradient a bit and as an alternative of utilizing 50%, we use 50% + var(--s)/2. We add an offset equal to half the width of a star which suggests the primary coloration will take more room and our star score element is ideal!

We are able to nonetheless optimize the code just a little the place as an alternative of defining a top for the thumb, we preserve it 0 and we take into account the vertical outset of border-image to unfold the coloration.

enter[type="range"]::thumb{
  width: 1px;
  border-image: 
    linear-gradient(90deg, gold calc(50% + var(--s) / 2), gray 0)
    fill 0 // var(--s) 500px;
  look: none;
}

We are able to additionally write the gradient otherwise utilizing a conic gradient as an alternative:

enter[type="range"]::thumb{
  width: 1px;
  border-image: 
    conic-gradient(at calc(50% + var(--s) / 2), gray 50%, gold 0)
    fill 0 // var(--s) 500px;
  look: none;
}

I do know that the syntax of border-image isn’t simple to know and I went a bit quick with the reason. However I’ve a really detailed article over at Smashing Journal the place I dissect that property with a whole lot of examples that I invite you to learn for a deeper dive into how the property works.

The complete code of our element is that this:

<enter kind="vary" min="1" max="5">
enter[type="range"] {  
  --s: 100px; /* management the dimensions*/
  
  top: var(--s);
  aspect-ratio: attr(max kind(<quantity>));
  padding-inline: calc(var(--s) / 2); 
  box-sizing: border-box; 
  look: none; 
  mask-image: /* ... */; /* both an SVG or gradients */
  mask-size: var(--s);
}

enter[type="range"]::thumb {
  width: 1px;
  border-image: 
    conic-gradient(at calc(50% + var(--s) / 2), gray 50%, gold 0)
    fill 0//var(--s) 500px;
  look: none;
}

That’s all! A couple of strains of CSS code and we’ve got a pleasant score star element!

Half-Star Score

What about having a granularity of half a star as a score? It’s one thing frequent and we will do it with the earlier code by making a number of changes.

First, we replace the enter component to increment in half steps as an alternative of full steps:

<enter kind="vary" min=".5" step=".5" max="5">

By default, the step is the same as 1 however we will replace it to .5 (or any worth) then we replace the min worth to .5 as nicely. On the CSS facet, we alter the padding from var(--s)/2 to var(--s)/4, and we do the identical for the offset contained in the gradient.

enter[type="range"] {  
  --s: 100px; /* management the dimensions*/
  
  top: var(--s);
  aspect-ratio: attr(max kind(<quantity>));
  padding-inline: calc(var(--s) / 4); 
  box-sizing: border-box; 
  look: none; 
  mask-image: ...; /* both SVG or gradients */
  mask-size: var(--s);
}

enter[type="range"]::thumb{
  width: 1px;
  border-image: 
    conic-gradient(at calc(50% + var(--s) / 4),gray 50%, gold 0)
    fill 0 // var(--s) 500px;
  look: none;
}

The distinction between the 2 implementations is an element of one-half which can also be the step worth. Meaning we will use attr() and create a generic code that works for each instances.

enter[type="range"] {  
  --s: 100px; /* management the dimensions*/
  
  --_s: calc(attr(step kind(<quantity>),1) * var(--s) / 2);
  top: var(--s);
  aspect-ratio: attr(max kind(<quantity>));
  padding-inline: var(--_s);
  box-sizing: border-box; 
  look: none; 
  mask-image: ...; /* both an SVG or gradients */
  mask-size: var(--s);
}

enter[type="range"]::thumb{
  width: 1px;
  border-image: 
    conic-gradient(at calc(50% + var(--_s)),gold 50%,gray 0)
    fill 0//var(--s) 500px;
  look: none;
}

Here’s a demo the place modifying the step is all that you want to do to manage the granularity. Don’t overlook you could additionally management the variety of stars utilizing the max attribute.

Utilizing the keyboard to regulate the score

As you might know, we will modify the worth of an enter vary slider utilizing a keyboard, so we will management the score utilizing the keyboard as nicely. That’s a great factor however there’s a caveat. As a consequence of the usage of the masks property, we not have the default define that signifies keyboard focus which is an accessibility concern for individuals who depend on keyboard enter.

For a greater person expertise and to make the element extra accessible, it’s good to show a top level view on focus. The best answer is so as to add an additional wrapper:

<span>
  <enter kind="vary" min="1" max="5">
</span>

That can have a top level view when the enter inside has focus:

span:has(:focus-visible) {
  define: 2px strong;
}

Attempt to use your keyboard within the beneath instance to regulate each scores:

One other concept is to contemplate a extra complicated masks configuration that retains a small space across the component seen to indicate the define:

masks: 
  /* ... */ 0/var(--s),
  conic-gradient(from 90deg at 2px 2px,#0000 25%,#000 0) 
   0 0/calc(100% - 2px) calc(100% - 2px);

I choose utilizing this final technique as a result of it maintains the single-element implementation however possibly your HTML construction means that you can add deal with an higher component and you’ll preserve the masks configuration easy. It completely relies upon!

Extra examples!

As I stated earlier, what we’re making is greater than a star score element. You possibly can simply replace the masks worth to make use of any form you need.

Right here is an instance the place I’m utilizing an SVG of a coronary heart as an alternative of a star.

Why not butterflies?

This time I’m utilizing a PNG picture as a masks. If you’re not comfy utilizing SVG or gradients you should utilize a clear picture as an alternative. So long as you might have an SVG, a PNG, or gradients, there isn’t any restrict on what you are able to do with this so far as shapes go.

We are able to go even additional into the customization and create a quantity management element like beneath:

I’m not repeating a selected form in that final instance, however am utilizing a fancy masks configuration to create a sign form.

Conclusion

We began with a star score element and ended with a bunch of cool examples. The title may have been “The right way to model an enter vary component” as a result of that is what we did. We upgraded a local element with none script or additional markup, and with just a few strains of CSS.

What about you? Can you concentrate on one other fancy element utilizing the identical code construction? Share your instance within the remark part!

Article collection

  1. A CSS-Solely Star Score Part and Extra! (Half 1)
  2. A CSS-Solely Star Score Part and Extra! (Half 2) — Coming March 7!
RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments