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
Feedback on Masonry Layout https://frontendmasters.com/blog/feedback-on-masonry-layout/ https://frontendmasters.com/blog/feedback-on-masonry-layout/#respond Wed, 24 Apr 2024 20:40:14 +0000 https://frontendmasters.com/blog/?p=1833 Jen Simmons posted Help us invent CSS Grid Level 3, aka “Masonry” layout over on the WebKit blog the other day and is actively soliciting feedback.

Our hope is that web designers and developers chime in (post to social media, write blog posts) with your thoughts about which direction CSS should take.

Don’t mind if I do.

Do we need Masonry Layout at all?

Jen writes:

Others are questioning whether or not this kind of layout is needed on the web at all — they aren’t sure that well-known websites will use it.

I think people will use it. It can be a compelling way to lay out blocks of content. For “image galleries” alone I feel like it’s worth having a good solution for. To name the most notorious example, this type of layout has been an iconic part of the Pinterest branding since forever. How’s that for a well-known website? I’d love to see the day the actual pinterest.com uses a native web platform masonry.

Screenshot of homepage of pinterest.com

There are lots of things on the web that have naturally different aspect ratios, like images and Card components with arbitrary content. A “normal” grid layout with perfectly horizontal grid row lines isn’t always a good fit. Here’s an example of a reviews website where the reviews are of different sizes, and a masonry look works well:

Web designers have been trying to pull this layout off in various ways for ages. There are various JavaScript based solutions (using JavaScript for layout isn’t ideal, but when there is no other choice, it happens). Perhaps most famously Masonry, where the 16k+ stars on GitHub is compelling alone.

The cows have made a path here. It’s a cowroad.

Do we need a new way of doing Masonry?

I’ve made the point in the past there are existing ways to pull of this layout entirely in CSS. You can use CSS columns.

You can also use flexbox.

That is literally the layout that masonry is achieving without inventing anything new. But that leads to the next two sections.

This is partially a conversation about tabbing order.

Notice in the example above, the boxes are layed out as going “down” the first column, then back up to the second column at whatever box naturally comes next. This depends on the available width. But it might end up something like this:

[ 1 ] [ 4 ] [ 8 ]
[ 2 ] [ 5 ] [ 9 ]
[ 3 ] [ 6 ]
[ 7 ]

This is where I’m not 100% sure if problems set in or not. As I currently understand it:

  • ✅ Sighted mouse/trackpad user: not a problem
  • ✅ 100% screen reader user: not a problem
  • ⚠️ User that relies on tabbing through content visually: Maybe a problem??

So I’d love to fully understand if this vertical-by-row stacking order is actually a problem or not. And if it is, and it’s the only problem, are there other ways of solving it other than totally abandoning the columns approach? Is there some version of the (fake) idea .masonry-layout-with-columns { tabbing-order: visual; }?

So then as I understand it, if CSS grid-powered masonry became a thing, it kinda “solves” this by maintaining “as horizontal as-it-can” tabbing order. So tabbing through content goes more like this:

Screenshot of browser with masonry layout of a bunch of images. Arrows drawn over the top showing the tabbing order, which goes a bit up and down as it moves from image to image horizontally.
Is this definitely better than tabbing up and down columns? Unclear.

Is that actually better? It seems kinda funky either way to me. I just wouldn’t automatically take it for granted that this horizontal-ish tabbing order is automatically better. If the elements are of wildly different sizes, I can imagine the tabbing being more awkward than the column style tabbing (which is at least predictable).

Although if the columns went down much further than one viewport height, then I do see how the horizontal-ish method is preferable.

The name is a little funny.

The term masonry implies “like a mason would lay out bricks for a brick wall”. That’s funny to me as in that case, there actually are hard “row” lines.

brick wall

That kind of layout is already totally doable with flexbox and flex wrapping.

So I suppose the actual name masonry doesn’t feel quite right to me, especially for a term getting codified into the language.

I see “waterfall” is a possible alternative name and I think I’d vote that direction (I see there is active discussion). Or maybe the grid-template-rows: off; that actually feels nicely logical.

Didn’t we already go down this road?

Jen reminds:

A mechanism in CSS for “masonry layout” was first proposed by Mozilla in January 2020 as an extension of CSS Grid, and implemented as an experiment behind a flag in Firefox Nightly.

I’ve had a site using that as a “progressive enhancement” for most of the 4 years that’s been out. You can just set up a grid without masonry, and just add it on if it would work nicely.

.grid {
   display: grid;
   grid-template-columns: repeat(2, 1fr);
   gap: 4rem;
   grid-template-rows: masonry;
}

Seems perfectly fine to me. That would work by setting an alternative grid-template-rows on the line above as a fallback if needed or with using @supports to test the property: value pair.

Just because a test version got implemented doesn’t mean the final version has to follow that, I’m just saying I’ve played with it and used it and it seems fine.

Is it right for CSS grid?

Jen writes:

Some people remain skeptical that this capability should be part of CSS Grid

Questioning the current proposal is good. But you can’t just say you don’t like it without a pretty strong why or better alternatives. I didn’t scour the discourse myself, but the only one I see clearly in the article is:

main {
  display: masonry;
  masonry-columns: repeat(5, minmax(28ch, 1fr)); 
}

I don’t hate it, but it is a bit more limited than the fancier possibilities of grid-template-columns.

My main disagreement is that I feel like display is already so overloaded. It’s annoying to me that the same property that dictates the kind of layout the element does internally is the same thing that hides the element entirely. To me since the idea works well within the context of grid already, just use grid. Like it just seems like a natural fit.

Just this kind of control to me is obviously compelling:

And see the post for Jen’s example of using max-content columns which is honestly even more practical.

Mega menus have been hard to code, especially across multiple screen sizes. With CSS Grid Level 3, it becomes incredibly easy. A few lines of code creates a dynamic layout which adds and removes columns one at a time as space allows — without any media/container queries, and with wrapping prevention.

Then you add spanning columns to the party and subgrid and it’s clear me Jen has made a very strong case for adding “masonry” to CSS grid.

More

  • Do we ever get multiple values for gap where we can control the space between different rows/columns differently? Does the display: masonry approach allow that where grid would not?
  • Can we get a “automatically make tabbing order visually logical, I’m sick of thinking about it and want to re-order and re-arrange things as pleases me” situation going?
  • Are there any more fleshed out alternative proposals?
  • Are there any problems with using CSS grid for this? The advantages seem clear but the disadvantages do not.
  • Controlling individual column widths is sweet, should that make its way to CSS columns?
]]>
https://frontendmasters.com/blog/feedback-on-masonry-layout/feed/ 0 1833
Things That Can Break aspect-ratio in CSS https://frontendmasters.com/blog/things-that-can-break-aspect-ratio-in-css/ https://frontendmasters.com/blog/things-that-can-break-aspect-ratio-in-css/#comments Tue, 16 Apr 2024 23:22:15 +0000 https://frontendmasters.com/blog/?p=1473 CSS has an aspect-ratio property, which has had full support since around 2021. It can be a very satisfying property to use, because it can help match how your brain 🧠 works or what the desired design outcome does better than forcing dimensions does. “I need a square here” or “I need to match the 16:9 size of a <video>are very reasonable design needs. Especially in a fluid environment where you’re purposely trying not to think in exact dimensions because you know they can change.

Usually, It Just Works

Here I’ve got a <div> sitting in a container. The <div> naturally fills the width. I’ve set aspect-ratio: 1 / 1; on it, thus, it’s a square. 🥁

That video example from the intro is also prudent. In that case, for whatever historical reason, <video> has a default style of being 300px wide, so if we want it to fill the space, we need to set width: 100% and then aspect-ratio will do what we want:

Element in a grid will behave nicely as well. Here, 12 <div>s are placed within a grid with a square aspect ratio and they respect it handily:

But things can go wrong, leaving an element that appears to not respect the aspect-ratio

Potential Breakage #1) Setting Both Dimensions

If both a height and width are set on an element, then aspect-ratio is ignored. That goes for block-size and inline-size too, of course, the logical property equivalents.

.el {
  inline-size: 300px;
  block-size: 200px;
  aspect-ratio: 1 / 1; /* does nothing */
}

That makes sense to me (which one would it ignore?), but it can be awfully confusing if one of the dimensions is set from somewhere you didn’t expect. Perhaps you were expecting to set a height and aspect-ratio on an <img>, not realizing your baseline CSS file also sets the width on images, that would make aspect-ratio fail somewhat unexpectedly.

This can be true of information outside of CSS as well. For example a height attribute on an <img> tag (which is highly recommended) counting as the other axis here and not allowing aspect-ratio to work:

Note that limiting dimensions with min-width, min-height, min-inline-size, min-block-size, max-width, max-height, max-block-size, max-inline-size will be respected and also might break the aspect-ratio.

Potential Breakage #2) Stretching

Here we have three <div> in a flex container all with different aspect ratios. But… they are all the same dimensions:

Why?! Those <div>s only have a width set on them, no height (or logical equivalent). The problem here is that the height/block-size is forced because flex items are influenced by the default align-items: stretch;

If we change that default to align-items: flex-start; on the parent flex container, you can see aspect-ratio start working:

This kind of thing can be true of any of the stretch values in CSS grid or flexbox. For example, CSS grid has justify-items: stretch; which is capable of causing this same kind of problem.

Potential Breakage #3) Content That Forces Height

One of the ways we used to pull of aspect-ratio was Uncle Dave’s ol’ Padded Box Trick. The box would be created by using padding-bottom on a zero-height box. Percentage padding is a percentage of the width, so that connection between width and height is established that way. But then you’d put an absolutely positioned box inside that. The problem with absolute positioning here is that the content is outside the normal flow of the document. So if, for example, you put text inside that is taller than the box, you’d have a problem. See below how the content hangs out of the box. The box has the right aspect-ratio, but it’s unlikely this is a desirable outcome:

If we use aspect-ratio for our box instead, we’ll get:

Now the orange box contains all the content, which again is probably the desired outcome here in most situations. But notably, the box is no longer the aspect-ratio that we declared.


Did I miss any of the ways aspect-ratio can break or have unexpected results? I suppose browser-support is another thing to consider, but it’s pretty solid.

]]>
https://frontendmasters.com/blog/things-that-can-break-aspect-ratio-in-css/feed/ 1 1473
Gap is the new Margin https://frontendmasters.com/blog/gap-is-the-new-margin/ https://frontendmasters.com/blog/gap-is-the-new-margin/#comments Thu, 11 Apr 2024 21:12:32 +0000 https://frontendmasters.com/blog/?p=1673 In 2020, Max Stoiber wrote the 🌶️ spicy Margin considered harmful. On one hand, it seems silly. The margin property of CSS is just a way to push other elements away. It’s very common and doesn’t feel particularly problematic. On the other hand… maybe it is? At least at the design system component level, because those components don’t know the context in which they will be used. Max wrote:

Margin breaks component encapsulation. A well-built component should not affect anything outside itself.

Adam Argyle wrote slightly earlier that he predicted the usage of margin to naturally decline:

Prediction: margins in stylesheets will decline as gap in stylesheets climb

Well it’s four years later now! Has any of this played out? Well it’s super hard to know. Anecdotally, it feels like gap is much more heavily used and my own usage is certainly up. There is public data on usage of CSS features, and, amazingly, margin usage does appear to be slowly going down.

Looks like a slow but sure declare the last 18 months or so.

I say “amazingly” because the way this data is collected checks if the site uses the feature at all, not how much it’s used.

The chart below shows the percentage of page loads (in Chrome) that use this feature at least once.

So seeing a dip here means less sites are using the margin properly entirely.

]]>
https://frontendmasters.com/blog/gap-is-the-new-margin/feed/ 2 1673
A CSS-Powered Add/Remove Tags UI https://frontendmasters.com/blog/a-css-powered-add-remove-tags-ui/ https://frontendmasters.com/blog/a-css-powered-add-remove-tags-ui/#respond Thu, 11 Apr 2024 14:46:11 +0000 https://frontendmasters.com/blog/?p=1650 Checkboxes and labels used to have to be right next to each other to be a potent UI duo. You could do trickery like this:

<label for="toggle">Toggle</label>
<input type="checkbox" id="toggle">
<div></div>
#toggle:checked + div {
  /* Do something fancy in here with a toggleable on/off state */
}

But now, thanks to :has() in CSS, we’re not beholden to that structure anymore. We can :has() it all, as it is said. Now that these HTML elements have some autonomy, without losing their connection to one another, a lot can be achieved

Using this as a base concept, we can build a tag management component operated entirely in HTML & CSS.

A Tag Component with Interactive HTML

<label> is an interactive element that can trigger its controlled element. For instance, when we click a <label> paired with an <input type="checkbox"> the checkbox’s :checked state toggles.

A combo with a <label> allows us to design a toggle UI that can be operated from two different locations on a page (both the label and the checkbox). The controlled element is in one location and the label is in the other.

How far and how independent these two locations have to be from each other for the duo to work used to be limited, since labels can’t directly inform us which state their controlled element is currently in. For that, we have to ask the controlled element itself each time, and keep the label close to the element, so the label can be accessed in CSS during the element’s state change.

That was the case before.

Because of modern CSS standards like Grid, :has() selector and such, there’s much more freedom now between the source code arrangement in the HTML and our ability to reach any element in CSS.

<div>
  <label for="one">One</label>
  <label for="two">Two</label>
  <label for="three">Three</label>
</div>

<p>Arbitrary DOM</p>

<div>
  <input type="checkbox" id="one">
  <input type="checkbox" id="two">
  <input type="checkbox" id="three">
</div>
body:has(#one:checked) p {
  background: pink;
}
body:has(#two:checked) p {
  background: lightgreen;
}
body:has(#three:checked) p {
  background: lightblue;
}

In this article, I’ll be using checkboxes, labelsand :has() selectors to design a UI where you can add and remove “tags”. The UI will have a set of tags to select from, and a set of tags that have been selected. Clicking a tag in one set removes it from one area and makes it appear in the other set. It’s a functionality that’s perfect for checkboxes and labels to take on. Using the :has() selector means, I can keep the two set of tags in as much of a distance or depth from each other as I want, which in turn provides a lot flexibility.

Although the :has() selector can be used in many ways, in this article we’ll focus on its ability to target an element containing a specific child element. The parent is mentioned before the colon (:) and the child is mentioned inside the parentheses of has(). For example, p:has(> mark) selects all elements that have at least one direct descendant that’s a <mark>. Another example, div:has(:checked) selects all <div> elements that have at least one descendant (direct or not) element that’s in a checked state, like a radio or checkbox.

Now that we’ve got the basics covered. Here’s the final demo we’ll be working towards. We’re going to use movie genres as our tags.

Let’s get started.

HTML Construction

There are two parts:

  1. One part nests the checkboxes
  2. The other, their labels

Because we’ll be designing a cluster of tags of movie genres, a script is set up to add the checkboxes and labels for each genre to the HTML.

The script uses HTML <template> to build the new elements off of. This is to prove that you could build all of this dynamically with arbitrary tags from a data source. You can use any method you prefer or not use script at all and directly build the HTML. You’ll see the full source code in a moment.

<div>
  <!-- Plus tags (tags to be included) will render here -->
</div>

<ul>
  <!-- Minus tags (tags already included) will render here -->
</ul>

<template>
  <!-- The tags' templates -->
  <span class="plus"><input type="checkbox" /></span>
  <li><label class="minus"></label></li>
</template>
const template = document.querySelector('template').content;

const div = document.querySelector('div');
const ul = document.querySelector('ul');

const genres = ["Adventure", "Comedy", "Thriller", "Horror"];

for (let i = 0; i < genres.length; i++) {
  /* get a clone of the template's content */
  let clone = template.cloneNode(true);

  let checkbox = clone.querySelector('input[type="checkbox"]');
  /* add id to the checkbox */
  checkbox.setAttribute("id", `c${i + 1}`);
  /* add genre text to the plus tag */
  checkbox.parentElement.innerHTML += genres[i];
  /* add plus tag to the div on page */
  div.appendChild(clone.querySelector(':has(input[type="checkbox"])'));

  let label = clone.querySelector("label");
  /* add "for" attr. to the label */
  label.setAttribute("for", `c${i + 1}`);
  /* add text to the minus tag */
  label.innerText = genres[i];
  /* add minus tag to the ul on page */
  ul.appendChild(clone.querySelector(":has(label)"));
}

In the script:

  1. A set of genres (tag values) is stored as an array
  2. For each item in the genres array, a new clone is created from the template that has an empty plus (checkbox) and minus (label) tag, as seen inside the <template> in HTML
  3. The empty tags’ text and attributes are filled using the genres item’s value and index
  4. Finally, the filled tags are added to their respective containers on the page — <div> and <ul>

Here’s how the HTML source code will look like once the page renders:

<div>
  <span class="plus"><input type="checkbox" id="c1">Adventure</span>
  <span class="plus"><input type="checkbox" id="c2">Comedy</span>
  <span class="plus"><input type="checkbox" id="c3">Thriller</span>
  <span class="plus"><input type="checkbox" id="c4">Horror</span>
</div>

<ul>
  <li><label class="minus" for="c1">Adventure</label></li>
  <li><label class="minus" for="c2">Comedy</label></li>
  <li><label class="minus" for="c3">Thriller</label></li>
  <li><label class="minus" for="c4">Horror</label></li><
</ul>
  1. The parent of each checkbox is .plus. They are meant to be the tags that are yet to be included. It’s sectioned off inside a <div>
  2. Each label is .minus — the tags that are included. These are arranged in a list inside a <ul>

CSS Tag Management

Let’s look at the the key CSS rules first, then we’ll simplify it. This provides the core functionality of the tag management.

/* Remove all minus tags initially */
li { display: none; } 

/* Remove a plus tag, if it's checked */
.plus:has(input:checked){
    display: none; 
}

/* Remove a minus tag, if its plus tag is checked */
:has(#c1:checked) li:has([for='c1']),
:has(#c2:checked) li:has([for='c2']),
:has(#c3:checked) li:has([for='c3']),
:has(#c4:checked) li:has([for='c4']) {
    display: revert; 
}
  1. Initially, the list items (li) with the .minus tags are not displayed. It means the user hasn’t selected any tag to be included yet
  2. When user selects a tag — i.e. checks a checkbox (:has(input:checked)) — its parent element, .plus, is removed with display: “`none`
  3. And the corresponding .minus label’s parent (ex. li:has([for='c1']) is made visible with display: revert

Note: The CSS keyword revert changes a property value to its browser default

To automate the selector listing at the end of the above CSS snippet, I’ll add those rules in the script itself, so that it doesn’t have to be hard-coded in CSS. Again, the whole point here is proving this can all be dynamically generated for your own set of tags if you wanted. If you don’t prefer script, you can leave it as it is in CSS or use a CSS framework, whichever works for you. The following is a continuation of the previous JavaScript snippet, where only the code added now is shown.

const style = document.createElement('style');
document.head.appendChild(style);

for (let i = 0; i < genres.length; i++) {
  /* ... */
  style.sheet.insertRule(`:has(#c${i+1}:checked) li:has([for='c${i+1}']) { display: revert; }`);
}

/* a clear view of the CSS rule string from the above snippet */
`:has(#c${i+1}:checked) li:has([for='c${i+1}']) { display: revert; }`

In the above script: A new style is added to the page, and to this style a css rule for each genres items is added. The css rule is same as in the css snippet from before. Here the id values are dynamically generated.

CSS Tag Styling

Let’s style the tags’ appearance:

.plus,
.minus {
  display: inline-block;
  height: 1lh;
  padding-inline-end: 1.5em;
  border-radius: 4px;
  border: 1px solid currentColor;
  text-indent: 10%;
  font-weight: bold;
  &::after {
    display: block;
    width: 100%;
    margin-block-start: -1lh;
    margin-inline-start: 1.2em;
    text-align: right;
  }
  &:hover {
    box-shadow: 0 0 10px white, 0 0 6px currentColor;
  }
}
.plus {
  position: relative;
  color: rgb(118, 201, 140);
  margin-inline: 0.5em;
  &::after {
    content: "+";
  }
}
.minus {
  color: rgb(95, 163, 228);
  &::after {
    content: "\02212";
  }
}
input[type="checkbox"] {
  width: 100%;
  height: inherit;
  border-radius: inherit;
  appearance: none;
  position: absolute;
  left: 0;
  top: 0;
  margin: 0;
}
input[type="checkbox"],
label {
  cursor: pointer;
}
  1. The .plus and .minus tags are colored and bordered. Each has a pseudo-element (::after) to add the “+” and “-“ icon next to it, respectively
  2. The checkbox’s default appearance is removed and is made to fill the area of its container element, so the entire container is clickable

CSS Dynamic Notification

Notification messages can be displayed to the users in certain circumstances, and this can also be done entirely in CSS:

  1. When no tags are selected
  2. When at least one tag is selected
  3. When all tags have been selected
ul {
  &::before {
    display: block;
    text-align: center;
    margin-block-start: 1lh;
  }
  :not(:has(input:checked)) &::before {
    /* has no checked boxes */
    content: "No tags included yet";
  }
  :has(input:checked):has(input:not(:checked)) &::before {
    /* has atleast one checked and unchecked boxes */
    content: "Following tags are included";
  }
  :not(:has(input:not(:checked))) &::before {
    /* has no unchecked boxes */
    content: "All tags are included";
  }
}

In the above CSS nested code snippet:

  1. & represents <ul>. Hence &::before means ul:before
  2. :has() and :not(:has()) represent when the root element (the page) contains a given selector (mentioned inside the parentheses) and when it doesn’t
  3. input:checked is a checked box
  4. input:not(:checked) is an unchecked box

And here’s what’s happening in the code:

  1. ::before pseudo-element is added to the <ul>. This serves as the notification area
  2. When the page has no checked element, 'No tags included yet' is displayed
  3. When the page has at least one checked and one unchecked box, 'Following tags are included' is shown
  4. When the page doesn’t have any unchecked box, 'All tags are included' appears

Tip: Instead of the root element you can scope the code to a common parent, too. Ex. using main:has() instead of :has(), when <main> is a common ancestor of the two group of tags

Here’s the final demo:

Conclusion

Because there’s so much independence between the two set of tags, it frees you up in styling the tags however you like. You can embed the tags in sentences if you want, or you could have as many elements in between them as you want without worrying. For as long as the user is well informed by the design the purpose of the tags, and which ones have been selected, and which ones remain unselected, design them however you feel like.

Bonus: You can even use CSS counters to add a running count of selected and unselected tags, if you want. Read on CSS counters for a similar use case here.

]]>
https://frontendmasters.com/blog/a-css-powered-add-remove-tags-ui/feed/ 0 1650
Text Effects https://frontendmasters.com/blog/text-effects/ https://frontendmasters.com/blog/text-effects/#respond Tue, 02 Apr 2024 23:11:22 +0000 https://frontendmasters.com/blog/?p=1533 Looks like Mandy Michael has been busy lately! I’m just seeing her fairly new Text Effects site, which has a growing collection of cool looks for text with demos and tutorials (GOLD!). Then I noticed her Variable Fonts site has kind of joined the fray with a similar design and a third site, Text Lab as well with really progressive demos.

]]>
https://frontendmasters.com/blog/text-effects/feed/ 0 1533
Drawing a Line to Connect Elements with CSS Anchor Positioning https://frontendmasters.com/blog/drawing-a-line-to-connect-elements-with-css-anchor-positioning/ https://frontendmasters.com/blog/drawing-a-line-to-connect-elements-with-css-anchor-positioning/#respond Tue, 02 Apr 2024 18:02:50 +0000 https://frontendmasters.com/blog/?p=1511 The World Wide Web Consortium (W3C) published a First Public Working Draft of CSS Anchor Positioning last year, so I thought I would give it a try. I already had a perfect candidate to try it on: a component on my other site, adedicated.dev, which showcase my services by linking different words together.

To link different elements in columns, my component relies on heavy JavaScript calculation. Here’s that example. While I love solving a math problem here and there, I prefer browsers doing these kinds of calculations for me!

Let’s take a look at CSS Anchor Positioning and see how it might have a solution for us.

A Bit about CSS Anchor Positioning

CSS Anchor Positioning provides a better way to position an element in relation to another element. Think of a tooltip and how it is positioned related to the element that triggers it. The perfect tooltip usually “knows” if it overflows outside of the containing block. For example, if the tooltip doesn’t fit above its trigger element, it should go below it. CSS Anchor Positioning solves this problem for us, and that mean less JavaScript calculation.

It is worth noting that CSS Anchor Positioning is quite new API and it is prone to changes. At the time of this writing, the only browser that supports this feature is Chrome Canary, and it is behind the “Experimental Web Platform Features” flag.

The Demo

Back to my component. I have a three columns, and in each one I have a set of words which, when linked, form a new term. When you hover over any word, a random word in three different columns is highlighted and the final term is created. For example, “Creating WordPress Websites” or “Developing HubSpot Pages” or “Updating Shopify Layouts”. I thought it would be fun to showcase my skills in such a way. Here’s how the component works:

To solve the problem of linking different words, we need to prepare the HTML for that. I am using two <div>s for two links, first one for link between first and second column, and the other one for linking second and third column.

<div class="link link--alpha"></div>
<div class="link link--beta"></div>

First thing we need to do is to position our <div>s. For each level links, I had to set up the min-block-size (the logical equivalent of width in a left-to-right or right-to-left language — we’ll be using more of these logical properties as this article goes on):

.link {
  position: absolute;
  min-block-size: 2px;
}

Then we need a grid of all words. I am using unordered list and CSS Grid to achieve this.

<ul>
  <li>Creating</li>
  <li>WordPress</li>
  <li>Websites</li>

  <li>Developing</li>
  <li>HubSpot</li>
  <li>Pages</li>

  <li>Updating</li>
  <li>Shopify</li>
  <li>Layouts</li>

  <li>Implementing</li>
  <li>Jekyll</li>
  <li>Templates</li>

  <li>Optimizing</li>
  <li>Hugo</li>
  <li>Components</li>
</ul>
:root {
  --color-alpha: lightcyan;
  --color-beta: cyan;
  --color-gamma: indigo;
}

ul {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
  cursor: pointer;
}

li {
  color: var(--color-gamma);
  background-color: var(--color-alpha);
  border-radius: var(--space-alpha);
  position: relative;
  padding: 32px;
  transition: background-color 110ms, color 110ms;
}

To highlight the word, I am using data attributes on unordered list element, like so:

<ul data-col1="2" data-col2="3" data-col3="4">
  ...
</ul>

[data-col1="1"] li:nth-child(1),
[data-col1="2"] li:nth-child(4),
[data-col1="3"] li:nth-child(7),
[data-col1="4"] li:nth-child(10),
[data-col1="5"] li:nth-child(13),
[data-col2="1"] li:nth-child(2),
[data-col2="2"] li:nth-child(5),
[data-col2="3"] li:nth-child(8),
[data-col2="4"] li:nth-child(11),
[data-col2="5"] li:nth-child(14),
[data-col3="1"] li:nth-child(3),
[data-col3="2"] li:nth-child(6),
[data-col3="3"] li:nth-child(9),
[data-col3="4"] li:nth-child(12),
[data-col3="5"] li:nth-child(15) {
  background-color: var(--color-beta);
  transition: background-color var(--trd-beta), color var(--trd-beta);
}

Now for the fun stuff — let’s anchor some elements! We are going to define three anchor-names, each for a single column. These elements will be defined by the word element. That way our line elements will be able to use the position of linked word elements and link each other.

[data-col1="1"] li:nth-child(1),
[data-col1="2"] li:nth-child(4),
[data-col1="3"] li:nth-child(7),
[data-col1="4"] li:nth-child(10),
[data-col1="5"] li:nth-child(13) {
  anchor-name: --link-col1;
}

[data-col2="1"] li:nth-child(2),
[data-col2="2"] li:nth-child(5),
[data-col2="3"] li:nth-child(8),
[data-col2="4"] li:nth-child(11),
[data-col2="5"] li:nth-child(14) {
  anchor-name: --link-col2;
}

[data-col3="1"] li:nth-child(3),
[data-col3="2"] li:nth-child(6),
[data-col3="3"] li:nth-child(9),
[data-col3="4"] li:nth-child(12),
[data-col3="5"] li:nth-child(15) {
  anchor-name: --link-col3;
}

Here’s the image so you can visualize the link elements more easily.

Next, we need to define the offset for our element by using the anchor function. We want our first line (the left pink rectangle) to start outside and in the middle of the first word element and to end outside and in the middle of the second word element. 

.link--alpha {
  inset-block-start: anchor(--link-col1 center);
  inset-inline-start: anchor(--link-col1 right);
  inset-inline-end: anchor(--link-col2 left);
  inset-block-end: anchor(--link-col2 center);
}

(Editor’s note: I drew this crude diagram that follows to demonstrate how the placement of that pink rectangle works because it’s totally fascinating to me!)

It’s the same setup for the second line, but we are the referencing the second and third word elements instead of the first and second.

.link--beta {
  inset-block-start: anchor(--link-col2 center);
  inset-inline-start: anchor(--link-col2 right);
  inset-inline-end: anchor(--link-col3 left);
  inset-block-end: anchor(--link-col3 center);
}

To make the lines, I am using a linear gradient in the following fashion:

  • The first linear gradient is vertical line that is 100% in height and placed in the center of rectangle
  • The second linear gradient is horizontal line that starts in the top left corner and is 50% of width
  • The third linear gradient is horizontal line that starts in the bottom right corner and is 50% of width
.link {
  background-image: linear-gradient(to bottom, black, black), linear-gradient(to right, black, black), linear-gradient(to bottom, black, black);
  background-size: 2px, 50% 2px, 50% 2px;
  background-position: center, top left, bottom right;
  background-repeat: no-repeat;
}

Here’s how it looks now.

To generate different terms on each hover event and to automatically change the terms when no hover effects occur to make the whole component more appealing and inviting, we need to introduce a bit of JavaScript. Once the timeout expires, JavaScript will update the data-col1, data-col2, and data-col3 attributes.

const highlighter = (timeout = 4000) => {
  const $ul = document.querySelector('ul')
   
  const getRandomNumber = (min, max) => {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
  
  const randomHighlighter = ($s, c) => {
    const nth1 = [1, 4, 7, 10, 13]
    const nth2 = [2, 5, 8, 11, 14]
    const nth3 = [3, 6, 9, 12, 15]
    
    let c1 = getRandomNumber(1, 5)
    let c2 = getRandomNumber(1, 5)
    let c3 = getRandomNumber(1, 5)

    if(c && nth1.indexOf(c) !== -1) {
      c1 = nth1.indexOf(c) + 1
    }

    if(c && nth2.indexOf(c) !== -1) {
      c2 = nth2.indexOf(c) + 1
    }

    if(c && nth3.indexOf(c) !== -1) {
      c3 = nth3.indexOf(c) + 1
    }

    if(c2 < c1) {
      document.body.classList.add('link-alpha-inverse')
    } else {
      document.body.classList.remove('link-alpha-inverse')
    }
    
    if(c3 < c2) {
      document.body.classList.add('link-beta-inverse')
    } else {
      document.body.classList.remove('link-beta-inverse')
    }

    $s.setAttribute('data-col1', c1)
    $s.setAttribute('data-col2', c2)
    $s.setAttribute('data-col3', c3)
  }

  if($ul) {
    const $lis = $ul.querySelectorAll('li')

    let hover = false;

    randomHighlighter($ul)

    const si = setInterval(() => {
      if(!hover) {
        randomHighlighter($ul)
      }
    }, timeout)

    $lis.forEach(($li, i) => {
      $li.addEventListener('mouseenter', () => {
        randomHighlighter($ul, i + 1)

        hover = true
      })
      
      $li.addEventListener('click', () => {
        randomHighlighter($ul, i + 1)

        hover = true
      })
    })

    $ul.addEventListener('mouseleave', () => {
      hover = false
    })
  }
}

highlighter()

There is one final problem that we need to resolve. In case when the second word is “higher” than the first word, the positioning will not work. That is because we cannot have “negative” elements, meaning the block end must be bigger or equal to block start property. To solve that problem, we will add another class to the body element.

// ...

if(c2 < c1) {
  document.body.classList.add('link-alpha-inverse')
} else {
  document.body.classList.remove('link-alpha-inverse')
}

if(c3 < c2) {
  document.body.classList.add('link-beta-inverse')
} else {
  document.body.classList.remove('link-beta-inverse')
}

// ...

Now we could adjust our line component’s CSS and fix the background positioning, too.

.link-alpha-inverse .link--alpha {
  inset-block-end: anchor(--link-col1 center);
  inset-block-start: anchor(--link-col2 center);
  background-position: center, bottom left, top right;
}

.link-beta-inverse .link--beta {
  inset-block-end: anchor(--link-col2 center);
  inset-block-start: anchor(--link-col3 center);
  background-position: center, bottom left, top right;
}

Conclusion

The original solution to this kind of problems required a whole lot of JavaScript calculations and clumsy inserting of <style> element to our HTML. With CSS Anchor Positioning, we use JavaScript only to update our data attributes and toggle body classes – all calculations and heavy lifting are done by our browser. I think that is wild and I cannot wait to see other useful places where this could be used.

Final Demo

Remember to see the lines, at the time of publication, you need to be in Chrome Canary with the Experimental Web Features flag turned on. If you want to see more JavaScript calculation heavy fallback, see here.

If you’re really into these ideas, definitely check out the web.dev article Tether elements to each other with CSS anchor positioning

]]>
https://frontendmasters.com/blog/drawing-a-line-to-connect-elements-with-css-anchor-positioning/feed/ 0 1511
What You Need to Know about Modern CSS (Spring 2024 Edition) https://frontendmasters.com/blog/what-you-need-to-know-about-modern-css-spring-2024-edition/ https://frontendmasters.com/blog/what-you-need-to-know-about-modern-css-spring-2024-edition/#comments Tue, 26 Mar 2024 13:53:40 +0000 https://frontendmasters.com/blog/?p=1013 My goal with this bookmarkable guide is to provide a list of (frankly: incredible) new additions to CSS lately. There is no hardline criteria for this list other than that these things are all fairly new and my sense is that many people aren’t aware of these things. Or even if they are, they don’t have a great understanding of them and could use a plain language explanation of what it is, why they should care, and a bit of reference code. Maybe that’s you.

I’d like to work on our collective muscle memory on these features. Like I said, “even if you know about this stuff, it takes time to build the muscle memory around it.”

There is quite a bit more syntax, detail, and nuance to these things than I am presenting here. I want you to know what’s possible, reference the most basic usage and syntax, then dig deeper when you need to.

Container Queries (Size)

What are Size Container Queries?

Container Queries allow you to write styles that apply to the children of a container element when that container matches certain media conditions, typically a width measurement.

<div class="element-wrap">
  <div class="element">
  </div>
</div>
.element-wrap {
  container: element / inline-size;
}
@container element (min-inline-size: 300px) {
  .element {
    display: flex;
    gap: 1rem;
  }
}

When should you care?

If you’ve ever thought: I wish I could make styling decisions based on the size of this element, not the entire page like @media queries force me to do. Then using @container queries are for you! People who work on design systems or heavily component-based websites will probably mostly use Container Queries to style things based on size, because the size of the entire page is a poor proxy for the size of a component.

Support

Browser SupportFull
Progressive Enhancement?Potentially — if it’s not critical what you are styling, then yes.
PolyfillableYes

Basic Demo of Usage

Use the resizer in the middle to see the calendar change layout depending on how much space it has. It has three breakpoints of its own.

Container Queries (Style)

What are Style Container Queries?

Container Style Queries allow you to apply styles when a given Custom Property has a given value.

.container {
  --variant: 1;

  &.variant2 {
    --variant: 2;
  }
}

@container style(--variant: 1) {
  button { } /* You can't style .container, but can select inside it */
  .other-things { }
}

@container style(--variant: 2) {
  button { }
  .whatever { }
}

When should you care?

Have you ever wanted a mixin in CSS? As in, you set one property but get multiple properties. Sass made mixins fairly popular. You can do that with a Style Container Query. But just like how Sass had variables then CSS variables turned out to be more powerful and useful, Style Container Queries are likely to be more powerful and useful, because they respect the cascade and can be calculated and such.

Support

Browser Support✅ Chrome ‘n’ Friends
🔜 Safari
❌ Firefox
Progressive Enhancement?Potentially — It depends on what you’re doing with the styles, but let’s say not really.
PolyfillableNo

Basic Demo of Usage

Container Units

What are Container Units?

Container Units (literally units, like px, rem, or vw) allow you to set the size of things based on the current size of a container element. Similar to how with viewport units 1vw is 1% of the browser window width, 1cqw is 1% of the width of the container (although I’d recommend you use cqi instead, the “logical equivalent”, meaning the “inline direction”).

The units are cqw (“container query width”), cqh (“container query height”), cqi (“container query inline”), cqb (“container query block”), cqmin (smaller of cqi and cqb), and cqmax (larger of cqi and cqb).

When should you care?

If the sizing of anything in an element feels as if it should be based on the current size of the container, container units are essentially the only way. An example of this is typography. A typical Card element may deserve a larger header text when it happens to be rendered larger, without something like a class name needing to be added to control that. (I’m a fan of this demo.) Even a container query is clunky comparatively.

Support

Browser SupportFull
Progressive Enhancement?Yes — you could list a declaration using fallback units right before the declaration using container query units.
PolyfillableYes

Basic Demo of Usage

This element uses container query units for not only the font-size, but the padding, border, and margin as well.

The :has() Pseudo Selector

What is the :has() selector?

The :has() selector allows you to conditionally select an element when elements deeper in the DOM tree of the original element match the selector you put inside :has().

figure:has(figcaption) {
  border: 1px solid black;
  padding: 0.5rem;
}

When should you care?

If you’ve ever wanted a “parent” selector in CSS, :has() can do that, but it’s more powerful than that, as once you’ve selected the parent you want, you can again drill back down. Jhey Tompkins once called it a “family selector” which a nice way to think about it. You can also combine it with :not() to build a selector when an element doesn’t “have” a matching element inside.

Support

Browser SupportFull
Progressive Enhancement?Depends on what you’re doing with the styles, but let’s say not really.
PolyfillableFor the JavaScript side only

Basic Demo of Usage

View Transitions

What are View Transitions?

There are two types of View Transitions:

  1. Same-Page Transitions (Require JavaScript)
  2. Multi-Page Transitions (CSS Only)

They are both useful. A same-page transition involves and animation when the DOM is changed without the page changing, like a list being sorted. A multi-page transition is for animating elements between page loads, like a video thumbnail transitioning into a video element. This is the basic syntax for a same-page transition:

if (!document.startViewTransition) {
  updateTheDOM();
} else {
  document.startViewTransition(() => updateTheDOM());
}

For multi-page transitions: you need this meta tag:

<meta name="view-transition" content="same-origin">

Then any element you want to transition between pages you make sure has a totally unique view-transition-name applied in the styles, on both the outgoing page and incoming page.

When should you care?

Users can understand an interface better if an element moves to a new position rather than instantly being there. There is an animation concept called tweening where the animation is automatically created based on a starting and ending state. View Transitions are essentially tweening. You can control aspects of the animation, but for the most part the animation is automatically created based on the starting and ending state of the DOM, rather than you having to be really specific about the animation details.

Support

Browser Support✅ Chrome ‘n’ Friends
❌ Safari
❌ Firefox
Progressive Enhancement?Yes — the transitions can just not run, or you could provide a fallback animation.
PolyfillableNo

Basic Demo of Usage

This is an example of a same-page view transition:

Nesting

What is nesting?

Nesting is a way of writing CSS that allow you to write additional selectors within an existing ruleset.

.card {
  padding: 1rem;

  > h2:first-child {
    margin-block-start: 0;
  }

  footer {
    border-block-start: 1px solid black;
  }
}

When should you care?

Nesting is mostly a CSS authoring convenience, but the fact that it can group related CSS nicely together and prevent you from having to repeat writing a selector can mean avoiding mistakes and making the CSS easier to read. Nested CSS can also be something of a footgun in that may encourage writing CSS that matches the nesting of HTML in an unnecessary way, increasing the specificity and decreasing the reusability of some CSS.

.card {
  container: card / inline-size;
  
  display: grid;
  gap: 1rem;
  
  @container (min-inline-size: 250px) {
    gap: 2rem;
  }
}

The only major difference from Sass-style nesting is that you can’t combine the & directly.

.card {
  body.home & { /* totally fine */ }
  & .footer { /* totally fine, don't even need the & */
  &__big { /* nope, can't do this */ }
}

Support

Browser SupportFull
Progressive Enhancement?No
PolyfillableYou could use a processor like LightningCSS, Sass, Less, etc.

Scroll-Driven Animations

What are Scroll-Driven Animations?

Any animation that is tied to the scrolling of an element (often the page itself) can now be done in CSS rather than needing to bind DOM scrolling events in JavaScript. They come in two varieties:

  • The scroll progress of the element. (animation-timeline: scroll())
  • An element’s current viewable position within the element. (animation-timeline: view())

When should you care?

Imagine a reading progress indicator bar that fills from 0% to 100% as the user scrolls down the page. That can be done with an animation moving the background-position of an element tried to the overall scroll position of the page. Doing this in CSS instead of JavaScript is good for performance.

The other major use case covered by scroll-driven animations is to run an animation as an element enters (or leaves!) the viewport. You have lots of control over the details, like when the animation starts and ends based on how visible the element is.

Support

Browser Support✅ Chrome ‘n’ Friends
❌ Safari
🔜 Firefox
Progressive Enhancement?Yes — these effects tend to be visual flair, not required functionality.
PolyfillableYes

Basic Example of Usage

This is the demo from when we looked at image zooming and page scrolling.

Anchor Positioning

What is Anchor Positioning?

Anchor positioning allows you to place items relative to where another element is. Seems pretty obvious when put like that, but that’s what it is. You declare an element an anchor and give it a name, then can position elements to the top/right/bottom/left (or center, or the logical equivalents) of the anchor.

When should you care?

Once you can use this freely, you’ll have to care less about exact DOM positioning of elements (aside from accessibility concerns). The way it is now, the element you want to position relative to another has to be a child element and for there to be a positioning context to work within. This can dictate where elements go in the DOM, whether or not that makes sense.

The big use cases are going to be tooltips and custom context menus.

Support

Browser Support🔜 Chrome ‘n’ Friends
❌ Safari
❌ Firefox
Progressive Enhancement?Possibly — if you can tolerate a totally different position for elements.
PolyfillableYes

Basic Example of Usage

At the time I’m publishing this, this only works in Chrome Canary with the “Experimental Web Platform Features” flag enabled.

Scoping

What is Scoped CSS?

Scoping in CSS is in the form of an @scope at-rule that declares a block of CSS to only apply to the given selector. And optionally, stop applying at another given selector.

When should you care?

You can also scope CSS by applying a class and nesting within that class. But @scope has a few tricks up it’s sleeve that can make it interesting. The “donut scope” option is a unique ability it has:

@scope (.card) to (.markdown-output) {
  h2 {
    background: tan; /* stop doing this when we get to the Markdown */
  }
}

More logical proximity styling is another useful feature. This is a bit tricky to explain but once you see it you can’t unsee it. Consider theming. You have a .dark selector and a .light selector. If you only ever use one on the entire page, that’s fine, but if you end up nesting them at all, because they have the same specificity, whichever one you define later is technically a bit more powerful, and can win out even if it doesn’t make sense to. Minimal example:

.purple-paragraphs p { color: purple; }
.red-paragraphs p { color: red; }
<div class="purple-paragraphs">
  <div class="red-paragraphs">
    <div class="purple-paragraphs">
       <p>some text</p>
    </div>
  </div>
</div>

You might think the paragraph element in there would have the color purple, but it will actually be red. That’s just awkward, but it can be fixed with @scope. When scoped selectors match, as Bramus says, “it weighs both selectors by proximity to their scoping root”, and since “light” is closer here, it would win.

My favorite though is the ability to drop in a <style> tag in the DOM and have it apply scoped styles only to that bit of the DOM, without having to name anything.

<div class="my-cool-component">
  <style>
    @scope {
      :scope { /* selects the div above, without having to select it by class or anything */
      }
      .card {
      }
    }
  </style>

  <article class="card">
  </article>
</div>

Support

Browser Support✅ Chrome
✅ Safari
❌ Firefox
Progressive Enhancement?No
PolyfillableNo

Basic Example of Usage

Cascade Layers

What are Layers?

Cascade Layers in CSS are an extremely powerful syntax that affects the styling strength of a chunk of styles. You can optionally name and order layers (if you don’t explicitly order them, they order in source order). Styles in higher layers automatically beat styles in lower layers, regardless of selector strength. Styles not within layers are the most powerful.

<body id="home">
@layer base {
  body#home {
    margin: 0;
    background: #eee;
  }
}

body {
  background: white;
}

We’re used to thinking that body#home is a much more powerful selector, thus the background will be #eee. But because there are unlayered styles here, that will win, making the background white.

You may have as many layers as you like and can order them upfront. I think layering is likely to become a best practice on new greenfield projects, and take shape something like:

@layer reset, default, themes, patterns, layouts, components, utilities;

One gotcha is that !important rules on lower layers are actually more powerful.

When should you care?

One clear way you get a lot of value out of CSS layers if you work on a project that uses a third-party styling library. You can put that library on a lower layer than the styles that your team writes, and you won’t have to worry about fighting the third-party library in terms of selector strength. Your styles on a higher layer will always win, which is likely to create cleaner and more maintainable CSS.

For example, put all of Bootstrap on a lower layer just using the layer keyword and then any styles you write after that will win, even if Bootstrap itself uses a higher power selector.

@import url("https://cdn.com/bootstrap.css") layer;

h5 {
  margin-bottom: 2rem;
}

Support

Browser SupportFull
Progressive Enhancement?No
PolyfillableYes

Basic Example of Usage

Logical Properties

What are Logical Properties?

Logical properties are alternatives to properties that specify a direction. For example, in a left-to-right language like English, the inline direction is horizontal and the block direction is vertical, so margin-right is equivalent to margin-inline-end and margin-top is equivelant to margin-block-start. In a right-to-left language like Arabic, margin-inline-end changes to the equivalent of margin-left, because that is the end side of the inline flow of elements. There are a lot of CSS properties and values that have a directional component like this (border, padding, offset, set), so the trick is understanding inline and block flow and using the correct start or end value.

When should you care?

Often when you are declaring directional information in CSS, what you mean is “in the inline direction of text”. That might sound strange, but imagine a button and the space between an icon and the text. If you apply margin-right to the icon to space it away from the text, but then the page is translated to a right-to-left language, that spacing is now on the wrong side of the icon. What you meant was margin-inline-end on the icon. If you code your side using logical properties in this way, it will automatically translate better without writing any additional conditional code.

Support

Browser SupportFull
Progressive Enhancement?You’d have to use @supports and unset to remove the fallback value and reset using a logical property, but it’s possible.
PolyfillableI can’t vouch for it, but there is a processing option.

Basic Example of Usage

P3 Colors

What is the Display P3 Color Space?

We’re largely used to the sRGB color space on the web. That’s what hex colors use, and the rgb(), hsl(), and hsb() functions. Many displays these days are capable of display a much wider range of colors than sRGB is capable of describing, so being limited to that color space sucks. The Display P3 color space is about 50% wider than sRGB, expanding in the direction of more rich and vibrant colors. New CSS functions, which can even use different color models that have their own useful properties, allow us to declare colors in this space.

When should you care?

If you want to use colors that are quite vibrant, you’ll need to tap into colors in the P3 Color Space. Using newer color models (and functions) can do this, and are very useful for a variety of other things.

For example, the oklch() function (and thus OKLCH color model) can display any color any other method can (plus P3), has a similar human readability in common with hsl(), and has “uniform perceived brightness”, so that the first number (lightness) behaves way more predictably than it does in hsl(). That’s awfully nice for color on the web. But it’s not the only new color model and function! I find the oklab color model generally best for gradients.

Support

Browser SupportFull (e.g. oklab)
Progressive Enhancement?Yes — you can declare fallback colors and displays that can’t display the color you declare will come back down into range.
PolyfillableYes

Basic Example of Usage

You can edit these <style> blocks because I made them display: block; and contenteditable:

Color Mixing

What is color-mix()?

The color-mix() function in CSS allows you to, wait for it, mix colors. This kind of thing has been baked into CSS processing tools for a long time, and as typical of CSS evolution, now that it’s in native CSS, it’s more thoughtful and powerful than it ever was in a processor.

When you should care?

Have you ever wanted to darken or lighten a color you already have on the fly? That’s one of the things color-mix() can do. Color mixing can happen in a specific color model which means you can take advantage of how that models works. For example, the perceptually uniform brightness of OKLCH makes it sensible to use for adjusting brightness. You can make whole color palettes using color-mix().

Browser Support

Browser SupportFull
Progressive Enhancement?Yes, you could declare fallback colors.
PolyfillableCould be but I don’t know of one.

Basic Example of Usage

Margin Trim

What is margin-trim?

The margin-trim property removes any margin in the direction specified from the selected container at the end of that direction. Imagine you have five blocks in a row that all have right margin on them in a container. You might select the :last-child to remove the right margin. With margin-trim you can ensure that margin is removed from the parent element itself.

.container {
  /* prevent "extra" margin at the end of the element */
  margin-trim: block-end;

  /* an element like this might be the culprit, but it could be anything */
  > p {
    margin-block-end: 1rem;
  }
}

When should you care?

You know how the gap property of flexbox and grid is… awesome? It only puts spacing between elements. Well, if you need to apply spacing between elements but you’re in a position where you can’t use gap, margin-trim is awfully nice as it means you apply directional margin to all the children and not worry about an additional fancy selector to select the first or last one and remove that unneeded final margin. It might end up a best practice.

Support

Browser Support✅ Safari
❌ Chrome
❌ Firefox
Progressive Enhancement?Yes. A little extra space likely isn’t a major problem.
PolyfillableNo

Basic Example of Usage

The last paragraph here is a notorious situation where the bottom margin on it creates more space at the bottom than any of the other edges. With margin-trim we can ensure it’s sliced off without having to select that last paragraph and manually remove it.

Text Wrapping

What is text-wrap?

The text-wrap property likely isn’t in your long term CSS memory. It’s capable of text-wrap: nowrap;, but we generally think of white-space: nowrap; for this. But now, text-wrap has two new tricks up it’s sleeve:

  • text-wrap: balance; — Attempt to make equal-width lines when text wraps.
  • text-wrap: pretty; — Avoid orphans.

When should you care?

A headline with one word orphaned onto the next line just looks really awkward and could be considered poor typography. There wasn’t a great way to solve this before, short of somewhat awkward tricks like inserting a &nbsp; instead of a normal space between the last two words. Balancing a headline prevents this, but goes further in making the multiple lines of text generally the same width. Using pretty is more focused just on orphan prevention alone, making it more appropriate for body text.

Support

Browser SupportDepends on which value. balance has decent support with all browsers ready or coming soon. pretty, less-so.
Progressive Enhancement?Yes. While slightly less aesthetic, widows and orphans are not that big of a problem, so if this property doesn’t work, it’s no big deal.
PolyfillableYes.

Basic Example of Usage

Subgrid

What is Subgrid?

Subgrid is an optional part of using CSS grid that is relevant when you are nesting gridded elements. By setting grid-template-columns: subgrid; or grid-template-rows: subgrid; on a grid-level element, you’re saying “inherit these columns or rows from my parent grid, where relevant”.

When should you care?

The point of using grids for layout is generally lining things up. Without subgrid, it means that child elements of a grid don’t have access to the grid lines of the parent grid, and thus lack the opportunity help line things up. Subgrid fills that gap. When DOM nesting is important for functionality or accessibility, like in a <form>, subgrid can help ensure things line up sensibly.

Support

Browser SupportFull
Progressive Enhancement?Yes. You can fall back to defining your own grid lines that are workable if not perfect.
PolyfillableNo. There is a grid polyfill but it doesn’t do subgrid.

Basic Example of Usage

Things to keep an eye on…

The speed of CSS development doesn’t seem to have slowed down. There is plenty to continue to watch for and look forward to.

  • CSS Mixins & Functionsactual mixins and functions that take parameters
  • Relative Color Syntax — a way to manipulate the parts of colors in an intuitive and powerful way.
  • Interop 2024 — All the things that we can essentially bet on for being cross-browser compatible soon, including the relative color syntax above.
  • The CSS property field-sizing should help the long-standing difficult issue of auto-resizing form elements like textareas and input to the content they contain.
  • <selectmenu> in HTML is essentially a fully CSS styleable <select>, which is wild.

That’s just a few things to watch. You might as well subscribe to our feed as we’ll be doing the watching for you and then you’ll catch the next one.

Did I miss a relatively new favorite of yours? Let me know.

]]>
https://frontendmasters.com/blog/what-you-need-to-know-about-modern-css-spring-2024-edition/feed/ 6 1013
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
Chill Scroll Snapping: Article Headers https://frontendmasters.com/blog/chill-scroll-snapping-article-headers/ https://frontendmasters.com/blog/chill-scroll-snapping-article-headers/#respond Tue, 19 Mar 2024 22:44:42 +0000 https://frontendmasters.com/blog/?p=1295 CSS has a feature called scroll snapping. A lot of the demos and examples, rightfully so, focus around things that benefit highly from it. For instance, an image slider, carousel, or grid of things that just beg to be aligned after scrolling.

But you don’t have to be in such a strict and rigid situation for scroll snapping to just be a nice touch. Instead you can think: is there anything on this page that would look or feel nice if when you scrolled nearby it would snap into place?

Maybe you have one of those “full viewport filling” headers to a page. If you added a scroll snapping point to the first element after that (e.g. .article-header + :first-child) then you could help users scroll right to it, whooshing past the giant header. Like:

Love it? Hate it? It’s just an idea. Some people really bristle against things that seem to take scrolling control away from them.

We could go a bit more chill.

What if just the headers of an article had scroll snapping points? That looks something like this, by the way:

html {
  scroll-snap-type: y mandatory;
}

main {
  h2, h3 {
    scroll-snap-stop: normal;
    scroll-snap-align: start;
  }
}

Now you’ve got these headers that just so happen to line up very nicely at the top of the screen when you get close to them:

Gotta tell ya there: don’t hate it.

Here’s a demo where the headers are scroll snap points, plus the header itself, plus the very first thing after the header. I’ve also used a smidge of scroll-margin-block-start to push the snap point a little bit away from the header giving it some breathing room.

main {
  max-width: 60ch;
  margin-inline: auto;
  h1,
  h2,
  > h1 + * {
    scroll-snap-stop: normal;
    scroll-snap-align: start;
  }
  > h1 + *,
  h2 {
    scroll-margin-block-start: 0.5rem;
  }
}

html {
  scroll-snap-type: y mandatory;
}

My main point isn’t “do exactly this”, it’s just that thinking about scroll snapping points in a design doesn’t have to be relegated to carousels. There is likely smaller and more chill opportunities to slap in snap point just to make something feel a bit more polished.

]]>
https://frontendmasters.com/blog/chill-scroll-snapping-article-headers/feed/ 0 1295