Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Thu, 25 Apr 2024 23:38:06 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.2 225069128 The HTML, CSS, and SVG for a Classic Search Form https://frontendmasters.com/blog/the-html-css-and-svg-for-a-classic-search-form/ https://frontendmasters.com/blog/the-html-css-and-svg-for-a-classic-search-form/#respond Thu, 25 Apr 2024 23:38:05 +0000 https://frontendmasters.com/blog/?p=1821 Let’s build a search form that looks like this:

That feels like the absolute bowl-it-down-the-middle search form right now. Looks good but nothing fancy. And yet, coding it in HTML and CSS I don’t think is perfectly intuitive and makes use of a handful of decently modern and slightly lesser used features.

The Label-Wrapping HTML

At a glance, this looks like an <input> all by itself. Perhaps the placeholder text is pushed in with some text-indent or something and an <svg> icon is plopped on top. But no, that’s actually harder than what we’ve done here. Instead we’re going to wrap the input in a label like this:

<label class="searchLabelWrap">
  Search
  <input type="search" placeholder="Search" class="searchInput" name="s">
</label>

This wrapping means the label and input are automatically tied to each other (e.g. clicking the label will focus the input) without having to use the for attribute and a matching id.

We’re also using the search type here on the input, which is semantically correct, but also gives us the free UX of having a ✖️ “clear search” icon in the input for free.

Wrapping All That in a Search and Form element

HTML now has a <search> element, so again that’s a semantically smart choice, and we’ll also use a <form> element so that submitting the search can be done with the Enter key. Gotta think UX! If you don’t like the extra wrapper, you could put role="search" on the <form>, but I like it:

<search>
  <form action="/search" method="GET" id="searchForm">
    <label class="searchLabelWrap">
       ...
    </label>
  </form>
</search> 

The GET method means it will append our search term as a search parameter which is usually a desirable trait of a search form. The name attribute of the input will be the search param key. That’s looking pretty solid right there.

Hiding the Label, Adding an Icon

We definitely need there to be a text label for the input for screen readers, but since we’ll be visually marking the input with both a visual icon and placeholder text, I think it’s OK to hide the text label while leaving it accessible.

<label class="searchLabelWrap">
  <span class="visually-hidden">Search</span>
  <svg viewBox="0 0 512 512" aria-hidden="true" class="icon">
    <path d="..." />
  </svg>
  <input type="search" placeholder="Search" class="searchInput">
</label>
.visually-hidden {
  position: absolute;
  left: -9999px;
}

Label Wrapping Styling

Without any CSS, we’re in this sort of situation:

Perfectly functional, but we’ve got work to do. Visually, we want the icon to appear inside the “input” area. So we’ll actually apply the background to the searchLabelWrap instead here, and wipe out all the styling on the input itself. While we’re at it, let’s think about Dark Mode/Light Mode and use the newfangled light-dark() function. This is very new so, ya know, do what you gotta do. We’ll keep things aligned with flexbox and apply very chill other styles:

.searchLabelWrap {
  display: flex;
  gap: 0.5rem;
  background: light-dark(var(--gray-light), var(--gray-dark));
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
}

.searchInput {
  border: 0;
  outline: 0; /* focus style on parent */
  background: transparent;
  font: inherit;
}

We’re doing the sin of removing the focus style on the input, which isn’t a very accessible thing to do. So we gotta bring that back!

Focus Within FTW

It’s actually the input itself which receives the :focus, but we can target the parent here instead. Maybe use :has() you say? Like :has(input:focus) could work, but there is actually a cleaner way here:

.searchLabelWrap {
  ...

  &:focus-within {
    outline: 2px solid var(--focus-blue);
    outline-offset: 2px;
  }
}

I love :focus-within it’s so cool. It was kinda the OG has and it was theorized when it came out that it could be a gateway to :has() and that’s totally what happened.

Also notice the outline-offset there. I think that’s a nice touch to push the somewhat beefy outline away a smidge.

The Icon

I’m a fan of just using inline SVG for icons. No network request and easy to style my friends. Here’s one:

<svg viewBox="0 0 512 512" aria-hidden="true" class="icon" width="
20">
  <path d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z" />
</svg>

I like tossing a width on there because an <svg> with a viewBox before CSS loads can load super wide on the screen and it’s akward. CSS will come in and make it right here:

.icon {
  width: 1rem;
  aspect-ratio: 1;
  fill: currentColor;
}

You could apply different styling, but here I’m making the icon text follow the text color so it’s easier to update. The icon is also essentially in a square area hence the simple aspect-ratio.

Colors

I used a few --custom-properties as we went. I’ll define them here at the root, as well as ensure our page knows we’re intending to support both modes:

html {
  color-scheme: light dark;

  --gray-light: #eee;
  --gray-dark: #333;
  --focus-blue: #1976d2;
}

body {
  background: light-dark(white, black);
  color: light-dark(black, white);
  font: 100%/1.5 system-ui, sans-serif;
}

That’ll do use nicely, finishing things off.

Demo

]]>
https://frontendmasters.com/blog/the-html-css-and-svg-for-a-classic-search-form/feed/ 0 1821
Arguments for opening links in a new tab or window https://frontendmasters.com/blog/links-in-new-tabs/ https://frontendmasters.com/blog/links-in-new-tabs/#respond Tue, 26 Mar 2024 00:42:56 +0000 https://frontendmasters.com/blog/?p=1386 I feel like I’ve always been in the minority on this: I don’t think you should use target="_blank" on links. Unless you have a very good reason, that is, like there is currently-playing media that would stop, or a user has unsaved work you don’t want to interrupt. But I find that most people disagree, and have their own philosophy like “links to external sites should open in a new tab”. Jason Grigsby digs into this and enumerates the reasons people think this way, which is stuff like “well then they can get back to the original site easier”. That makes a smidge of sense, but it breaks the back button which I feel like is a stronger reason not to do it. And much crappier reasons like “it increases our time-on-site metric in Google Analytics.”

]]>
https://frontendmasters.com/blog/links-in-new-tabs/feed/ 0 1386
Project Idea: Convert a PDF Form to a Web Form https://frontendmasters.com/blog/project-idea-convert-a-pdf-form-to-a-web-form/ https://frontendmasters.com/blog/project-idea-convert-a-pdf-form-to-a-web-form/#respond Fri, 22 Mar 2024 22:01:22 +0000 https://frontendmasters.com/blog/?p=1387 Jermey Keith visited a school doing this exact project, and I thought it was a great idea for learning:

There’s a specific module his students are partaking in that’s right up my alley. They’re given a PDF inheritance-tax form and told to convert it for the web.

You could make doing this job really well a whole full-stack experience, but it starts with a great front end.

I genuinely get excited by the potential for progressive enhancement here. Sure, there’s the obvious approach of building in layers; HTML first, then CSS, then a sprinkling of JavaScript. But there’s also so much potential for enhancement within each layer.

Got your form fields marked up with the right input types? Great! Now what about autocompleteinputmode, or pattern attributes?

Got your styles all looking good on the screen? Great! Now what about print styles?

Got form validation working? Great! Now how might you use local storage to save data locally?

I also love how with web forms, you don’t have to look at fields that are only required in certain circumstances. That’s implemented with progressive disclosure.

]]>
https://frontendmasters.com/blog/project-idea-convert-a-pdf-form-to-a-web-form/feed/ 0 1387
Streaming HTML https://frontendmasters.com/blog/streaming-html/ https://frontendmasters.com/blog/streaming-html/#comments Mon, 11 Mar 2024 16:26:44 +0000 https://frontendmasters.com/blog/?p=1193 I admit I went pretty far in my web development career without understanding that Streamed HTML is a thing. And while I’m admitting things, I’m still not 100% sure when it’s an ideal solution and how best to take advantage of it. But knowing is half the battle sometimes, so let me get into some research and recent writings about it.

Quick summary: Streamed HTML is as you imagine it. Rather than serving the entire HTML document at once, servers serve pieces of it. The browser gets these pieces and can start working on them, even rendering them, so the page can appear to load more quickly. It’s similar to how a progressive JPG loads or how video tends to “stream” as it plays on the web. While browsers can handle it, the rather large caveat is that not all other languages and frameworks are built to handle it.

The first I heard of it was in Taylor Hunt’s The weirdly obscure art of Streamed HTML. At a time, he was doing work for Kroger grocery stores. Every website should have a goal of being fast, but in this case there was a really direct connection that could be drawn. Kroger literally sells mobile phones, and the bestselling phone it sold was the Hot Pepper’s Poblano VLE5 ($15 on sale) and it was a reasonable assumption that “slow 3G” was what users of that phone would experience.

With Streamed HTML in place, you can see the difference:

Streamed HTML on the right, where static HTML bits are already in place and products requiring API calls are loaded later. Non-streamed HTML on the left arrives all at once.

Not all sites have my API bottlenecking issue, but many have its cousins: database queries and reading files. Showing pieces of a page as data sources finish is useful for almost any dynamic site. For example…

  • Showing the header before potentially-slow main content
  • Showing main content before sidebars, related posts, comments, and other non-critical information
  • Streaming paginated or batched queries as they progress instead of big expensive database queries

Taylor goes on to explain other benefits, like that these chunks of HTML that arrive can be interactive immediately, an import aspect of web performance (as typified by FID or “First Input Delay” metrics, or TTI “Time To Interactive”).

Taylor found that React couldn’t help with this, wanted to use Svelte instead, but found it couldn’t support Streaming HTML. Instead, he landed on Marko. (I highly suggest reading Taylor’s article for the investigations of back and front-end technologies that largely put Streaming HTML in the back seat.)

I’d bet you can imagine why streamed HTML and JavaScript frameworks have a hard time getting along. If the framework code and your usage of that framework loads, but the bits of DOM its looking for aren’t there, well, that ain’t gonna work. Like ReactDOM.render() needs what it needs — if it can’t find the DOM element to bind to it’s just going to fail, not sit around waiting for it to potentially appear later.

This all came up in mind again as Chris Haynes blogged Streaming HTML out of order without JavaScript. The video captured quite a few people’s attention, me included:

This particular trick was done not just with streamed HTML but also incorporated Web Components and and the unique ability that <slot /> has. Meaning code that arrives in predictable HTML order might get, ahem, slotted into wherever it appears in a Shadow DOM, which may be an entirely different order.

Chris nicely puts a point on what I was trying to say earlier: you need a combination of server and client technology that supports all this to pull it off. Starting with the server. Chris is more optimistic than Taylor was:

You’re in luck here, there is pretty much universal support for this across all languages. I’ve opted for Hono as it’s a lightweight server, built on web standards, that runs on node as well as a wide variety of edge platforms.

Using Node seems to be smart here (see the code), as Node’s support seems to be the most first-class-citizen-y of all the back-end languages. Deno, a spiritual successor to Node, supports it as well, and I tried running their example and it worked great.

Note that it’s the document itself that is being updated, not client-side JavaScript doing that updating.

Then you might want something client-side to help, to make things a bit more ergonomic to work with:

In the JavaScript world, there aren’t a lot of standalone templating languages that support streaming, but a recent project called SWTL does.

It’s notable that this “Service Worker Template Language” is used here, as Service Workers are aligned with streaming. Service Workers can help do things like intercept requests to the network when offline to return cached data. That can be great for speed and access, which is what streaming HTML is also trying to help with.

This all kinda has the vibes of an old technology coming roaring back because it was a smart thing to do all along, like server-side rendering broadly. For instance, React 18’s renderToPipeableStream seems to support their server-side efforts more seriously. Solid also supports streaming. It’s not without some downsides, though. Aside from the trickiness of pairing technologies that support it Eric Goldstein notes:

Streaming has a few downsides. Once a response begins and a response code is chosen (e.g. 200), there is no way to change it, so an error occurring during a data fetch will have to let the user know another way.


I wanted to try this myself, but not bother with any of the exotic JavaScript stuff, templating stuff, or out-of-order stuff. I just wanted to see HTML stream at all, from my own servers. Most of my easy-access experimental sites run on PHP, so I tried that. Taylor had noted PHP “requires calling inscrutable output-buffering functions in a finicky order.” Best I could tell, that means to turn off output-buffering so that PHP isn’t holding on to output and instead returning it.

I couldn’t get it to work myself (demo attempt), which is essentially exactly what I was worried about. This stuff, despite being “old” isn’t particularly well documented. And since it seems a little against-the-grain right now, it’s hard to know why it doesn’t work.

My PHP Attempt
<?php
// Set the content type to HTML
header('Content-Type: text/html; charset=UTF-8');

// Turn off output buffering
ob_end_flush();
ob_implicit_flush(true);

// Start the page output
echo '<!DOCTYPE html>';
echo '<html>';
echo '<head><title>Streaming Example</title></head>';
echo '<body>';
echo '<h1>Content is streaming...</h1>';

// Simulate a process that takes time, e.g., database queries or complex calculations
for ($i = 0; $i < 10; $i++) {
  // Simulate some server-side processing time
  sleep(1); // Sleep for 1 second
	
  // Send a (decently large) piece of content to the browser
  echo "<div>New content block $i loaded.</div><details>...</details>";
}

echo '</body>';
echo '</html>';
?>

So what did I do wrong? Did I screw up the PHP? That’s very plausible since I’ve literally never even tried this before and the PHP docs didn’t inspire confidence. Does the PHP host this is on (Flywheel — quite WordPress focused which as far as I know doesn’t do streaming HTML) not allow this somehow at some lower level? Is there some kind of caching in front of the PHP (NGINX reverse cache?) that is preventing this? Is the fact that Cloudflare is in front of it all messing it up? I tried bypassing all cache at this URL which seems to have worked, but that doesn’t mean something else Cloudflare-related is happening.

Anyway! If y’all have played with this or are using it and have thoughts, I’d love to hear about it.

]]>
https://frontendmasters.com/blog/streaming-html/feed/ 2 1193
Building a TODO App from Scratch — Step 5 — Extra Functionality https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-5-extra-functionality/ https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-5-extra-functionality/#respond Sat, 09 Mar 2024 00:23:53 +0000 https://frontendmasters.com/blog/?p=1169 We left off in a decent place, but there was some missing functionality that we wanted all along.

  1. We need to be able to view completed to-dos (and be able to delete them entirely)
  2. We need to be able edit to-dos.

Article Series

Editing To-Dos

The interaction we decided on for editing is to double-click the todo. This will turn the to-do, in place, into an editable input. Then you hit enter (e.g. submit) or leave the input (e.g. blur) and it will save the changes.

We can use a bit of event delegation to set up this event:

list.addEventListener("dblclick", (event) => {
  const listItem = event.target.closest("li");

  // If already editing, let it be.
  if (listItem.classList.contains("editing")) return;

  listItem.classList.add("editing");

  // Do editing.
});

Now anywhere you double-click on the list will set the relevant list item into “editing mode”, here indicated by just adding a class we can style against, like hide the existing text.

More importantly, we need to insert some new HTML turning the text into an editable input. We can use a template literal of a <form> to inject as needed, like so:

list.addEventListener("dblclick", (event) => {
  const listItem = event.target.closest("li");

  // If already editing, let it be.
  if (listItem.classList.contains("editing")) return;

  listItem.classList.add("editing");
  const textItem = listItem.querySelector(".text");
  listItem.insertAdjacentHTML(
    "beforeend",
    `<form onsubmit="updateTodo(event);" class="form-edit">
       <input onblur="updateTodo(event);" type="text" class="input-edit" value="${textItem.textContent}">
     </form>`
  );
});

That calls an updateTodo() event we’ll have to write. But first, let’s make sure we focus the input and put the cursor at the end. Just a bit of nice UX right?

list.addEventListener("dblclick", (event) => {
  const listItem = event.target.closest("li");

  // If already editing, let it be.
  if (listItem.classList.contains("editing")) return;

  listItem.classList.add("editing");
  const textItem = listItem.querySelector(".text");
  listItem.insertAdjacentHTML(
    "beforeend",
    `<form onsubmit="updateTodo(event);" class="form-edit"><input onblur="updateTodo(event);" type="text" class="input-edit" value="${textItem.textContent}"></form>`
  );

  const input = listItem.querySelector(".input-edit");
  input.focus();

  // put cursor at end of input
  input.setSelectionRange(input.value.length, input.value.length);
});

Updating the to-do is pretty straightforward. We get our hands on the new text, update it in the DOM, toggle the editing class, and write the data back to localStorage. It looks like a lot of lines, but a lot of it is just getting our hands on DOM elements and basic manipulation.

function updateTodo(event) {
  event.preventDefault();
  const listItem = event.target.closest("li");
  const textItem = listItem.querySelector(".text");
  const inputItem = listItem.querySelector(".input-edit");
  const form = listItem.querySelector(".form-edit");
  textItem.textContent = inputItem.value;
  listItem.classList.remove("editing");
  form.remove();
  TODOs = TODOs.map((todo) => {
    if (todo.id === listItem.id) {
      todo.title = inputItem.value;
    }
    return todo;
  });
  localStorage["data"] = JSON.stringify(TODOs);
}

And it works!

Viewing Completed To-Dos

Before this, we could delete a to-do, but that was it. Even though our data structure was set up to have a complete attribute that could change, all we did was filter out the completed ones entirely from the data.

Here’s what that data structure is like:

{
  title: `text of to-do`,
  complete: false,
  id: self.crypto.randomUUID()
}

Now, when we check that checkbox in the UI to complete a to-do, we need to:

  1. Set complete to true if the list item is in the active list
  2. Remove the list item entirely if the to-do is already in the completed list

We’ll update our function to be called toggleTodo and do it like this:

function toggleTodo(event) {
  const listItem = event.target.parentElement;
  // Trigger complete animation
  listItem.classList.toggle("complete");
  setTimeout(() => {
    // list item is already complete, remove it
    if (listItem.dataset.complete === "true") {
      TODOs = TODOs.filter((todo) => !todo.complete);
    } else {
      // list item is just being set to complete now
      TODOs.forEach((todo) => {
        if (todo.id === listItem.id) {
          todo.complete = !todo.complete;
        }
      });
    }

    localStorage["data"] = JSON.stringify(TODOs);

    if (!document.startViewTransition) {
      buildUI();
    } else {
      document.startViewTransition(() => {
        buildUI();
      });
    }
  }, 1000);
}

This isn’t a big change from last time, just one little fork in the logic that either removes it or updates the data.

Now we need a control though to decide if we’re looking at the active to-dos or the completed ones. Let’s make that control in HTML:

<div class="todo-type-toggles">
  <button aria-pressed="true">Active</button>
  <button>Completed</button>
</div>

Now when you press the buttons, we’ll swap the state and re-build the UI accordingly:

const toggles = document.querySelectorAll(".todo-type-toggles > button");

toggles.forEach((toggle) => {
  toggle.addEventListener("click", (event) => {
    toggles.forEach((toggle) => {
      toggle.setAttribute("aria-pressed", false);
    });
    toggle.setAttribute("aria-pressed", true);

    if (toggle.textContent === states.ACTIVE) {
      buildUI(states.ACTIVE);
    } else {
      buildUI(states.COMPLETED);
    }
  });
});

Now I’m calling buildUI() with a param to declare which type I want to see. I like using little ENUM type variables for this just to make sure we don’t do typos.

const states = {
  ACTIVE: "Active",
  COMPLETED: "Completed"
};

Then we update the function to display one or the other…

function buildUI(state) {
  let HTML = ``;
  let viewTODOs = [];

  if (state === states.COMPLETED) {
    viewTODOs = TODOs.filter((todo) => todo.complete);
  } else {
    viewTODOs = TODOs.filter((todo) => !todo.complete);
  }

  if (viewTODOs.length === 0) {
    HTML = `<li class="empty">Nothing to do!</li>`;
  }

  // Loop over the viewTODOs and build HTML to insert, exactly as before.
}

This gives us an empty state as well.

And we’ve done it!

Perhaps more back-endy readers will be like “is this dude not sanitizing data before it goes to data storage?” and that would be a smart observation. We should probably be sanitizing the HTML. But for now, the only person you can pwn with this is yourself, so not a massive deal.

Here’s where we’ve gotten now:

Article Series

]]>
https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-5-extra-functionality/feed/ 0 1169
Safari Gets a Toggle Switch Input https://frontendmasters.com/blog/safari-gets-a-toggle-switch-input/ https://frontendmasters.com/blog/safari-gets-a-toggle-switch-input/#comments Wed, 06 Mar 2024 21:33:11 +0000 https://frontendmasters.com/blog/?p=1118 Safari 17.4 dropped an interesting an unexpected feature, a native UI toggle switch control! It’s so new it’s not even in the HTML spec yet. While that might be a blocker for your project, you can use it today as a progressive enhancement on top of the standard <input type="checkbox">.

<input type="checkbox" switch>
<input type="checkbox" checked switch>

Adding a switch attribute to a normal <input type="checkbox"> changes the visual affordance in Safari 17.4+ to a switch toggle input:

Switch toggle inputs in Safari 17.4, set in rebeccapurple
Switch toggle inputs in Safari 17.4, set in rebeccapurple

The “Active” state is the default Apple blue input color (like checkboxes) but accepts the CSS accent-color property so you can make it more on brand with minimal effort. There’s even more good news because the styling story isn’t limited to accent colors, there are some new pseudo-elements to make even more customizations.

The new ::thumb and ::track pseudo-elements?

Along with the new input[switch] control, Safari is experimenting with ::thumb and ::track pseudo elements as a replacement for the old proprietary ::-webkit prefixed pseudo-elements. They are current behind a flag in Safari Technical Preview 189+.

I was excited for input[switch] but having standardized pseudo-elements for styling form controls is probably my favorite part of this feature release. It solves a minor pain point, but makes for much more approachable and maintainable CSS.

The Webkit blog demos for input[switch] show what’s possible with new CSS and get pretty wild including a Light/Dark toggle with some embedded animations. I’m excited to see what people come up with but it’s a good time to talk about some UX concerns with switch toggle inputs.

UX Concerns

The good minds at Axess Lab had some strong words for switch toggle inputs in their post “Toggles Suck!” The post is worth a read, but the gist is that there are a lot of shoddy implementations where it would be more clear to use a button, a radio, or a checkbox instead of a fancy toggle. So before you rollout a brand new settings page UI, check with your customers to see if the current checkboxes and radios are working just fine.

Reading through some of the common complaints, there’s two major concerns you need to pay attention to with toggles:

  1. You need clear labelling
  2. You need clear “Active” and “Inactive” states

If your toggle is floating in space disconnected from any sort of label or context, that’s a clear fail. If your toggle has a weird label like “Don’t not unsubscribe”, that’s a fail. If your active state looks more disabled than your inactive state, you failed.

Webkit gives the following advice which is reflected in their demos:

Generally, we recommend using a switch when the end user understands the user interface element as a setting that is either “on” or “off”.

I hate nuance, but a lot of it depends on your situation and the context in which you’re using switch toggle inputs.

Internationalization Concerns

From an internationalization standpoint, you need to think in logical properties. The idea that “thumb to the right == active” is not always true. In rtl languages “thumb to the left” is the active state. That’s where working in CSS logical properties helps you, you can use the keyword inline-end instead and you get the correct behavior in both language directions in one line of code.

Screenshot with devtools open showing the HTML element with `dir="rtl"`
Switch toggle inputs in RTL (right-to-left) direction.

Accessibility Concerns

As far as accessibility goes, from a screen reader perspective there’s a few things happening under the hood. Adding the switch attribute to a checkbox in Safari upgrades role="checkbox" to role="switch" and it announces its state in VoiceOver as “On” or “Off”, as opposed to either “checked” or “unchecked”. The can both be interacted with using the Spacebar.

VoiceOver announcement of switch inputs

One more difference to be aware of is that input[type="checkbox"] can have an indeterminate state while input[switch] cannot, it’s a boolean value.

If you’re thinking about polyfilling this feature, be aware of some notes in how screen readers announce role="switch", but if you’re comfortable using input[switch] with the fallback being a checkbox for unsupported browsers, it might make a great progressive enhancement for your page.

Detecting switch input support

Experimenting with input[switch] I found myself wanting a different layout for regular checkboxes and when input[type="checkbox"][switch] was enhanced. One nuanced difference I’ve noticed in settings screens is I like is the following:

  • Checkboxes go at the inline-start (before the label)
  • Switches go at the inline-end (after the label)

This is not a hard and fast rule, but there are a couple different ways we can achieve this in our little example. Let’s start with JavaScript.

Use JavaScript to detect switch inputs

One way to detect switch toggle input support is to use JavaScript. I wrote a little script that checks for support and adds a class to the <html> element.

// Remember Modernizr?!? lol.
function supportsSwitchInput() {
  const switchInput = document.createElement('input');
  switchInput.type = 'checkbox'
  if('switch' in switchInput) return true;

  return false
}

if(supportsSwitchInput()) document.documentElement.classList.add('supports-switch');

In your CSS you’d do something like the following to flip the presentation of the checkbox row.

div:has(input[type="checkbox"]) {
  display: flex;
  flex-direction: row;
}

.supports-switch div:has(input[type="checkbox"][switch]) {
  flex-direction: row-reverse;
}

Experimental CSS detection for switch inputs

If JavaScript is not your thing and you want to only use CSS you could try something a little more experimental…

/* ::thumb only supported in Safari TP which supports input[switch] */
@supports selecotor(::thumb) {
  div:has(input[type="checkbox"][switch]) {
    flex-direction: row-reverse;
  }
}

We can’t detect support for input[switch] exactly but does sniff for the ::thumb pseudo-element support, which is supported in Safari TP along with input[switch].

Have you tried turning it off and on again?

Over the years I’ve coded a few toggle switches by moving a <span> or ::after pseudo-element inside a <button> or <label> based on a piece of state. It never felt like a good solution and it always felt clumsy. <input type="checkbox" switch> is an elegant way to solve a commonly requested UI element.

And best of all, it’s a progressive enhancement. That means low-risk for testing and experimenting. Your mileage may vary with your users and you may want to wait for another browser to roll this out, but I already can tell this will save me a few dozen lines of weird CSS in my application.

]]>
https://frontendmasters.com/blog/safari-gets-a-toggle-switch-input/feed/ 3 1118
Capo.js: A five minute web performance boost https://frontendmasters.com/blog/capo-js-a-five-minute-web-performance-boost/ https://frontendmasters.com/blog/capo-js-a-five-minute-web-performance-boost/#comments Fri, 01 Mar 2024 16:25:08 +0000 https://frontendmasters.com/blog/?p=1086 You want a quick web performance win at work that’s sure to get you a promotion? Want it to only take five minutes? Then I got you.

Screenshot of the Capo.js console output showing rows of colored rectangles for the Actual order and Sorted order of elements in the head.

Capo.js is a tool to get your <head> in order. It’s based on some research by Harry Roberts that shows how something seemingly insignificant as the elements in your <head> tag can make your page load up to 7 seconds slower! From pragma directives, to async scripts, to stylesheets, to open graph tags, it’s easy to mess up and can have consequences. Capo.js will show you the specific order of elements to make your <head> and your page a little (or a lotta) bit faster.

Usage

  1. Head over to Capo.js homepage
  2. Install the Capo.js Chrome Extension (you can also use it as a DevTools Snippet or bookmarklet)
  3. Run Capo.js

Capo.js will log two colored bar charts in your JS console; your “Actual” <head> order and a “Sorted” <head> order. You can expand each chart to see more details. If you see a big gray bar in the middle of your “Actual” bar chart, then you’re leaving some quick wins on the table. The “Sorted” dropdown will show you the corrected order and even give you the code. But in the real world you probably need to futz with a layout template or your _header.php to get it reorganized.

Installing Capo.js takes about a minute, rearranging your <head> takes another minute. Honestly the longest part is making the Pull Request.

EDITOR INTERVENTION

[Chris busts through the door.]

OK fine Dave, I’ll give it a shot right here on Boost itself.

I installed the Chrome Extension and ran it and got this little popup:

"Before" sort order, scattered rectangles of various colors

At first I was a little confused, like this was some fancy code that Web Perf people immediately understand but I was out of the loop on. But actually it’s just a visualization of the order of things (top: actual, bottom: ideal). As a little UX feedback, it should say “Open your console for more information” because that’s where all the useful stuff is.

I found it most useful to look at the “Sorted” output (which is what you should be doing) and then try to get my source code to match that. I think I generally did OK:

"After" sort order, scattered rectangles of various colors, slightly less scattered than the previous image

I wasn’t able to get it perfect because of WordPress. A decent chunk of what goes into your <head> in WordPress comes from the output of the <php wp_head(); ?> function. I’m sure it’s technically possible to re-order output in there, but that was a more effort that I felt was worth it right at this minute.

Take your wins, that’s what I always say.

]]>
https://frontendmasters.com/blog/capo-js-a-five-minute-web-performance-boost/feed/ 7 1086
Building a TODO App from Scratch — Step 2 — HTML https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-2-html/ https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-2-html/#respond Thu, 15 Feb 2024 22:28:39 +0000 https://frontendmasters.com/blog/?p=823 OK it’s HTML time! I love HTML time. We’re laying down a foundation that makes everything feel very real.

Article Series

Is HTML jumping the gun?

Nah.

The reason that we can jump to HTML right away is that it is common to any other future choice we make. As I mentioned, we might just stay in entirely vanilla land, but let’s say we chose a JavaScript framework instead. Those frameworks will likely all have their own slightly special takes on HTML.

  • If we pick Vue it might have funny attributes like @input, :value, or v-for
  • If we pick React we might be using JSX and have funny attributes like className or onClick and we might see .map() a lot.
  • If we pick Svelte we might see stuff like on:click and {#if} blocks.

Even if we go with native Web Components, we’ll see attributes you might not see every day like part and elements like <slot />.

But… it doesn’t really matter. All of them really need to start with solid semantic and accessible HTML, and no matter what other choices we make, we can acquiesce to their specific syntax.

The Parts

We can look at our very simple design and get a sense for the main parts of the HTML:

The Header

The header is just a decorative thing. Maybe something like:

<header>
  <svg ... />
  <h1>TODO</h1>
</header>

The <h1> is perhaps a little controversial. I often prefer “saving” that for whatever the main focus of the page is. But in this case since the name of the app is doing that for us, it kinda works. Otherwise a <div> or whatever is fine here.

The Form

The <form> feels vitally important to me. Already in poking around at other web-based to-do apps I’ve seen some inaccessible forms which feels sad to me for something so simple.

The trick is:

  1. Actually using a <form>.
  2. Making sure the <input> is properly <label>ed.
  3. Offering a submit <button>.
<form id="todo-form" class="todo-form">
  <label>
    <span class="screen-reader-text">New TODO</span>
    <input type="text" name="todo">
  </label>
  <button>Add</button>
</form>

I like having the id on the form for getting our hands on it in JavaScript like we’re likely going to want to. We can make the interactive event of adding a new to-do happen on the submit event. Plus that makes us ready for potentially adding more information into the form later if we wanted, like, say, a date picker for a deadline. The submit event will still work great for us, instead of something more specific like watching for the enter key being pressed while any specific input is focused.

I added the class for styling. That’s just a habit of mine, preferring to style with mostly classes for fairly flat specificity. If you have your own methods or use Tailwind or whatever, you likely already know what you’re doing and godspeed.

Notice my <label> doesn’t have a for attribute like you normally see, and that’s because the <input> is nested within it, which is a valid pattern. I like the pattern as it’s one less thing to screw up.

The actual text of the label we’re hiding, as-per the design. That’s also a little controversial, but I think is generally fine. It’s fairly clear what this form does, and if you were using a screen reader, the actual label text is still there even if we do hide it visually with CSS.

Last, the <button> is a lone wolf within the form, and that’ll make it be a submit button automatically. We could use an <input type="submit" /> as well, which is fine, but I like buttons. You can put more stuff in them and their type is implied in simple situations like this.

The List

The interesting thing about the list is that it’s going to be dynamically generated. We’ll have as many to-do list items as our data tells us. We can start with just the wrapper:

<ol id="todo-list" class="todo-list"></ol>

Again using the ol’ id/class one two punch there. I feel like an ordered list is appropriate somehow. Especially if we eventually add sorting of the list, then the order is very intentional and deserves that treatment. And the fact that we’re using a list at all feels right. Lists announce themselves as such in screen readers, including how many items are in the list, which may be a useful bit of information. That’s more than a bunch of <div>s would do!

The each list item becomes a <li>, but the extra stuff in there is going to beef it up. Let’s pause on the re-ordering and edit-ability for now and just focus on the item and the ability to complete it.

<li id="something-unique">
  <button aria-label="Complete">
    <svg ... />
  </button>
  To-do text
</li>

I figure each list item will have a unique ID on it. Actions taken on it will need to be unique to that item so that’ll be the main identifier. Maybe we don’t need it yet but we probably will. No class name here as I feel like selecting .todo-list > li is probably fine. We could always add it.

Our design makes the interactive element to complete a to-do look like a checkbox, and we could use an <input type="checkbox" />, but I’m kinda torn. This is where visual design and interactive design collide. We didn’t think out the interaction of completing a to-do all the way. What happens when you click that thing? Does it instantly get marked as done? Then disappear? Maybe it instantly gets marked as done but doesn’t disappear from the list. Or maybe there is like a 5-second countdown before it gets removed, so you have a chance to undo it in case it was a mistake.

I kinda like the delay idea, which to me leads me more toward the path of using an <input type="checkbox"> for that with the very obvious two states. We could really do it either way. If we keep the <button>, we’d just need to make sure to use an aria-pressed role for the two states accordingly.

Lastly, the position of that interactive element I have listed first here, before the text of the to-do, because that’s how it looks in the design. I could see the case being made though that in the HTML it should come after, just so that it reads nicer. Like as you tab through the list, you’d potentially hear the list item and then the button text offering to complete it. I’m not 100% sure what the right answer is there, so I think we’ll leave it first, because that’s the visual order, and I’ve heard it’s often best to match the source order and visual order.

Let’s stop there

With some very light wireframe-y CSS applied to those classes, we get this:

That’s a nice little foundation to layer on some styles and then get into the actual functionality. But aren’t you glad we have this strong foundation first? We could go anywhere from here.

Article Series

]]>
https://frontendmasters.com/blog/building-a-todo-app-from-scratch-step-2-html/feed/ 0 823
Do you ever wish the table you were looking at had numbered rows? https://frontendmasters.com/blog/do-you-ever-wish-the-table-you-were-looking-at-had-numbered-rows/ https://frontendmasters.com/blog/do-you-ever-wish-the-table-you-were-looking-at-had-numbered-rows/#respond Tue, 13 Feb 2024 16:32:19 +0000 https://frontendmasters.com/blog/?p=815 needed numbering, but when I wanted to know the “rank” of some of the items, I found myself wishing it did. Fortunately, CSS […]]]> I had the thought gosh I wish this table’s rows were numbered when I was looking at the big table of Interop 2024 popular votes. I don’t know the that the <table> needed numbering, but when I wanted to know the “rank” of some of the items, I found myself wishing it did.

Fortunately, CSS has a quick answer:

table {
  counter-reset: tr;
  tbody tr {
    counter-increment: tr;
    :where(td, th):first-child::before {
      content: "#" counter(tr) ": ";
    }
}

The idea there is that you can count things in CSS with CSS counters, then display that value as a pseudo element slapped in there wherever you want it. The nesting is nice there as you can just plop it into any ol’ applied CSS and adjust the selector to the table you need it to.

]]>
https://frontendmasters.com/blog/do-you-ever-wish-the-table-you-were-looking-at-had-numbered-rows/feed/ 0 815
Basic Dialog Usage and Gotchas To Watch For https://frontendmasters.com/blog/basic-dialog-usage-and-gotchas-to-watch-for/ https://frontendmasters.com/blog/basic-dialog-usage-and-gotchas-to-watch-for/#comments Mon, 05 Feb 2024 18:07:03 +0000 https://frontendmasters.com/blog/?p=724 The <dialog> element in HTML is tremendous. We’ve got support across the board now, so using it is a smart plan. Just with basic usage, you get a centered modal dialog experience that comes up when you call it, a dimmed background, focus trapped within it, closes with the ESC key, and focus returning where it came from. You can style it all entirely predictably in CSS. Those things range from a little bit of a pain to downright hard to pull off if left to our own implementations. Now we get them all for free as they say.

You don’t automatically get a close button, so here’s a basic implementation that adds that.

<button class="show-dialog-button">Show Dialog</button>

<dialog id="dialog">
  <div class="dialog-title">
    Hi, I'm a dialog.
  </div>
  <button aria-label="Close Dialog" class="close-dialog-button">
    <svg ...>
  </button>
</dialog>

Now we need two click handlers, one for opening and one for closing.

const showDialogButton = document.querySelector(".show-dialog-button");
const closeDialogButton = document.querySelector(".close-dialog-button");
const dialog = document.querySelector("dialog");

showDialogButton.addEventListener("click", () => {
  dialog.showModal();
});

closeDialogButton.addEventListener("click", () => {
  dialog.close();
});

I think the naming of things here almost qualifies as a gotcha. So it’s showModal() eh? Why not showDialog(), since, ya know, it’s for the <dialog> element? So to close it it must be closeDialog() or hideDialog() surely, right? No. Just close(). Oh well I’m sure that was bikeshedded to death and there are probably ✨ reasons.

That code above is it really, that’s largely functional. And that, friends, is extremely cool. Here’s a quick live demo:

Here’s another little gotcha! Where do you want to position that close button?

I would think probably in the top right or top left of the dialog. So you might…

dialog {
  position: relative;

  .close-button {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
  }
}

Danger danger!

By setting the <dialog> to position: relative;, something we naturally do when I’m thinking about using position: absolute; on a child element, we’ve introduced a potentially gnarly UX bug. Desktop browsers will now scroll the page all the way to the top when the dialog is opened, and you’ll see the dialog open and centered in the viewport. But on iOS, the window will not scroll up. This leads to a situation where you can open the dialog and… not see it at all.

The problem there is that, by default, the <dialog> is position: fixed;, which means it would show up no matter where the page is scrolled to (even without force-scrolling to the top). But we’ve (ok ok, I’ve) accidentally overridden it with relative creating this unexpected behavior.

It’s a little tempting to look into locking the scroll position while the dialog is open, perhaps with a simple :has()-based selector, but I’m not sure how much I care if the page can scroll while it’s open.

There is other little gotcha’s to think about as well, like the fact that content within dialogs are not find-on-page-able until opened. Which kinda makes sense, but content within details elements are, so it’s just something you need to know about and probably not accidentally hide content within you want available always. I’d listen to Scott O’Hara, myself.

If you want to see a <dialog> in production use, look no further. Posts right here on Boost have a “Take Quiz” button the sidebar that opens a dialog with a custom Web Component that the Frontend Masters team have built to help build you a custom course path.

Any other gotchas you’ve found with the dialog element?

Oh hey ya know what, if someone were to make like 8-10 really cool designs for the <dialog> and the ::backdrop, that would make for a pretty sweet guest post, I’d say.

]]>
https://frontendmasters.com/blog/basic-dialog-usage-and-gotchas-to-watch-for/feed/ 3 724