Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Fri, 23 May 2025 14:35:16 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 P3 in Color Inputs https://frontendmasters.com/blog/p3-in-color-inputs/ https://frontendmasters.com/blog/p3-in-color-inputs/#respond Fri, 23 May 2025 14:34:34 +0000 https://frontendmasters.com/blog/?p=5981 I just complained that color inputs couldn’t deal in P3 colors. Looks like Safari is the first-mover on supporting that, as well as alpha:

<input 
  type="color" 
  value="oklab(59% 0.1 0.1 / 0.5)" 
  colorspace="display-p3" 
  alpha
>

I was able to make a quick demo and see it on iOS:

Under the Sliders tab, it’s still just R G & B, but it seems to me you can produce colors still in the P3 space. And if you’ve set the color space, it gives you a P3 hex code there (which I didn’t even know was a thing honestly). The actual value that you get is like: color(display-p3 0.921957 0.31855 0.969179). Which I guess is fine? Just don’t expect to get out a color in the same format you put in.

Safari also displays the alpha transparency in the preview color chip, which is great to see. This might be tricky to actually use right away, depending on what you’re doing, as non-supporting browsers will see the value as invalid and display/return black/#000000

]]>
https://frontendmasters.com/blog/p3-in-color-inputs/feed/ 0 5981
HTML & CSS for a One-Time Password Input https://frontendmasters.com/blog/html-css-for-a-one-time-password-input/ https://frontendmasters.com/blog/html-css-for-a-one-time-password-input/#comments Wed, 05 Feb 2025 22:11:57 +0000 https://frontendmasters.com/blog/?p=5067 You know those One Time Password inputs? The UI is typically 4 or 6 numbers with individual inputs. Just from today…

Here’s the UI the Safeway app uses in the Pharmacy section to log in.
Here’s how Substack authenticates.

Brad Frost was blogging about them recently. They certainly have some issue! Here’s one he spells out that I agree with wholeheartedly:

I don’t like the pattern where each digit is its own text box. It’s an affordance that’s supposed to make things clearer, but it doesn’t (for me at least). Can I paste? Where do I paste? Is my paste going to carry over into all of the little boxes? Half the time there’s a dash in the code; does that get included?

It’s awfully tricky to get right, considering the user confusion that can happen before you’re interacting with those little boxes. And once you are, the experience better be awfully accommodating.

A while back I read an article by Phuoc Nguyen about them called Build an OTP input field. I’d say all-in-all, Phuoc did a good job. The design and user experience was considered, like using the arrow keys to move between the inputs and handling “paste”. I’d say accessibility too but I feel like this is complicated enough of an interaction I can’t personally vouch for that.

But I’m also also like — damn — that’s complicated. That’s a lot of JavaScript code. Why is this so hard? And what would happen without JavaScript? Seems like it would be a pretty gnarly experience. A particular thing that makes it hard is making each character a separate <input /> in the HTML.

<div class="otp">
    <input type="text" maxlength="1" />
    <input type="text" maxlength="1" />
    <input type="text" maxlength="1" />
    <input type="text" maxlength="1" />
</div>

That complicates validation, input, pasting, accessibility, navigation… literally everything.

And then I was like… why can’t this just be one input? The rectangles behind the numbers is just visual theater. Just a bit of trendy decoration. It’s just a styling concern, not a semantic, usability, or any other concern.

So I was like… I’m just gonna make those rectangles background-images and see if that works. So I built a demo, but it had a flaw: as you typed the last character, the value would kinda slide one direction and look bad. You can see it here.

But I posed the challenge in our ShopTalk Discord and others had some ideas. Josh Collingsworth had an idea where you could cover up some area at the end and prevent the movement issue (the yellow block would be white or whatever covers up properly). Alex Fimion did it a smidge cleaner by covering the last bit with background-image instead of a pseudo-element. Here’s that:

Is that better than the 4-inputs approach?

I’m giving an only-slightly-hesitant thumbs up 👍. My hesitation is that in order for this to look right, there is a lot of “magic number” usage. That is, numbers that are just visually tweaked by hand to make it all work, and based on finicky things like font metrics (which might change over time and with different fonts) rather than hard foundational layout.

So let’s call this a pretty good take. I think when you consider the HTML used alone you can see using a one-input approach feels best:

<input
  required
  type="text"
  autocomplete="one-time-code"
  inputmode="numeric"
  maxlength="4"
  pattern="\d{4}"
>

In fact, if I started having problems with the look of the “rectangles behind the numbers” approach, I’d just make it a big ol’ single input without the individual rectangles. Like I said, I feel those are just something of a design trend anyway.

What does AI want to do?

Just as a fun little exercise, I used the very generic prompt create a 4 digit PIN input UI with zero rounds of feedback/iteration across a number of different scaffolding tools.

v0 used TSX and four individual inputs and tried to help with dealing with the complex UX. It worked decently once, then two more tries it tried using shadcn and some toast library and all kinds of stuff and just failed to run at all.
Copilot wanted four individual inputs and helped with the moving to the next input after any character typed, but none of the other issues.
Cascade (in Windsurf) went with a single input (!) and got the HTML pretty decent.
Bolt.new used a React/TSX/Tailwind approach with four inputs and handled pasting, input moving, etc pretty nicely.

I’m fairly confident that if you provided a more elaborate prompt with more specific requirements and were willing to go through rounds of iteration, you could get what you want out of these tools. I just found it interesting that by default, based on the code they were trained on, that what you get tends to focus on using multiple inputs, not to mention a heap of tools you don’t ask for.

]]>
https://frontendmasters.com/blog/html-css-for-a-one-time-password-input/feed/ 4 5067
Relatively New Things You Should Know about HTML Heading Into 2025 https://frontendmasters.com/blog/bone-up-html-2025/ https://frontendmasters.com/blog/bone-up-html-2025/#comments Mon, 06 Jan 2025 15:42:42 +0000 https://frontendmasters.com/blog/?p=4732 Not all of this is like absolutely brand spanking new just-dropped-in-2024 stuff. Some of it is, but generally it’s relatively new stuff that’s all pretty great. I’m pointing things out that I think are really worth knowing about. It’s possible you haven’t kept up too much with HTML developments as it tends to, rightfully, move a lot slower than CSS or JavaScript.

A group of details elements can behave like an accordion, among other improvements, but still have accessibility limitations.

We’ve had cross-browser <details> / <summary> support since 2016, but only recently have the abilities started to expand and clean up.

For one, you can make an “exclusive” accordion by grouping them together via the name attribute:

<details name="group"><summary>One</summary> ... </details>
<details name="group"><summary>At</summary> ... </details>
<details name="group"><summary>A</summary> ... </details>
<details name="group"><summary>Time</summary> ... </details>

Me, I mostly think the only-one-open-at-a-time thing is an anti-pattern (as do others), mostly because it’s weird to close something a user may have intentionally opened via side effect. But the web is a big place and certain specific designs I can see needing it to be effective so I don’t hate that it exists. At least I think using the term “accordion” is now appropriate in this context, but that there are still potential accessibility issues. Like imagine using this for a FAQ section where each question would normally be a header like <h3>, well, the semantics of that <h3> is wiped out by the <summary>, which is a “button”, so that’s not great.

Here’s an example of the accordion pattern being used with a group of horizontally laid out details elements. If more could be opened, it could cause horizontal scroll which I sure we can all imagine also sucks.

Note that those <details> elements are in a flexbox layout and are themselves display: flex; and only recently has that improved. (See Stephanie Stimac’s article on recent improvements.)

Ya know how the inside of a <details> is:

  1. <summary>
  2. … and whatever else

The “whatever else” can be one or more elements, and there isn’t any particularly clean way of selecting them. This is a CSS thing, but now we’ve got a ::details-content pseudo-element selector to get our hands on all that HTML in case it needs similar treatment (imagine fading it all in or out).

Here’s a great demo of using that, as well as other brand new CSS features, to make honest-to-god animating open/close details elements with arbitrary content in just HTML and CSS.

Styleable Selects are Coming

Browsers can’t just all the sudden make every aspect of a <select> and <option>s styleable, otherwise historical CSS that didn’t apply to them all the sudden does and it would wreak untold havoc on websites. The web doesn’t like to roll like that, and I applaud it for that backwards compatibility.

So… there needed to be an opt-in to make it work. A new element can work for that, which for a hot minute seemed like it would be <selectmenu>. But the progressive enhancement story for that basically sucked. So the new opt-in looks like it will be CSS triggered:

select,
::picker(select) {
  appearance: base-select;
}
Demo

Once you’ve opted in, you can apply styling to elements inside the <select> pretty freely, opening up huge doors to designing that experience.

There is some other funky things to know so I’d suggest reading this whole article. Even some new (tricky) HTML!

<select class="country-select">
  <button>
    <selectedoption></selectedoption>
  </button>
  <option value="" hidden>
    <figure></figure>
    <span>Select a country</span>
  </option>
  <option value="andorra">
    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Flag_of_Andorra.svg/120px-Flag_of_Andorra.svg.png" alt="" />
    <span>Andorra</span>
  </option>
  ...
</select>

My favorite episode of Off the Main Thread this year was about styleable selects and all the interesting details behind them that will have knock-on effects.

Oh — also you can slap a line into a select menu

I kinda love how the old school name attribute was used with <details> for the accordion behavior.

And speaking of old school elements, you can put an <hr> within a <select> to just draw a line there (a “horizontal rule” as it were). You’ve still got <optgroup label="label"> for more emphatic grouping, but sometimes a line is all you need.

<select>
   <option>apple</option>
   <option>orange</option>
   <option>banana</option>
   <hr>
   <option>pepper</option>
   <option>squash</option>
   <option>broccoli</option>
</select>

You Can Open/Close a Popover with a Button Alone

No JavaScript is required to make the opening and closing of a popover work.

<button popovertarget="the-popover">Open Popover</button>

<div popover id="the-popover">
  I'm a popover.
  
  <button popovertarget="the-popover">Close Popover</button>
</div>

If you’d prefer that the popover be closed by just a click anywhere outside of it (that’s called a “light dismiss”) then update the popover attribute to popover="auto" and that’ll do it.

The “targetting” you can see happening with those buttons is an example of an “Invoker”, which is poised bring great power to HTML in coming years.

You can’t close a popover with a form submission like you can a <dialog>, but a popover probably isn’t a great place for a form anyway so that’s fine. Another interesting sidenote is you can make a <dialog popover> if you like, getting you this button behavior for free.

There are quite a few differences between dialogs and popovers, and both are awfully useful. Perhaps the most important two features being the focus trap potential and the fact they are promoted to the “top layer” of the rendered site, meaning no futzing with z-index.

The situation we’re in with popovers is that you pretty much need to be OK with either centered or edge-based positioning for them for now, like dialogs. They are just begging for anchor positioning, but the current guess is 2026 for interop on that.

Checkboxes can be Toggle Switches

It’s as easy as:

<input type="checkbox" switch>

Although only Safari supports it for now and it’s not actually specced yet so it could be said they jumped the gun a bit. Main discussion here. And I guess we should call it a “switch” to be proper.

I’m a fan here because the absolutely correct implementation of a toggle/switch was easy to get wrong from an accessibility standpoint, and this seems… hard to get wrong.

Daniel Yuschick has an article digging into the details. I like the idea that pseudo elements specific to this UI will be exposed, like ::thumb and ::track, but I can’t tell you what the status of that is right now. Even the official demos in Safari Nightly Preview with the flag turned on aren’t rendering properly for me.

Wrap your Search

This will be easy to remember. Got an area of your site that is meant for searching? Wrap it.

<search>

</search>

It’s the same as doing <div role="search">, but my bet is that you’ll actually remember to do it.

You probably don’t need noopener noreferrer on links anymore

I’ve used linters on projects that help ensure that a link like:

<a 
  href="https://google.com" 
  target="_blank"
>

Has attributes like this as well:

<a 
  href="https://google.com" 
  target="_blank"
  rel="noopener noreferrer"
>

The problem was, that actually gave the opened pages rights to their referrer: it opened a security hole that could potentially have leaked user information or opened the door to phishing.

Ben Werd

This is not new “2025” information, but I’m only just learning that this isn’t really needed anymore. Chrome was the last to automatically apply this behavior to _blank links and that was 2021. I’ve been doing it as I have a linter that always warns me about it, so in case your browser support targets agree, you might want to check those linter settings.

Declarative Shadow DOM Paves the Way for Better Web Component Frameworks

It used to be that if you wanted a Web Component to use Shadow DOM, the only way to do it was for it to be rendered by JavaScript. This meant that Web Components that wanted or needed to use Shadow DOM had no Server Side Rendering (SSR) story at all. That was a big gap, as all the major UI frameworks have coalesced on the idea that SSR is a good idea for many reasons (performance (perceived and actual), resilience, SEO, hosting options, etc).

Now we’ve got Declarative Shadow DOM and the gap has closed.

I think it’s cool to see the Shadow DOM at work with no JavaScript at all:

Demo

What I hope we’ll see in 2025 and beyond is frameworks actually help use this. It feels like foundational technology that mostly isn’t expected to be written by hand by authors, but instead used by libraries/frameworks to build great authoring experiences around.

React 19 was the last framework to fully correctly support Web Components, so perhaps we’ll see frameworks do more than support them now but embrace them. I would expect to see a “Next.js of Web Components” at some point.

Import Maps

I used to be fond of point out that a line like this isn’t standard JavaScript.

import React from "react";

That looks like ES Modules code, but the fact that the value in quotes doesn’t start with an absolute URL or a . (a relative path) means it’s… not. It’s just a convention that we all got used to writing because JavaScript bundlers understand it to mean “that’s a thing from npm so I should go look for it in the node_modules folder.

That’s changed now, since you can, via HTML Import Maps, map the value “react” to something else via Import Maps.

So if you executed that JavaScript above from an HTML file that included an import map like this:

<script type="importmap">
  {
    "imports": {
      "react": "https://esm.sh/react@18",
      "reactdom": "https://esm.sh/react-dom@18"
    }
  }
</script>

(Basic demo.)

It would work and import the JavaScript from those locations instead. This opens up the door for not needing to use a bundler and still having the benefit of an abstraction for importing dependencies. Having the possibility to avoid tooling can be huge for long term maintenance of projects.

Don’t Sleep on the inert Attribute

You can make an element, and the entire chunk of DOM under it, ignored completely from an interactivity perspective, just by by using the inert attribute. It’s quite powerful. No clicks, no focus, the element(s) are gone from the accessibility tree, nothing can be selected, heck, the on-page “find” command won’t even find text within there.

If you’ve got any reason at all to put stuff in the DOM but have it essentially behave as if it isn’t there, make it inert until you are ready for it not to be. Imagine a multi-step form that is all in the DOM right away, but only one step at a time is not inert, so future or previous form controls aren’t accidentally used until ready.

I’d tell you this is ideal for implementing modals, but you get this behavior for free, and easier because it can be placed anywhere, just by using <dialog>.

Keep your find-on-page working properly

Another interesting attribute here. We’ve long had hidden as an attribute (even though it’s kinda weak). The change here is it taking a value, like hidden="until-found". That will hide the element as hidden does, but the content within it will still be findable with on-page text search. When it is found, it’s on you to react to the DOM event beforematch to un-hide (by removing the attribute) the content so it can be seen the hidden attribute is removed automatically, plus you’ve got the beforematch event to hook into if you need to do additional work (thx Nathan).

Here’s the demo from chrome for developers, which you might need to try in Debug View for it to trigger properly.

You’d think this would be useful for <details> elements in how they hide content, but this behavior is baked into them. This is more solid evidence for using native features — because they get the details right (generally) rather than you needing to replicate them.

Multi-Page View Transitions

You have to opt-in to this via CSS like:

@view-transition {
  navigation: auto;
}

Then once you have, regular ol’ clicked links that move to a new page (on the same domain) can have view transitions. That is, you’ll see the page use a fade effect by default. But this unlocks an amazing amount of animation control over the transition between those two page loads.

I’m listing this as an HTML feature, because I find most of the useful-ness of multi-page view transitions are unlocked in the HTML. For instance…

<!-- homepage -->
<div class="card">
  <h3 
    style="view-transition-name: post-title-087afd;"
  >Blog Post Title</h3>
  <p>...</p>
  <a href="/blog-post">Read the article Blog Post Title</a>
</div>

<!-- blog post -->
<article>
  <h1 
    style="view-transition-name: post-title-087afd;"
  >Blog Post Title</h1>
  <p>...</p>
</article>

Above, I’m imagining the “card” as a component that is generated from data and is likely one of many on the page. So it requires having a unique view-transition-name, and really the only good opportunity to apply that is in HTML.

Responsive Video is catching up to Responsive Images

With the <picture> element in HTML we get the ability to use the <source> element within and control exactly when we swap out to different source images. It’s a powerful concept that can offer big performance wins.

That idea actually originally came from a concept with the <video> tag, but then that was bizarrely removed from (most) browsers. But now it’s back thanks to some serious work by Scott Jehl and others he convinced to help the revival along.

You can do media attribute control the sources, which will probably mostly be used for width query stuff, but it can do anything media can do. Scott wrote up some examples here, like:

<video autoplay controls playsinline muted loop>
  <source media="(orientation: landscape)" src="sunset-landscape-1080.mp4">
  <source src="sunset-portrait-1080.mp4">
</video>

HTML Imports are Coming Back

Just kidding they totally aren’t.

Did we miss anything?

Any newfangled HTML out there you’ve been eyeing up or using?

]]>
https://frontendmasters.com/blog/bone-up-html-2025/feed/ 17 4732
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