Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Wed, 22 Oct 2025 20:27:27 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 The Two Button Problem https://frontendmasters.com/blog/the-two-button-problem/ https://frontendmasters.com/blog/the-two-button-problem/#comments Tue, 21 Oct 2025 23:16:01 +0000 https://frontendmasters.com/blog/?p=7422 I see this UI/UX issue all the time: there are two buttons, and it’s not clear which one of them is in the active state. Here’s an example from my TV:

Which one of those two buttons above is active? Like if I press the enter/select button on my remote, am I selecting “Try it free” or “Sign in”? It’s entirely unclear to me based on the design. Those two design styles are ambiguous. Just two random selections from today’s trends of button design.

If I press the up (or down) arrows on my remote, the styles of the buttons just reverses, so even interacting with the interface doesn’t inform the choice.

This is a problem that can be solved at multiple levels. If the buttons are toggles that affect on-page content, the accessibility angle is partially solved by the aria-selected attribute, for example. It’s also slightly less of an issue on devices with a cursor, as you likely just click on the one that you want. This is mostly a problem with remote control and keyboard usage where the active state is unclear or ambiguous.

I call it the “two button” problem because if there were more than two buttons, the one that is styled differently is probably the one that is active. We could use our grade school brains to figure out which button is active.

(via)

Ideally, though, we don’t have to think very hard. It should be obvious which one is active.

Again, the problem:

The most obvious solution here is to make both button styles the same, but be additive when one of them is the active button.

I feel like it’s very clear now that “Try it free” is the selected button now. Even if it’s not to a user immediately. If they tab/arrow/whatever to the other button, that outline design will move to it and it will become clear then.

You could also, ya know, literally point to it:

Perhaps you could resort to more “extreme” design styles like this when there is prove-ably no mouse/cursor involved, like:

@media (hover: none) and (pointer: coarse) {
  button:active {
    /* big obvious arrow styles */
  }
}

We’ve got a recent post on @media queries that goes into lots of situations like this!

This “two button” problem also can come up in the design pattern of “toggles”. Take something like this:

A “pill” toggle design pattern.

Which one of those buttons is currently active? The up arrow? The down arrow? Neither? It’s impossible to tell by look alone.

Sometimes in this case the “active” button is “grayed out”:

The implication here is that the up arrow is the “active” one, so you don’t need to press it again as it won’t do anything. Only the non-active button is pressable. I feel like this is okay-ish as a pattern, but it’s not my favorite as the active state is less prominent instead of more prominent almost indicating it’s disabled for some other reason or doesn’t matter.

This kind of thing makes me almost miss the days of skeuomorphism where digital interfaces were designed to try to mimic real world surfaces and objects. We don’t have to go full leather-coated buttons though, we can just make the active button appear pressed through shadows and flattening.

This situation differs from the TV interface issue above in that this “active” button is indicating the button has already been pressed, not that it’s the button that will be pressed. So you’d need a style for that state as well.

Maybe these aren’t the most amazing examples in the world, but I hope I’ve got you thinking about the two-button problem. When there are only two buttons, you can’t just pick two arbitrary different styles, as that doesn’t help people understand which of the two are active. You need to think about it a little deeper and get those styles in a place where it’s obvious.

]]>
https://frontendmasters.com/blog/the-two-button-problem/feed/ 5 7422
A Progressive Enhancement Challenge https://frontendmasters.com/blog/a-progressive-enhancement-challenge/ https://frontendmasters.com/blog/a-progressive-enhancement-challenge/#comments Fri, 03 Oct 2025 16:06:19 +0000 https://frontendmasters.com/blog/?p=7324 Let’s say you’ve got some interactive element.

This element works perfectly fine in just HTML, which is the foundation of progressive enhancement.

And now, in your JavaScript, the functionality this button provides isn’t really necessary anymore, and your plan is to hide this element.

What is the best way to accomplish this?

I think it’s good to think of this abstractly, but if what I’ve presented above is so abstract that it makes it hard to think about, here are some examples:

  1. A “Load More” anchor link that loads the next set of items (i.e. <a href="?page=3">Load More</a>) which you don’t need after JavaScript loads because you’ve implemented an infinite scroll UX.
  2. A “Save” button that saves user-entered information on the page to the database (i.e. <button onclick="save()">Save</button>) which you don’t need after JavaScript loads because you’ve implemented auto-saving functionality.

A “js” Class

A classic approach to this is hiding the button when you know JavaScript is available. You put something like this pretty early in your HTML:

<script>
  document.documentElement.classList.add("js");
</script>

If this executes, you’ve proven that JavaScript is available, so you hide the button:

html.js {
  .save-button {
    display: none;
  }
}

As appealing as this looks, it may not be the catch-all perfect solution.

Downsides:

  • You’ve proven here that JavaScript is available, but you aren’t checking if the particular JavaScript that does the auto-saving is loaded and has run successfully. You can probably account for that by applying a more specific class just for this situation and applying it after the code that implements auto-saving.
  • The longer you (necessarily) have to wait for the JavaScript to be done, the longer the button is visible on the screen. This is likely to cause a “flash” of the button being there where is doesn’t need really need to be.

On States

This question came up for me from a ShopTalk Show listener Tibor Leupold writing in asking about it. He was concerned about layout shift as a result of hiding the element(s) as well as the awkward UX.

Let’s get this one out of the way: couldn’t you just… leave the interactive elements there but change their functionality when the JavaScript loads? Maybe? Probably? That’s skirting the question though. Let’s assume the web is a big place with an unknowable amount of situations and that this particular situation of needing/wanting to hide an element with minimal impact is a reasonable one.

A way to think about our needs here is that there are three states to concern ourselves with:

  1. JavaScript is unavailable entirely
  2. Before the relevant JavaScript has loaded and executed
  3. The relevant JavaScript is loaded and executed successfully

No JS

We’re probably not going to hide the button by default, as we don’t have a mechanism for un-hiding it in a no-JS situation. So we basically don’t need to do anything to accomplish this state, just have the interactive element on the page and functional in HTML.

In the reverse situation, where you have an element on the page that only works with JavaScript, you can hide it in a no-JS situation like:

<noscript>
  <style>
    .js-only-interactive-element {
      display: none;
    }
  </style>
</noscript>

Before JS Loaded

This is the hardest state. In this state, perhaps we know that JavaScript is available, but we don’t know how long it’s going to take or even if the JavaScript we care about is going to execute successfully.

It seems like the ideal behavior would be “hide the interactive element for a brief period, then if the relevant JavaScript isn’t ready, show the element.” But how?! We can’t count on JavaScript for this behavior, which is the only technology I’m aware of that could do it. Rock and a hard place!

Maybe there is some extremely exotic technique involving HTML streaming that could delay the “send” of the interactive element down from the network for that brief blocking period? That’d be wild.

Another thing I think of is the behavior of font-display: block;. This is about the behavior of loading custom fonts via CSS @font-face. It can tell the browser how to behave while the custom font it loading. Do you want the browser to wait to see if the custom font loads and then “swap” to it? You’ve got options. The block value says:

Gives the font face a short block period and an infinite swap period.

Seems related! Maybe there is a way to bring this kind of behavior to progressive enhancement elements to mimic the behavior we want: “hide the interactive element for a brief period, then if the relevant JavaScript isn’t ready, show the element.” Help us, web platform.

JS Ready

This is a fairly straightforward state, but it’s the cause of the “flash” and potential layout shift.

function setUpInfiniteScroll() {
  // do all the work

  // at the end, say it's ready
  document.documentElement.classList.add("infinite-scroll-ready");
}
.infinite-scroll-ready {
  .load-more-link {
    display: none;
  }
}

The problem here is: how long is that “Load More” link going to be on the page before it disappears? Is it fairly instant? A few hundred milliseconds? Eight seconds? Never? (You really can’t know.)

Also: will the layout shift it triggers cause the user to potentially click on something they didn’t mean to? Maybe hiding can be done without the layout shift?

.infinite-scroll-ready {
  .load-more-link {
    visibility: hidden;
  }
}

Is there a better way?

I feel like people have been thinking about progressive enhancement for a couple decades now. Is there an extremely clean/simple way to do this that I’m just not seeing?

]]>
https://frontendmasters.com/blog/a-progressive-enhancement-challenge/feed/ 2 7324
Reanimating the CSS Day Buttons https://frontendmasters.com/blog/reanimating-the-css-day-buttons/ https://frontendmasters.com/blog/reanimating-the-css-day-buttons/#respond Mon, 31 Mar 2025 16:55:52 +0000 https://frontendmasters.com/blog/?p=5489 Are you as excited about CSS Day as I am? While browsing the conference website, I couldn’t help but notice their big firebrick-red buttons. A website isn’t just about displaying content, it’s about creating excitement. Every interaction should feel polished and engaging, especially buttons, which are the primary way users navigate the site.

A well-animated button can capture attention, reinforce branding, and make the experience more enjoyable. In this article, we’ll take a closer look at the existing buttons and explore ways to enhance them with modern CSS techniques.

First, here is a version of the button that is currently on the website. Hover over the button to see its shape change.

The a element is wrapped with a div that has a solid background color, and the hover effect sets the div‘s background to transparent, revealing the a‘s arrow shape, created using a clip-path. The transition between the states (the ‘fade’ of the background) creates weird looking triangles.

I think that we can improve on that by using some movement and dynamics.

Approach 1: Background Image Animations

One simple-yet-effective way to add flair to buttons is by animating a background-image. Well, we’re not really animating the image, but we can achieve a smooth transition effect that feels dynamic by animating the background-position property.

Here is a button that uses a linear transition on the background-position:

button {
  border: 2px solid firebrick;
  background: linear-gradient(120deg, white 50%, firebrick 0) 0 0/ 250% 100%;
  color: firebrick;
  padding: 0.6em 1em 0.7em;
  cursor: pointer;
  transition: all 0.5s;
  
  &:hover {
    background-position-x: 100%;
    color: white;
  }
}

In this example the background stretches the gradient horizontally to 2.5 times the width of the button, and the background’s position changes from 0% to 100%. to better understand this effect, you can use the ‘Visualize background’ checkbox.

So how do you use background-position to create the arrow-shaped hover effect? We’ll actually need to layer two backgrounds and control both positions. Here’s how:

The pointed shape is created using two simple conic-gradients, and again, the background stretches each gradient to 2.5 times the width of the button, so we can set the background-position to bring the center of these conics in and out of view.

This method can actually be quite powerful and is great for many applications. And while I love using (and animating) background gradients, maybe for this specific use case it’s not the best option. So let’s try something else…

Approach 2: Clip-Path Transition

Another way to animate shapes in CSS is by using clipping, allowing us to create unique shapes and transitions. In our case, the buttons already have a clip-path property, so let’s use it. We’ll set it to the hover state, and ‘reset’ the polygon on idle.

.button {
  display: block;
  border: none;
  background: var(--yearColour);
  color: white;
  padding: 0.6em 1em 0.7em;
  clip-path: polygon(0% 0%, 100% 0%, 100% 50%, 100% 100%, 0% 100%, 0% 50%);
  cursor: pointer;
  transition: clip-path 0.5s;

  &:hover {
    clip-path: polygon(0% 0%, 95% 0%, 100% 50%, 95% 100%, 0% 100%, 5% 50%);
  }
}

Note that in order to transition the movement, the number of nodes in the polygon shapes must be the same.

This approach already looks nice, but I think we can do better.

When working with clip-path (and clipping in general) you need to remember that the clipping is inwards, removing parts of your elements, and you can’t overflow anything outside the clipped area. If we do want to expand outward from our element, we need to first expend the element itself, and then adjust the clipping.

.button-v2 {
  display: block;
  border: none;
  background: var(--yearColour);
  color: white;
  padding: 0.6em 1.3em 0.7em;
  clip-path: polygon(2.5% 0%, 97.5% 0%, 97.5% 50%, 97.5% 100%, 2.5% 100%, 2.5% 50%);
  cursor: pointer;
  transition: clip-path 0.5s;

  &:hover {
    clip-path: polygon(0% 0%, 95% 0%, 100% 50%, 95% 100%, 0% 100%, 5% 50%);
  }
}

In the above example I’ve increased the inline padding, making the element wider, then adjusted the idle state of the polygon to remove the added width. Now the clipping is not just inward, but also expands out, which not only creates a more dynamic effect but also reduces the risk of cutting into the button’s content.

Here is a live demo of the two versions, In my opinion, this second shape looks slightly better overall. Which one do you like?

Solving the Challenge of the Dotted Button

While the previous techniques work well for solid buttons, the CSS Day website also has a dotted-style button. These require a different approach, since background-image and clip-path alone don’t handle dotted outlines effectively.

Approach 3: Pseudo-Elements and Transforms

Somehow, whenever there’s an animation or interaction that feels tricky to implement, it often turns out that pseudo-elements (e.g. ::before and ::after) are the solution. They’re like the hidden superpower of every element, allowing us to do some really cool things.

In this case, we can achieve a clean and elegant solution using pseudo-elements. The idea is pretty straightforward: ensure each pseudo-element spans the full width of the button and half the height. We place one element at the top of the button and the second at the bottom. Then, on hover, we apply a skew transformation to the elements. Simple enough? Here’s a live demo:

Let’s break down what we added:

  • We applied position: relative; to the button as we’re going to position the pseudo-elements using position: absolute.
  • For the pseudo-elements, we started with shared styling: positioning, size, color, z-index, and of course, a transition so everything moves smoothly.
  • The ::before pseudo-element is placed at top: 0; to serve as the background for the top half of the button, and the ::after pseudo-element is positioned at bottom: 0; to cover the bottom half.
  • We added a transform with a simple skew function along the X-axis, and used calc to adjust the direction of the skew in the ::after element so the skew effects are applied in two different directions.
  • The last thing we added is a hover state that defines the desired skew angle, transforming the button into an arrow shape.

So how do pseudo-elements help us solve the dotted button challenge? It’s simple: all we need to do is change the text color of the button and the background color of the pseudo-elements, then apply a dotted border. The key is to ensure that the ::before has a border on the sides and top, while the ::after gets a border on the sides and bottom.

.button.dotted {
  color: firebrick;

  &::before, &::after {
    background-color: white;
    border: dotted firebrick;
  }
  &::before {
    border-width: 2px 2px 0;
  }
  &::after {
    border-width: 0 2px 2px;
  }
}

That’s it. I’ve also added a version that changes the button color after the shape shifts on hover. Here are live examples of both versions:

Wrapping Up

We’ve reanimated the CSS Day 2025 buttons by experimenting with different CSS techniques:

  1. Background-image animations for smooth gradient transitions.
  2. Clip-path effects for unique button shapes.
  3. Pseudo-elements to create a dynamic dotted button effect.

Each approach offers distinct advantages (and some drawbacks), and it’s important to familiarize ourselves with various animation options so that we can choose the most suitable one for each case based on the design needs, the desired effect, and the button’s context

Want to take this further? Try incorporating CSS Variables for more flexibility or mixing in @keyframes for even more animation control. Happy coding!

]]>
https://frontendmasters.com/blog/reanimating-the-css-day-buttons/feed/ 0 5489
CSS text-box https://frontendmasters.com/blog/css-text-box/ https://frontendmasters.com/blog/css-text-box/#respond Fri, 27 Dec 2024 19:05:54 +0000 https://frontendmasters.com/blog/?p=4884 Safari is first to drop text-box in CSS, which plenty of people are excited for as it takes care of aligning text in many cases without the use of fragile magic numbers. They say:

Now you can declare which font metric you want the browser to consider the edge of the text box when calculating layout

Jason Bradberry has a wonderfully deep article and showcases one of the big use cases (non jacked up button centering).

Like Robin Rendle says:

I’m always fighting the text-box when it comes to buttons in an interface and so hopefully this property fixes all that.

Adam Argyle says:

This line of code is about to be in every single stylesheet.

h1, p, button {
  text-box: trim-both cap alphabetic;
}
]]>
https://frontendmasters.com/blog/css-text-box/feed/ 0 4884
Multi-State Buttons https://frontendmasters.com/blog/multi-state-buttons/ https://frontendmasters.com/blog/multi-state-buttons/#comments Thu, 05 Dec 2024 16:20:50 +0000 https://frontendmasters.com/blog/?p=4677 There are traditional ways for a user to pick one-option-from-many. The classics beeing a <select> or a group of <input type="radio"> elements.

But it’s nice to have more options. Sometimes when a user must choose one option from many, it’s nice to have a single element that switches between available option on a quick click. A practical example of such a singular UI is a tag control that transitions through various states on each click. Any given tag in an interface like this could be be in three different states:

  1. Disregarded in search results (default state)
  2. Search results must include tag
  3. Search results must exclude tag

Here’s an image example of such a UI:

The Plan

We’ll be coding such a control using a set of stacked HTML radio buttons.

The UI’s functionality — jumping through different states on each click — is implemented by a bit of CSS-only trickery. We’ll be changing the value of the CSS property pointer-events in the radio buttons when one is selected.

The pointer-events property when applied to HTML elements determines whether a pointer event, such as a click or hover — through mouse pointer, touch event, stylus usage, etc — occurs on an element or not. By default, the events do occur in the elements, which is equivalent to setting pointer-events: auto;.

If pointer-events: none; is set, that element won’t receive any pointer events. This is useful for stacked or nested elements, where we might want a top element to ignore pointer events so that elements below it become the target.

The same will be used to create a multi-state control in this article.

Basic Demo

Below is a basic control we’ll be coding towards to demonstrate the technique. I’ll also include a Pen for the movie tags demo, shown before, at the end.

<div class="control">
  <label class="three">
    <input type="radio" name="radio" />
    Third state
  </label>

  <label class="two">
    <input type="radio" name="radio" />
    Second state
  </label>

  <label class="one">
    <input type="radio" name="radio" checked />
    First state
  </label>
</div>
.control {
    width: 100px;
    line-height: 100px;
    label {
        width: inherit;
        position: absolute; 
        text-align: center;
        border: 2px solid;
        border-radius: 10px;
        cursor: pointer;
        input {
            appearance: none;
            margin: 0;
        }
    }
    .one {
        pointer-events: none;
        background: rgb(247 248 251);
        border-color: rgb(199 203 211); 
    }
    .two {
        background: rgb(228 236 248);
        border-color: rgb(40 68 212); 
    }
    .three {
        background: rgb(250 230 229);
        border-color: rgb(231 83 61);
    }
}

In HTML shown above, there are three <input> radio buttons (for three states), which are nested within their respective <label> elements.

The label elements are stacked over each other within the parent <div> element (.control), sharing the same dimensions and style. The default appearance of the radio buttons is removed. Naturally, the label elements will trigger the check/uncheck of the radio buttons within them.

Each label is colored differently in CSS. By default, the topmost label (.one) is checked on page load for having the checked HTML attribute. In CSS, its pointer-events property is set to none.

Which means when we click the control, the topmost label isn’t the target anymore. Instead, it clicks the label below it and checks its radio button. Since only one radio button in a group with the same name attribute can be checked at a time, when the bottom label is checked, its radio button unchecks the topmost label’s. Consequently, the control transitions from its first to second state.

That’s the basis of how we’re coding a multi-state control. Here’s how it’s programmed in the CSS for all the labels and, consequently, their radio buttons:

label:has(:checked) {
    ~ label {
        opacity: 0;
    }
    &:is(:not(:first-child)) {
        pointer-events: none;
        ~ label { pointer-events: none; }
    }
    &:is(:first-child) {
        ~ label { pointer-events: auto; }
    }
}

When a label’s radio button is checked, the following labels in the source code are hidden with opacity: 0 so that it alone is visible to the user.

If a checked radio button’s label isn’t the first one in the source code (bottom-most on screen), it and the labels after it get pointer-events: none. This means the label underneath it on the screen becomes the target of any following pointer events.

If the checked radio button’s label is the first one in the source code (bottom-most on screen), all the labels after it get the pointer-events value auto, allowing them to receive future pointer events. This resets the control.

In a nutshell, when a user selects a state, the following state becomes selectable next by giving the current and all previously selected states pointer-events: none.

Usage Warning

Although this method is applicable to any number of states, I would recommend limiting it to three for typical user controls like tags, unless it’s a fun game where the user repeatedly clicks the same box and sees something different each time. Additionally, it’s apt to consider whether keyboard navigation is to be supported or not. If it is, it would be more practical to adopt a user experience where users can see all reachable options using the tab and navigation keys, rather than showing a single UI.

Advanced Demo

Below is a prototype for a tag cluster composed of three-state tags designed to filter movie search results based on genres. For instance, if a user wants to filter for comedy movies that are not action films, they can simply click on comedy once to include it and on action twice to exclude it. If you’re curious about how the counts of included and excluded tags are calculated in the demo below, refer to the list under the Further Reading section.

Further Reading

]]>
https://frontendmasters.com/blog/multi-state-buttons/feed/ 3 4677
Button Stealer https://frontendmasters.com/blog/button-stealer/ https://frontendmasters.com/blog/button-stealer/#respond Tue, 03 Sep 2024 23:03:43 +0000 https://frontendmasters.com/blog/?p=3732

A Chrome extension that “steals” a button from every website you open. Button Stealer works automatically. Do your usual everyday online stuff and watch the collection of your stolen buttons grow. It’s fun, useless, and free!

]]>
https://frontendmasters.com/blog/button-stealer/feed/ 0 3732
In Praise of Buttons https://frontendmasters.com/blog/in-praise-of-buttons/ https://frontendmasters.com/blog/in-praise-of-buttons/#respond Wed, 31 Jan 2024 17:15:21 +0000 https://frontendmasters.com/blog/?p=701 The caption on this image in Niko Kitsakis’s article In Praise of Buttons speaks to me.

Two rows of buttons. The first looking only like icons, the second like actual buttons.
Two rows of buttons. Which appear more inviting to interact with?
]]>
https://frontendmasters.com/blog/in-praise-of-buttons/feed/ 0 701