Archive

Author Archive

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

February 28th, 2025 No comments
DevTools inspecting an input element, showing the shadow root element parts.

Creating a star rating component is a classic exercise in web development. It has been done and re-done many times using different techniques. We usually need a small amount of JavaScript to pull it together, but what about a CSS-only implementation? Yes, it is possible!

Here is a demo of a CSS-only star rating component. You can click to update the rating.

CodePen Embed Fallback

Cool, right? In addition to being CSS-only, the HTML code is nothing but a single element:

<input type="range" min="1" max="5">

An input range element is the perfect candidate here since it allows a user to select a numeric value between two boundaries (the min and max). Our goal is to style that native element and transform it into a star rating component without additional markup or any script! We will also create more components at the end, so follow along.

Note: This article will only focus on the CSS part. While I try my best to consider UI, UX, and accessibility aspects, my component is not perfect. It may have some drawbacks (bugs, accessibility issues, etc), so please use it with caution.

The element

You probably know it but styling native elements such as inputs is a bit tricky due to all the default browser styles and also the different internal structures. If, for example, you inspect the code of an input range you will see a different HTML between Chrome (or Safari, or Edge) and Firefox.

Luckily, we have some common parts that I will rely on. I will target two different elements: the main element (the input itself) and the thumb element (the one you slide with your mouse to update the value).

Our CSS will mainly look like this:

input[type="range"] {
  /* styling the main element */
}

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

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

The only drawback is that we need to repeat the styles of the thumb element twice. Don’t try to do the following:

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

This doesn’t work because the whole selector is invalid. Chrome & Co. don’t understand the ::-moz-* part and Firefox doesn’t understand the ::-webkit-* part. For the sake of simplicity, I will use the following selector for this article:

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

But the demo contains the real selectors with the duplicated styles. Enough introduction, let’s start coding!

Styling the main element (the star shape)

We start by defining the size:

input[type="range"] {
  --s: 100px; /* control the size*/
  
  height: var(--s);
  aspect-ratio: 5;
  
  appearance: none; /* remove the default browser styles */
}

If we consider that each star is placed within a square area, then for a 5-star rating we need a width equal to five times the height, hence the use of aspect-ratio: 5.

CodePen Embed Fallback

That 5 value is also the value defined as the max attribute for the input element.

<input type="range" min="1" max="5">

So, we can rely on the newly enhanced attr() function (Chrome-only at the moment) to read that value instead of manually defining it!

input[type="range"] {
  --s: 100px; /* control the size*/
  
  height: var(--s);
  aspect-ratio: attr(max type(<number>));
  
  appearance: none; /* remove the default browser styles */
}

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

This enhanced version of attr() is only available in Chrome for now so all my demos will contain a fallback to help with unsupported browsers.

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

input[type="range"] {  
  --s: 100px; /* control the size*/
  
  height: var(--s);
  aspect-ratio: attr(max type(<number>));
  
  appearance: none; /* remove the default browser styles */

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

What about the mask-image property you might ask? I think it’s no surprise that I tell you it will require a few gradients, but it could also be SVG instead. This article is about creating a star-rating component but I would like to keep the star part kind of generic so you can easily replace it with any shape you want. That’s why I say “and more” in the title of this post. We will see later how using the same code structure we can get a variety of different variations.

Here is a demo showing two different implementations for the star. One is using gradients and the other is using an SVG.

CodePen Embed Fallback

In this case, the SVG implementation looks cleaner and the code is also shorter but keep both approaches in your back pocket because a gradient implementation can do a better job in some situations.

Styling the thumb (the selected value)

Let’s now focus on the thumb element. Take the last demo then click the stars and notice the position of the thumb.

CodePen Embed Fallback

The good thing is that the thumb is always within the area of a given star for all the values (from min to max), but the position is different for each star. It would be good if the position is always the same, regardless of the value. Ideally, the thumb should always be at the center of the stars for consistency.

Here is a figure to illustrate the position and how to update it.

The lines are the position of the thumb for each value. On the left, we have the default positions where the thumb goes from the left edge to the right edge of the main element. On the right, if we restrict the position of the thumb to a smaller area by adding some spaces on the sides, we get much better alignment. That space is equal to half the size of one star, or var(--s)/2. We can use padding for this:

input[type="range"] {  
  --s: 100px; /* control the size */
  
  height: var(--s);
  aspect-ratio: attr(max type(<number>));
  padding-inline: calc(var(--s) / 2);
  box-sizing: border-box;
  
  appearance: none; /* remove the default browser styles */

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

It’s better but not perfect because I am not accounting for the thumb size, which means we don’t have true centering. It’s not an issue because I will make the size of the thumb very small with a width equal to 1px.

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

  appearance: none; /* remove the default browser styles */ 
}
CodePen Embed Fallback

The thumb is now a thin line placed at the center of the stars. I am using a red color to highlight the position but in reality, I don’t need any color because it will be transparent.

You may think we are still far from the final result but we are almost done! One property is missing to complete the puzzle: border-image.

The border-image property allows us to draw decorations outside an element thanks to its outset feature. For this reason, I made the thumb small and transparent. The coloration will be done using border-image. I will use a gradient with two solid colors as the source:

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

And we write the following:

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

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

CodePen Embed Fallback

Do you see the logic? We created a kind of overflowing coloration on each side of the thumb — a coloration that will logically follow the thumb so each time you click a star it slides into place!

Now instead of 100px let’s use a very big value:

CodePen Embed Fallback

We are getting close! The coloration is filling all the stars but we don’t want it to be in the middle but rather across the entire selected star. For this, we update the gradient a bit and instead of using 50%, we use 50% + var(--s)/2. We add an offset equal to half the width of a star which means the first color will take more space and our star rating component is perfect!

CodePen Embed Fallback

We can still optimize the code a little where instead of defining a height for the thumb, we keep it 0 and we consider the vertical outset of border-image to spread the coloration.

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

We can also write the gradient differently using a conic gradient instead:

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

I know that the syntax of border-image is not easy to grasp and I went a bit fast with the explanation. But I have a very detailed article over at Smashing Magazine where I dissect that property with a lot of examples that I invite you to read for a deeper dive into how the property works.

The full code of our component is this:

<input type="range" min="1" max="5">
input[type="range"] {  
  --s: 100px; /* control the size*/
  
  height: var(--s);
  aspect-ratio: attr(max type(<number>));
  padding-inline: calc(var(--s) / 2); 
  box-sizing: border-box; 
  appearance: none; 
  mask-image: /* ... */; /* either an SVG or gradients */
  mask-size: var(--s);
}

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

That’s all! A few lines of CSS code and we have a nice rating star component!

Half-Star Rating

What about having a granularity of half a star as a rating? It’s something common and we can do it with the previous code by making a few adjustments.

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

<input type="range" min=".5" step=".5" max="5">

By default, the step is equal to 1 but we can update it to .5 (or any value) then we update the min value to .5 as well. On the CSS side, we change the padding from var(--s)/2 to var(--s)/4, and we do the same for the offset inside the gradient.

input[type="range"] {  
  --s: 100px; /* control the size*/
  
  height: var(--s);
  aspect-ratio: attr(max type(<number>));
  padding-inline: calc(var(--s) / 4); 
  box-sizing: border-box; 
  appearance: none; 
  mask-image: ...; /* either SVG or gradients */
  mask-size: var(--s);
}

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

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

input[type="range"] {  
  --s: 100px; /* control the size*/
  
  --_s: calc(attr(step type(<number>),1) * var(--s) / 2);
  height: var(--s);
  aspect-ratio: attr(max type(<number>));
  padding-inline: var(--_s);
  box-sizing: border-box; 
  appearance: none; 
  mask-image: ...; /* either an SVG or gradients */
  mask-size: var(--s);
}

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

Here is a demo where modifying the step is all that you need to do to control the granularity. Don’t forget that you can also control the number of stars using the max attribute.

CodePen Embed Fallback

Using the keyboard to adjust the rating

As you may know, we can adjust the value of an input range slider using a keyboard, so we can control the rating using the keyboard as well. That’s a good thing but there is a caveat. Due to the use of the mask property, we no longer have the default outline that indicates keyboard focus which is an accessibility concern for those who rely on keyboard input.

For a better user experience and to make the component more accessible, it’s good to display an outline on focus. The easiest solution is to add an extra wrapper:

<span>
  <input type="range" min="1" max="5">
</span>

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

span:has(:focus-visible) {
  outline: 2px solid;
}

Try to use your keyboard in the below example to adjust both ratings:

CodePen Embed Fallback

Another idea is to consider a more complex mask configuration that prevents hiding the outline (or any outside decoration). The trick is to start with the following:

mask:
  conic-gradient(#000 0 0) exclude,
  conic-gradient(#000 0 0) no-clip;

The no-clip keyword means that nothing from the element will be clipped (including outlines). Then we use an exclude composition with another gradient. The exclusion will hide everything inside the element while keeping what is outside visible.

Finally, we add back the mask that creates the star shapes:

mask: 
  /* ... */ 0/var(--s), 
  conic-gradient(#000 0 0) exclude,
  conic-gradient(#000 0 0) no-clip;

I prefer using this last method because it maintains the single-element implementation but maybe your HTML structure allows you to add focus on an upper element and you can keep the mask configuration simple. It totally depends!

CodePen Embed Fallback

Credits to Ana Tudor for the last trick!

More examples!

As I said earlier, what we are making is more than a star rating component. You can easily update the mask value to use any shape you want.

Here is an example where I am using an SVG of a heart instead of a star.

CodePen Embed Fallback

Why not butterflies?

CodePen Embed Fallback

This time I am using a PNG image as a mask. If you are not comfortable using SVG or gradients you can use a transparent image instead. As long as you have an SVG, a PNG, or gradients, there is no limit on what you can do with this as far as shapes go.

We can go even further into the customization and create a volume control component like below:

CodePen Embed Fallback

I am not repeating a specific shape in that last example, but am using a complex mask configuration to create a signal shape.

Conclusion

We started with a star rating component and ended with a bunch of cool examples. The title could have been “How to style an input range element” because this is what we did. We upgraded a native component without any script or extra markup, and with only a few lines of CSS.

What about you? Can you think about another fancy component using the same code structure? Share your example in the comment section!

Article series

  1. A CSS-Only Star Rating Component and More! (Part 1)
  2. A CSS-Only Star Rating Component and More! (Part 2) — Coming March 7!

A CSS-Only Star Rating Component and More! (Part 1) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

Compilation: 15 Hilarious Google Flops

February 28th, 2025 No comments

Even tech giants like Google stumble, with flops like Google+ and Google Glass proving that not every idea sticks. Often caused by overestimating demand or poor execution, these failures show that even the boldest innovators sometimes miss the mark.

Categories: Designing, Others Tags:

Goodbye, Blue Links—Hello, Rainbow Revolution!

February 26th, 2025 No comments

The classic blue hyperlink is fading as designers adopt colorful, creative alternatives. While stylish, these modern links can sometimes confuse users, leaving many nostalgic for the simplicity of the original blue.

Categories: Designing, Others Tags:

The Figma Dilemma: Too Many Cooks, Too Few Decisions

February 24th, 2025 No comments

Collaboration tools like Figma promise streamlined workflows and collective creativity, but there’s a darker side: too many opinions, endless edits, and a loss of individual vision. In this piece, we explore whether Figma’s collaborative power might actually hinder great design by inviting too many cooks into the kitchen.

Categories: Designing, Others Tags:

Applying the Web Dev Mindset to Dealing With Life Challenges

February 24th, 2025 No comments

Editor’s note: This article is outside the typical range of topics we normally cover around here and touches on sensitive topics including recollections from an abusive marriage. It doesn’t delve into much detail about the abuse and ends on a positive note. Thanks to Lee for sharing his take on the intersection between life and web development and for allowing us to gain professional insights from his personal life.

When my dad was alive, he used to say that work and home life should exist in separate “watertight compartments.” I shouldn’t bring work home or my home life to work. There’s the quote misattributed to Mark Twain about a dad seeming to magically grow from a fool to a wise man in the few years it took the son to grow from a teen to an adult — but in my case, the older I get, the more I question my dad’s advice.

It’s easy to romanticize someone in death — but when my dad wasn’t busy yelling, gambling the rent money, or disappearing to another state, his presence was like an AI simulating a father, throwing around words that sounded like a thing to say from a dad, but not helpful if you stopped to think about his statements for more than a minute.

Let’s state the obvious: you shouldn’t do your personal life at work or work too much overtime when your family needs you. But you don’t need the watertight compartments metaphor to understand that. The way he said it hinted at something more complicated and awful — it was as though he wanted me to have a split personality. I shouldn’t be a developer at home, especially around him because he couldn’t relate, since I got my programming genes from my mum. And he didn’t think I should pour too much of myself into my dev work. The grain of truth was that even if you love your job, it can’t love you back. Yet what I’m hooked on isn’t one job, but the power of code and language.

The lonely coder seems to free his mind at night

Maybe my dad’s platitudinous advice to maintain a distance between my identity and my work would be practicable to a bricklayer or a president — but it’s poorly suited to someone whose brain is wired for web development. The job is so multidisciplinary it defies being put in a box you can leave at the office. That puzzle at work only makes sense because of a comment the person you love said before bedtime about the usability of that mobile game they play. It turns out the app is a competitor to the next company you join, as though the narrator of your life planted the earlier scene like a Chekov’s gun plot point, the relevance of which is revealed when you have that “a-ha” moment at work.

Meanwhile, existence is so online that as you try to unwind, you can’t unsee the matrix you helped create, even when it’s well past 5 p.m. The user interface you are building wants you to be a psychologist, an artist, and a scientist. It demands the best of every part of you. The answer about implementing a complex user flow elegantly may only come to you in a dream.

Don’t feel too bad if it’s the wrong answer. Douglas Crockford believes it’s a miracle we can code at all. He postulates that the mystery of how the human brain can program when he sees no evolutionary basis is why we haven’t hit the singularity. If we understood how our brains create software, we could build an AI that can program well enough to make a program better than itself. It could do that recursively till we have an AI smarter than us.

And yet so far the best we have is the likes of the aptly named Github Copilot. The branding captures that we haven’t hit the singularity so much as a duality, in which humanity hopefully harmonizes with what Noam Chomsky calls a “kind of super-autocomplete,” the same way autotune used right can make a good singer sound better, or it can make us all sound like the same robot. We can barely get our code working even now that we have all evolved into AI-augmented cyborgs, but we also can’t seem to switch off our dev mindset at will.

My dev brain has no “off” switch — is that a bug or a feature?

What if the ability to program represents a different category of intelligence than we can measure with IQ tests, similar to neurodivergence, which carries unique strengths and weaknesses? I once read a study in which the researchers devised a test that appeared to accurately predict which first-year computer science students would be able to learn to program. They concluded that an aptitude for programming correlates with a “comfort with meaninglessness.” The researchers said that to write a program you have to “accept that whatever you might want the program to mean, the machine will blindly follow its meaningless rules and come to some meaningless conclusion. In the test, the consistent group showed a pre-acceptance of this fact.”

The realization is dangerous, as both George Orwell and Philip K. Dick warned us. If you can control what words mean, you can control people and not just machines. If you have been swiping on Tinder and take a moment to sit with the feelings you associate with the phrases “swipe right” and “swipe left,” you find your emotional responses reveal that the app’s visual language has taught you what is good and what is bad. This recalls the scene in “Through the Looking-Glass,” in which Humpty Dumpty tells Alice that words mean what he wants them to mean. Humpty’s not the nicest dude. The Alice books can be interpreted as Dodgson’s critique of the Victorian education system which the author thought robbed children of their imagination, and Humpty makes his comments about language in a “scornful tone,” as though Alice should not only accept what he says, but she should know it without being told. To use a term that itself means different things to different people, Humpty is gaslighting Alice. At least he’s more transparent about it than modern gaslighters, and there’s a funny xkcd in which Alice uses Humpty’s logic against him to take all his possessions.

Perhaps the ability to shape reality by modifying the consensus on what words mean isn’t inherently good or bad, but in itself “meaningless,” just something that is true. It’s probably not a coincidence the person who coined the phrases “the map is not the territory” and “the word is not the thing” was an engineer. What we do with this knowledge depends on our moral compass, much like someone with a penchant for cutting people up could choose to be a surgeon or a serial killer.

Toxic humans are like blackhat hackers

For around seven years, I was with a person who was psychologically and physically abusive. Abuse boils down to violating boundaries to gain control. As awful as that was, I do not think the person was irrational. There is a natural appeal for human beings pushing boundaries to get what they want. Kids do that naturally, for example, and pushing boundaries by making CSS do things it doesn’t want to is the premise of my articles on CSS-Tricks. I try to create something positive with my impulse to exploit the rules, which I hope makes the world slightly more illuminated. However, to understand those who would do us harm, we must first accept that their core motivation meets a relatable human need, albeit in unacceptable ways.

For instance, more than a decade ago, the former hosting provider for CSS-Tricks was hacked. Chris Coyier received a reactivation notice for his domain name indicating the primary email for his account had changed to someone else’s email address. After this was resolved and the smoke cleared, Chris interviewed the hacker to understand how social engineering was used for the attack — but he also wanted to understand the hacker’s motivations. “Earl Drudge” (ananagram for “drug dealer”) explained that it was nothing personal that led him to target Chris — but Earl does things for“money and attention” and Chris reflected that “as different as the ways that we choose to spend our time are I do things for money and attention also, which makes us not entirely different at our core.”

It reminds me of the trope that cops and criminals share many personality traits. Everyone who works in technology shares the mindset that allows me to bend the meaning and assumptions within technology to my will, which is why the qualifiers of blackhat and whitehat exist. They are two sides of the same coin. However, the utility of applying the rule-bending mindset to life itself has been recognized in the popularization of the term “life hack.” Hopefully, we are whitehat life hackers. A life hack is like discovering emergent gameplay that is a logical if unexpected consequence of what occurs in nature. It’s a conscious form of human evolution.

If you’ve worked on a popular website, you will find a surprisingly high percentage of people follow the rules as long as you explain properly. Then again a large percentage will ignore the rules out of laziness or ignorance rather than malice. Then there are hackers and developers, who want to understand how the rules can be used to our advantage, or we are just curious what happens when we don’t follow the rules. When my seven-year-old does his online math, he sometimes deliberately enters the wrong answer, to see what animation triggers. This is a benign form of the hacker mentality — but now it’s time to talk about my experience with a lifehacker of the blackhat variety, who liked experimenting with my deepest insecurities because exploiting them served her purpose.

Verbal abuse is like a cross-site scripting attack

William Faulkner wrote that “the past is never dead. It’s not even past.” Although I now share my life with a person who is kind, supportive, and fascinating, I’m arguably still trapped in the previous, abusive relationship, because I have children with that person. Sometimes you can’t control who you receive input from, but recognizing the potential for that input to be malicious and then taking control of how it is interpreted is how we defend against both cross-site scriptingand verbal abuse.

For example, my ex would input the word “stupid” and plenty of other names I can’t share on this blog. She would scream this into my consciousness again and again. It is just a word, like a malicious piece of JavaScript a user might save into your website. It’s a set of characters with no inherent meaning. The way you allow it to be interpreted does the damage. When the “stupid” script ran in my brain, it was laden with meanings and assumptions in the way I interpreted it, like a keyword in a high-level language that has been designed to represent a set of lower-level instructions:

  1. Intelligence was conflated with my self-worth.
  2. I believed she would not say the hurtful things after her tearful promises not to say them again once she was aware it hurt me, as though she was not aware the first time.
  3. I felt trapped being called names because I believed the relationship was something I needed.
  4. I believed the input at face value that my actual intelligence was the issue, rather than the power my ex gained over me by generating the reaction she wanted from me by her saying one magic word.

Patching the vulnerabilities in your psyche

My psychologist pointed out that the ex likely knew I was not stupid but the intent was to damage my self-worth to make me easy to control. To acknowledge my strengths would not achieve that. I also think my brand of intelligence isn’t the type she values. For instance, the strengths that make me capable of being a software engineer are invisible to my abuser. Ultimately it’s irrelevant whether she believed what she was shouting — because the purpose was the effect her words had, rather than their surface-level meaning. The vulnerability she exploited was that I treated her input as a first-class citizen, able to execute with the same privileges I had given to the scripts I had written for myself. Once I sanitized that input using therapy and self-hypnosis, I stopped allowing her malicious scripts to have the same importance as the scripts I had written for myself, because she didn’t deserve that privilege. The untruths about myself have lost their power — I can still review them like an inert block of JavaScript but they can’t hijack my self-worth.

Like Alice using Humpty Dumpty’s logic against him in the xkcd cartoon, I showed that if words inherently have no meaning, there is no reason I can’t reengineer myself so that my meanings for the words trump how the abuser wanted me to use them to hurt myself and make me question my reality. The sanitized version of the “stupid” script rewrites those statements to:

  1. I want to hurt you.
  2. I want to get what I want from you.
  3. I want to lower your self-worth so you will believe I am better than you so you won’t leave.

When you translate it like that, it has nothing to do with actual intelligence, and I’m secure enough to jokingly call myself an idiot in my previous article. It’s not that I’m colluding with the ghost of my ex in putting myself down. Rather, it’s a way of permitting myself not to be perfect because somewhere in human fallibility lies our ability to achieve what a computer can’t. I once worked with a manager who when I had a bug would say, “That’s good, at least you know you’re not a robot.” Being an idiot makes what I’ve achieved with CSS seem more beautiful because I work around not just the limitations in technology, but also my limitations. Some people won’t like it, or won’t get it. I have made peace with that.

We never expose ourselves to needless risk, but we must stay in our lane, assuming malicious input will keep trying to find its way in. The motive for that input is the malicious user’s journey, not ours. We limit the attack surface and spend our energy understanding how to protect ourselves rather than dwelling on how malicious people shouldn’t attempt what they will attempt.

Trauma and selection processes

In my new relationship, there was a stage in which my partner said that dating me was starting to feel like “a job interview that never ends” because I would endlessly vet her to avoid choosing someone who would hurt me again. The job interview analogy was sadly apt. I’ve had interviews in which the process maps out the scars from how the organization has previously inadvertently allowed negative forces to enter. The horror trope in which evil has to be invited reflects the truth that we unknowingly open our door to mistreatment and negativity.

My musings are not to be confused with victim blaming, but abusers can only abuse the power we give them. Therefore at some point, an interviewer may ask a question about what you would do with the power they are mulling handing you —and a web developer requires a lot of trust from a company. The interviewer will explain: “I ask because we’ve seen people do [X].” You can bet they are thinking of a specific person who did damage in the past. That knowledge might help you not to take the grilling personally. They probably didn’t give four interviews and an elaborate React coding challenge to the first few developers that helped get their company off the ground. However, at a different level of maturity, an organization or a person will evolve in what they need from a new person. We can’t hold that against them. Similar to a startup that only exists based on a bunch of ill-considered high-risk decisions, my relationship with my kids is more treasured than anything I own, and yet it all came from the worst mistake I ever made. My driver’s license said I was 30 but emotionally, I was unqualified to make the right decision for my future self, much like if you review your code from a year ago, it’s a good sign if you question what kind of idiot wrote it.

As determined as I was not to repeat that kind of mistake, my partner’s point about seeming to perpetually interview her was this: no matter how much older and wiser we think we are, letting a new person into our lives is ultimately always a leap of faith, on both sides of the equation.

Taking a planned plunge

Releasing a website into the wild represents another kind of leap of faith — but if you imagine an air-gapped machine with the best website in the world sitting on it where no human can access it, that has less value than the most primitive contact form that delivers value to a handful of users. My gambling dad may have put his appetite for risk to poor use. But it’s important to take calculated risks and trust that we can establish boundaries to limit the damage a bad actor can do, rather than kid ourselves that it’s possible to preempt risk entirely.

Hard things, you either survive them or you don’t. Getting security wrong can pose an existential threat to a company while compromising on psychological safety can pose an existential threat to a person. Yet there’s a reason “being vulnerable” is a positive phrase. When we create public-facing websites, it’s our job to balance the paradox of opening ourselves up to the world while doing everything to mitigate the risks. I decided to risk being vulnerable with you today because I hope it might help you see dev and life differently. So, I put aside the CodePens to get a little more personal, and if I’m right that front-end coding needs every part of your psyche to succeed, I hope you will permit dev to change your life, and your life experiences to change the way you do dev. I have faith that you’ll create something positive in both realms.


Applying the Web Dev Mindset to Dealing With Life Challenges originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags:

Design Handoff Pitfalls: Common Mistakes and How to Avoid Them

February 21st, 2025 No comments

Design handoffs often fail due to misunderstandings over specs, missing assets, and poor communication between designers and developers. To avoid these pitfalls, designers should standardize documentation, provide clear context, and maintain ongoing collaboration throughout the development process.

Categories: Designing, Others Tags:

NordVPN Special Sale: Unlock Ultimate Online Privacy: Up to 73% Off + 6 Months Free!

February 20th, 2025 No comments

Take advantage of NordVPN’s limited-time offer to get up to 70% off plus 6 months free on select plans, ensuring your online privacy and security. Visit NordVPN now to protect your data…

Categories: Designing, Others Tags:

How to Create Better Error Alerts: A Guide to Improving User Experience

February 19th, 2025 No comments

Effective error alerts inform, guide, and reassure users with clarity and empathy. Prioritize actionable messages, avoid jargon, and iterate based on feedback to improve user experience.

Categories: Designing, Others Tags:

YouTube Unveils a New Red: A Bold and Subtle Brand Evolution

February 17th, 2025 No comments

YouTube has refreshed its iconic red with a new red-to-magenta gradient to celebrate its 20th anniversary, enhancing visual appeal and color consistency across devices. The subtle update improves accessibility, encourages user interaction, and reflects the platform’s ongoing evolution.

Categories: Designing, Others Tags:

The What If Machine: Bringing the “Iffy” Future of CSS into the Present

February 17th, 2025 No comments

Geoff’s post about the CSS Working Group’s decision to work on inline conditionals inspired some drama in the comments section. Some developers are excited, but it angers others, who fear it will make the future of CSS, well, if-fy. Is this a slippery slope into a hellscape overrun with rogue developers who abuse CSS by implementing excessive logic in what was meant to be a styling language? Nah. Even if some jerk did that, no mainstream blog would ever publish the ramblings of that hypothetical nutcase who goes around putting crazy logic into CSS for the sake of it. Therefore, we know the future of CSS is safe.

You say the whole world’s ending — honey, it already did

My thesis for today’s article offers further reassurance that inline conditionals are probably not the harbinger of the end of civilization: I reckon we can achieve the same functionality right now with style queries, which are gaining pretty good browser support.

If I’m right, Lea’s proposal is more like syntactic sugar which would sometimes be convenient and allow cleaner markup. It’s amusing that any panic-mongering about inline conditionals ruining CSS might be equivalent to catastrophizing adding a ternary operator for a language that already supports if statements.

Indeed, Lea says of her proposed syntax, “Just like ternaries in JS, it may also be more ergonomic for cases where only a small part of the value varies.” She also mentions that CSS has always been conditional. Not that conditionality was ever verboten in CSS, but CSS isn’t always very good at it.

Sold! I want a conditional oompa loompa now!

Me too. And many other people, as proven by Lea’s curated list of amazingly complex hacks that people have discovered for simulating inline conditionals with current CSS. Some of these hacks are complicated enough that I’m still unsure if I understand them, but they certainly have cool names. Lea concludes: “If you’re aware of any other techniques, let me know so I can add them.”

Hmm… surely I was missing something regarding the problems these hacks solve. I noted that Lea has a doctorate whereas I’m an idiot. So I scrolled back up and reread, but I couldn’t stop thinking: Are these people doing all this work to avoid putting an extra div around their widgets and using style queries?

It’s fair if people want to avoid superfluous elements in the DOM, but Lea’s list of hacks shows that the alternatives are super complex, so it’s worth a shot to see how far style queries with wrapper divs can take us.

Motivating examples

Lea’s motivating examples revolve around setting a “variant” property on a callout, noting we can almost achieve what she wants with style queries, but this hypothetical syntax is sadly invalid:

.callout { 
  @container (style(--variant: success)) {
    border-color: var(--color-success-30);
    background-color: var(--color-success-95);

    &::before {
      content: var(--icon-success);
      color: var(--color-success-05);
    }
  }
}

She wants to set styles on both the container itself and its descendants based on --variant. Now, in this specific example, I could get away with hacking the ::after pseudo-element with z-index to give the illusion that it’s the container. Then I could style the borders and background of that. Unfortunately, this solution is as fragile as my ego, and in this other motivating example, Lea wants to set flex-flow of the container based on the variant. In that situation, my pseudo-element solution is not good enough.

Remember, the acceptance of Lea’s proposal into the CSS spec came as her birthday gift from the universe, so it’s not fair to try to replace her gift with one of those cheap fake containers I bought on Temu. She deserves an authentic container.

Let’s try again.

Busting out the gangsta wrapper

One of the comments on Lea’s proposal mentions type grinding but calls it “a very (I repeat, very) convoluted but working” approach to solving the problem that inline conditionals are intended to solve. That’s not quite fair. Type grinding took me a bit to get my head around, but I think it is more approachable with fewer drawbacks than other hacks. Still, when you look at the samples, this kind of code in production would get annoying. Therefore, let’s bite the bullet and try to build an alternate version of Lea’s flexbox variant sample. My version doesn’t use type grinding or any hack, but “plain old” (not so old) style queries together with wrapper divs, to work around the problem that we can’t use style queries to style the container itself.

CodePen Embed Fallback

The wrapper battles type grinding

Comparing the code from Lea’s sample and my version can help us understand the differences in complexity.

Here are the two versions of the CSS:

And here are the two versions of the markup:

Markup Code Comparison

So, simpler CSS and slightly more markup. Maybe we are onto something.

What I like about style queries is that Lea’s proposal uses the style() function, so if and when her proposal makes it into browsers then migrating style queries to inline conditionals and removing the wrappers seems doable. This wouldn’t be a 2025 article if I didn’t mention that migrating this kind of code could be a viable use case for AI. And by the time we get inline conditionals, maybe AI won’t suck.

But we’re getting ahead of ourselves. Have you ever tried to adopt some whizz-bang JavaScript framework that looks elegant in the “to-do list” sample? If so, you will know that solutions that appear compelling in simplistic examples can challenge your will to live in a realistic example. So, let’s see how using style queries in the above manner works out in a more realistic example.

Seeking validation

Combine my above sample with this MDN example of HTML5 Validation and Seth Jeffery’s cool demo of morphing pure CSS icons, then feed it all into the “What If” Machine to get the demo below.

CodePen Embed Fallback

All the changes you see to the callout if you make the form valid are based on one custom property. This property is never directly used in CSS property values for the callout but controls the style queries that set the callout’s border color, icon, background color, and content. We set the --variant property at the .callout-wrapper level. I am setting it using CSS, like this:

@property --variant {
  syntax: "error | success";
  initial-value: error;
  inherits: true;
}

body:has(:invalid) .callout-wrapper {
  --variant: error;
}

body:not(:has(:invalid)) .callout-wrapper {
  --variant: success;
}

However, the variable could be set by JavaScript or an inline style in the HTML, like Lea’s samples. Form validation is just my way of making the demo more interactive to show that the callout can change dynamically based on --variant.

Wrapping up

It’s off-brand for me to write an article advocating against hacks that bend CSS to our will, and I’m all for “tricking” the language into doing what we want. But using wrappers with style queries might be the simplest thing that works till we get support for inline conditionals. If we want to feel more like we are living in the future, we could use the above approach as a basis for a polyfill for inline conditionals, or some preprocessor magic using something like a Parcel plugin or a PostCSS plugin — but my trigger finger will always itch for the Delete key on such compromises. Lea acknowledges, “If you can do something with style queries, by all means, use style queries — they are almost certainly a better solution.”

I have convinced myself with the experiments in this article that style queries remain a cromulent option even in Lea’s motivating examples — but I still look forward to inline conditionals. In the meantime, at least style queries are easy to understand compared to the other known workarounds. Ironically, I agree with the comments questioning the need for the inline conditionals feature, not because it will ruin CSS but because I believe we can already achieve Lea’s examples with current modern CSS and without hacks. So, we may not need inline conditionals, but they could allow us to write more readable, succinct code. Let me know in the comment section if you can think of examples where we would hit a brick wall of complexity using style queries instead of inline conditionals.


The What If Machine: Bringing the “Iffy” Future of CSS into the Present originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Categories: Designing, Others Tags: