Tech News

Pop(over) the Balloons

Css Tricks - Thu, 07/25/2024 - 3:31am

I’ve always been fascinated with how much we can do with just HTML and CSS. The new interactive features of the Popover API are yet another example of just how far we can get with those two languages alone.

You may have seen other tutorials out there showing off what the Popover API can do, but this is more of a beating-it-mercilessly-into-submission kind of article. We’ll add a little more pop music to the mix, like with balloons… some literal “pop” if you will.

What I’ve done is make a game — using only HTML and CSS, of course — leaning on the Popover API. You’re tasked with popping as many balloons as possible in under a minute. But be careful! Some balloons are (as Gollum would say) “tricksy” and trigger more balloons.

I have cleverly called it Pop(over) the Balloons and we’re going to make it together, step by step. When we’re done it’ll look something like (OK, exactly like) this:

CodePen Embed Fallback Handling the popover attribute

Any element can be a popover as long as we fashion it with the popover attribute:

<div popover>...</div>

We don’t even have to supply popover with a value. By default, popover‘s initial value is auto and uses what the spec calls “light dismiss.” That means the popover can be closed by clicking anywhere outside of it. And when the popover opens, unless they are nested, any other popovers on the page close. Auto popovers are interdependent like that.

The other option is to set popover to a manual value:

<div popover=“manual”>...</div>

…which means that the element is manually opened and closed — we literally have to click a specific button to open and close it. In other words, manual creates an ornery popup that only closes when you hit the correct button and is completely independent of other popovers on the page.

CodePen Embed Fallback Using the <details> element as a starter

One of the challenges of building a game with the Popover API is that you can’t load a page with a popover already open… and there’s no getting around that with JavaScript if our goal is to build the game with only HTML and CSS.

Enter the <details> element. Unlike a popover, the <details> element can be open by default:

<details open> <!-- rest of the game --> </details>

If we pursue this route, we’re able to show a bunch of buttons (balloons) and “pop” all of them down to the very last balloon by closing the <details>. In other words, we can plop our starting balloons in an open <details> element so they are displayed on the page on load.

This is the basic structure I’m talking about:

<details open> <summary>&#x1f388;</summary> <button>&#x1f388;</button> <button>&#x1f388;</button> <button>&#x1f388;</button> </details>

In this way, we can click on the balloon in <summary> to close the <details> and “pop” all of the button balloons, leaving us with one balloon (the <summary> at the end (which we’ll solve how to remove a little later).

You might think that <dialog> would be a more semantic direction for our game, and you’d be right. But there are two downsides with <dialog> that won’t let us use it here:

  1. The only way to close a <dialog> that’s open on page load is with JavaScript. As far as I know, there isn’t a close <button> we can drop in the game that will close a <dialog> that’s open on load.
  2. <dialog>s are modal and prevent clicking on other things while they’re open. We need to allow gamers to pop balloons outside of the <dialog> in order to beat the timer.

Thus we will be using a <details open> element as the game’s top-level container and using a plain ol’ <div> for the popups themselves, i.e. <div popover>.

All we need to do for the time being is make sure all of these popovers and buttons are wired together so that clicking a button opens a popover. You’ve probably learned this already from other tutorials, but we need to tell the popover element that there is a button it needs to respond to, and then tell the button that there is a popup it needs to open. For that, we give the popover element a unique ID (as all IDs should be) and then reference it on the <button> with a popovertarget attribute:

<!-- Level 0 is open by default --> <details open> <summary>&#x1f388;</summary> <button popovertarget="lvl1">&#x1f388;</button> </details> <!-- Level 1 --> <div id="lvl1" popover="manual"> <h2>Level 1 Popup</h2> </div>

This is the idea when everything is wired together:

CodePen Embed Fallback Opening and closing popovers

There’s a little more work to do in that last demo. One of the downsides to the game thus far is that clicking the <button> of a popup opens more popups; click that same <button> again and they disappear. This makes the game too easy.

We can separate the opening and closing behavior by setting the popovertargetaction attribute (no, the HTML spec authors were not concerned with brevity) on the <button>. If we set the attribute value to either show or hide, the <button> will only perform that one action for that specific popover.

<!-- Level 0 is open by default --> <details open> <summary>&#x1f388;</summary> <!-- Show Level 1 Popup --> <button popovertarget="lvl1" popovertargetaction="show">&#x1f388;</button> <!-- Hide Level 1 Popup --> <button popovertarget="lvl1" popovertargetaction="hide">&#x1f388;</button> </details> <!-- Level 1 --> <div id="lvl1" popover="manual"> <h2>Level 1 Popup</h2> <!-- Open/Close Level 2 Poppup --> <button popovertarget="lvl2">&#x1f388;</button> </div> <!-- etc. -->

Note, that I’ve added a new <button> inside the <div> that is set to target another <div> to pop open or close by intentionally not setting the popovertargetaction attribute on it. See how challenging (in a good way) it is to “pop” the elements:

CodePen Embed Fallback Styling balloons

Now we need to style the <summary> and <button> elements the same so that a player cannot tell which is which. Note that I said <summary> and not <details>. That’s because <summary> is the actual element we click to open and close the <details> container.

Most of this is pretty standard CSS work: setting backgrounds, padding, margin, sizing, borders, etc. But there are a couple of important, not necessarily intuitive, things to include.

  • First, there’s setting the list-style-type property to none on the <summary> element to get rid of the triangular marker that indicates whether the <details> is open or closed. That marker is really useful and great to have by default, but for a game like this, it would be better to remove that hint for a better challenge.
  • Safari doesn’t like that same approach. To remove the <details> marker here, we need to set a special vendor-prefixed pseudo-element, summary::-webkit-details-marker to display: none.
  • It’d be good if the mouse cursor indicated that the balloons are clickable, so we can set cursor: pointer on the <summary> elements as well.
  • One last detail is setting the user-select property to none on the <summary>s to prevent the balloons — which are simply emoji text — from being selected. This makes them more like objects on the page.
  • And yes, it’s 2024 and we still need that prefixed -webkit-user-select property to account for Safari support. Thanks, Apple.

Putting all of that in code on a .balloon class we’ll use for the <button> and <summary> elements:

.balloon { background-color: transparent; border: none; cursor: pointer; display: block; font-size: 4em; height: 1em; list-style-type: none; margin: 0; padding: 0; text-align: center; -webkit-user-select: none; /* Safari fallback */ user-select: none; width: 1em; } CodePen Embed Fallback

One problem with the balloons is that some of them are intentionally doing nothing at all. That’s because the popovers they close are not open. The player might think they didn’t click/tap that particular balloon or that the game is broken, so let’s add a little scaling while the balloon is in its :active state of clicking:

.balloon:active { scale: 0.7; transition: 0.5s; }

Bonus: Because the cursor is a hand pointing its index finger, clicking a balloon sort of looks like the hand is poking the balloon with the finger. &#x1f449;&#x1f388;&#x1f4a5;

The way we distribute the balloons around the screen is another important thing to consider. We’re unable to position them randomly without JavaScript so that’s out. I tried a bunch of things, like making up my own “random” numbers defined as custom properties that can be used as multipliers, but I couldn’t get the overall result to feel all that “random” without overlapping balloons or establishing some sort of visual pattern.

I ultimately landed on a method that uses a class to position the balloons in different rows and columns — not like CSS Grid or Multicolumns, but imaginary rows and columns based on physical insets. It’ll look a bit Grid-like and is less “randomness” than I want, but as long as none of the balloons have the same two classes, they won’t overlap each other.

I decided on an 8×8 grid but left the first “row” and “column” empty so the balloons are clear of the browser’s left and top edges.

/* Rows */ .r1 { --row: 1; } .r2 { --row: 2; } /* all the way up to .r7 */ /* Columns */ .c1 { --col: 1; } .c2 { --col: 2; } /* all the way up to .c7 */ .balloon { /* This is how they're placed using the rows and columns */ top: calc(12.5vh * (var(--row) + 1) - 12.5vh); left: calc(12.5vw * (var(--col) + 1) - 12.5vw); } CodePen Embed Fallback Congratulating The Player (Or Not)

We have most of the game pieces in place, but it’d be great to have some sort of victory dance popover to congratulate players when they successfully pop all of the balloons in time.

Everything goes back to a <details open> element. Once that element is not open, the game should be over with the last step being to pop that final balloon. So, if we give that element an ID of, say, #root, we could create a condition to hide it with display: none when it is :not() in an open state:

#root:not([open]) { display: none; }

This is where it’s great that we have the :has() pseudo-selector because we can use it to select the #root element’s parent element so that when #root is closed we can select a child of that parent — a new element with an ID of #congrats — to display a faux popover displaying the congratulatory message to the player. (Yes, I’m aware of the irony.)

#game:has(#root:not([open])) #congrats { display: flex; }

If we were to play the game at this point, we could receive the victory message without popping all the balloons. Again, manual popovers won’t close unless the correct button is clicked — even if we close its ancestral <details> element.

Is there a way within CSS to know that a popover is still open? Yes, enter the :popover-open pseudo-class.

The :popover-open pseudo-class selects an open popover. We can use it in combination with :has() from earlier to prevent the message from showing up if a popover is still open on the page. Here’s what it looks like to chain these things together to work like an and conditional statement.

/* If #game does *not* have an open #root * but has an element with an open popover * (i.e. the game isn't over), * then select the #congrats element... */ #game:has(#root:not([open])):has(:popover-open) #congrats { /* ...and hide it */ display: none; }

Now, the player is only congratulated when they actually, you know, win.

Conversely, if a player is unable to pop all of the balloons before a timer expires, we ought to inform the player that the game is over. Since we don’t have an if() conditional statement in CSS (not yet, at least) we’ll run an animation for one minute so that this message fades in to end the game.

#fail { animation: fadein 0.5s forwards 60s; display: flex; opacity: 0; z-index: -1; } @keyframes fadein { 0% { opacity: 0; z-index: -1; } 100% { opacity: 1; z-index: 10; } }

But we don’t want the fail message to trigger if the victory screen is showing, so we can write a selector that prevents the #fail message from displaying at the same time as #congrats message.

#game:has(#root:not([open])) #fail { display: none; } We need a game timer

A player should know how much time they have to pop all of the balloons. We can create a rather “simple” timer with an element that takes up the screen’s full width (100vw), scaling it in the horizontal direction, then matching it up with the animation above that allows the #fail message to fade in.

#timer { width: 100vw; height: 1em; } #bar { animation: 60s timebar forwards; background-color: #e60b0b; width: 100vw; height: 1em; transform-origin: right; } @keyframes timebar { 0% { scale: 1 1; } 100% { scale: 0 1; } }

Having just one point of failure can make the game a little too easy, so let’s try adding a second <details> element with a second “root” ID, #root2. Once more, we can use :has to check that neither the #root nor #root2 elements are open before displaying the #congrats message.

#game:has(#root:not([open])):has(#root2:not([open])) #congrats { display: flex; } Wrapping up

The only thing left to do is play the game!

CodePen Embed Fallback

Fun, right? I’m sure we could have built something more robust without the self-imposed limitation of a JavaScript-free approach, and it’s not like we gave this a good-faith accessibility pass, but pushing an API to the limit is both fun and educational, right?

I’m interested: What other wacky ideas can you think up for using popovers? Maybe you have another game in mind, some slick UI effect, or some clever way of combining popovers with other emerging CSS features, like anchor positioning. Whatever it is, please share!

Pop(over) the Balloons originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Love Notes

Typography - Tue, 07/23/2024 - 4:01pm

Read the book, Typographic Firsts

Introducing love notes for some of our favorite fonts...

The post Love Notes appeared first on I Love Typography.

A Proliferation of Terms

LukeW - Mon, 07/22/2024 - 2:00pm

When working through the early stages of a product design, it's common that labels for objects and actions emerge organically. No one is overly concerned about making these labels consistent (yet). But if this proliferation of terms doesn't get reined in early, both product design and strategy get harder.

Do we call it a library, a folder, a collection, a workspace, a section, a category, a topic? How about a document, page, file, entry, article, worksheet? And.. what's the difference? While these kinds of decisions might not be front and center when working out designs for a product or feature, they can impact a lot.

For starters, having clear definitions for concepts helps keep teams on the same page. When engineering works on implementing a new object type, they're aligned with what design is thinking, which is what the sales team is pitching potential customers on. Bringing a product to life is hard enough, why complicate things by using different terms for similar things or vice versa?

Inconsistent terms are obviously also a comprehension issue for the people using our products. "Here's it's called a Document, there it's called an Article. Are those the same?" Additionally, undefined terms often lead to miscellaneous bins in our user interfaces. "What's inside Explore?" When the definition of objects and actions isn't clear, what choice do we have but to drop them into vague sounding containers like Discover?

The more a product gets developed (especially by bigger teams) the more things can diverge because people's mental model of what terms mean can vary a lot. So it's really useful to proactively put together a list of the objects and actions that make up an application and draft some simple one-liner definitions for each. These lists almost always kick off useful high-level discussions within teams on what we're building and for who. Being forced to define things requires you to think them through: what is this feature doing and why?

And of course, consistent labels also ease comprehension for users. Once people learn what something means, they'll be able to apply that knowledge elsewhere -instead of having to contend with mystery meat navigation.

Alvaro Montoro: CSS One-Liners to Improve (Almost) Every Project

Css Tricks - Mon, 07/22/2024 - 4:38am

These sorts of roundups always get me. My wife will flip through Zillow photos of the insides of homes for hours because she likes seeing how different people decorate, Feng Shui, or what have you. That’s her little dip into Voyeur-Land. Mine? It could easily be scrolling through CSS snippets that devs keep within arm’s reach.

Alvaro was kind enough to share the trustiest of his trusty CSS:

  1. Limit the content width within the viewport
  2. Increase the body text size
  3. Increase the line between rows of text
  4. Limit the width of images
  5. Limit the width of text within the content
  6. Wrap headings in a more balanced way
  7. Form control colors to match page styles
  8. Easy-to-follow table rows
  9. Spacing in table cells and headings
  10. Reduce animations and movement

Not dropping the snippets in here (it’s worth reading the full post for that). But I do have a couple of my own that I’d tack on. And like Alvaro says up-front about his list, not all of these will be 100% applicable to every project.

Global border-box sizing

No explanation needed here. It’s often the very first thing declared in any given stylesheet on the web.

*, *::before, *::after { box-sizing: border-box; }

I’m guessing Alvaro uses this, too, and maybe it’s too obvious to list. Or maybe it’s more of a DX enhancement that belongs in a reset more than it is something that improves the website.

System fonts

Default text on the web is just so… so… so blah. I love that Alvaro agrees that 16px is way too small to be the web’s default font-size for text. I would take that one step further and wipe out the Times New Roman default font as well. I’m sure there are sites out there leveraging it (I did on my own personal site for years as an act of brutal minimalism), but a personal preference these days is defaulting to whatever the OS default font is.

body { font-family: system-ui; }

We can be a little more opinionated than that by falling back to either a default serif or sans-serif font.

body { font-family: system-ui, sans-serif; }

There are much, much more robust approaches for sure, but this baseline is a nice starting point for just about any site.

Cut horizontal overflow from the <body>

Oh gosh, I never ever make this mistake. &#x1f61d;

But hypothetically, if I did — and that’s a BIG if — I like preventing it from messing with a visitor’s scrolling experience. Once the <body>‘s intrinsic width is forced outside the viewport, we get horizontal scrolling that might be a very cool thing if it’s intentional but is not-so-bueno when it’s not.

body { overflow-x: hidden; }

I’ll use this as a defensive mechanism but would never want to rely on it as an actual solution to the possible loss of data that comes with overflowing content. This merely masks the problem while allowing an opportunity to fix the root cause without visitors having to deal with the rendered consequences.

Give the <body> some breathing room

Not too much, not too little, but the baby bear porridge just the right amount of space to keep content from hugging right up to the edges.

body { padding-block: 15px; }

To Shared LinkPermalink on CSS-Tricks

Alvaro Montoro: CSS One-Liners to Improve (Almost) Every Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Sara Joy: Everybody’s Free (To Write Websites)

Css Tricks - Wed, 07/17/2024 - 8:36am

Sara Joy’s adaptation of the song “Everybody’s Free (To Wear Sunscreen)” (YouTube) originally by Baz Luhrman with lyrics pulled directly from Mary Schmich‘s classic essay, “Wear Sunscreen”. Anyone who has graduated high school since 1999 doesn’t even have to look up the song since it’s become an unofficial-official commencement ceremony staple. If you graduated in ’99, then I’m sorry. You might still be receiving ongoing treatment for the earworm infection from that catchy tune spinning endlessly on radio (yes, radio). Then again, those of us from those late-90’s classes came down with more serious earworm cases from the “I Will Remember You” and “Time of Your Life” outbreaks.

Some choice pieces of Sara’s “web version”:

Don’t feel guilty if you don’t know what you want to do with your site. The most interesting websites don’t even have an introduction, never mind any blog posts. Some of the most interesting web sites I enjoy just are.

Add plenty of semantic HTML.

Clever play on words and selectors:

Enjoy your <body>. Style it every way you can. Don’t be afraid of CSS, or what other people think of it. It’s the greatest design tool you’ll ever learn.

The time’s they are a-changin’:

Accept certain inalienable truths: connection speeds will rise, techbros will grift, you too will get old— and when you do, you’ll fantasize that when you were young websites were light-weight, tech founders were noble and fonts used to be bigger.

And, of course:

Respect the W3C.

Oh, and remember: Just build websites.

To Shared LinkPermalink on CSS-Tricks

Sara Joy: Everybody’s Free (To Write Websites) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS Selectors

Css Tricks - Mon, 07/15/2024 - 6:13am
Overview

CSS is really good at many things, but it’s really, really good at two specific things: selecting elements and styling them. That’s the raison d’être for CSS and why it’s a core web language. In this guide, we will cover the different ways to select elements — because the styles we write are pretty much useless without the ability to select which elements to apply them to.

The source of truth for CSS selectors is documented in the Selectors Module Level 4 specification. With one exception (which we’ll get to), all of the selectors covered here are well-covered by browsers across the board, and most certainly by all modern browsers.

In addition to selectors, this guide also looks at CSS combinators. If selectors identify what we are selecting, you might think of combinators as how the styles are applied. Combinators are like additional instructions we give CSS to select a very particular element on the page, not totally unlike the way we can use filters in search engines to find the exact result we want.

Quick reference Common Selectors /* Universal */ * { box-sizing: border-box; } /* Type or Tag */ p { margin-block: 1.5rem; } /* Classname */ .class { text-decoration: underline; } /* ID */ #id { font-family: monospace; } /* Relational */ li:has(a) { display: flex; } Common Combinators /* Descendant */ header h1 { /* Selects all Heading 1 elements in a Header element. */ } /* Child */ header > h1 { /* Selects all Heading 1 elements that are children of Header elements. */ } /* General sibling */ h1 ~ p { /* Selects a Paragraph as long as it follows a Heading 1. */ } /* Adjacent sibling */ h1 + p { /* Selects a Paragraph if it immediately follows a Heading 1 */ } /* Chained */ h1, p { /* Selects both elements. */ } General Selectors

When we talk about CSS selectors, we’re talking about the first part of a CSS ruleset:

/* CSS Ruleset */ selector { /* Style rule */ property: value; }

See that selector? That can be as simple as the HTML tag we want to select. For example, let’s select all <article> elements on a given page.

/* Select all <article> elements... */ article { /* ... and apply this background-color on them */ background-color: hsl(25 100% 50%); }

That’s the general process of selecting elements to apply styles to them. Selecting an element by its HTML tag is merely one selector type of several. Let’s see what those are in the following section.

Element selectors

Element selectors are exactly the type of selector we looked at in that last example: Select the element’s HTML tag and start styling!

That’s great and all, but consider this: Do you actually want to select all of the <article> elements on the page? That’s what we’re doing when we select an element by its tag — any and all HTML elements matching that tag get the styles. The following demo selects all <article> elements on the page, then applies a white (#fff) background to them. Notice how all three articles get the white background even though we only wrote one selector.

CodePen Embed Fallback

I’ve tried to make it so the relevant for code for this and other demos in this guide is provided at the top of the CSS tab. Anything in a @layer can be ignored. And if you’re new to @layer, you can learn all about it in our CSS Cascade Layers guide.

But maybe what we actually want is for the first element to have a different background — maybe it’s a featured piece of content and we need to make it stand out from the other articles. That requires us to be more specific in the type of selector we use to apply the styles.

Let’s turn our attention to other selector types that allow us to be more specific about what we’re selecting.

ID selectors

ID selectors are one way we can select one element without selecting another of the same element type. Let’s say we were to update the HTML in our <article> example so that the first article is “tagged” with an ID:

<article id="featured"> <!-- Article 1 --> </article> <article> <!-- Article 2 --> </article> <article> <!-- Article 3 --> </article>

Now we can use that ID to differentiate that first article from the others and apply styles specifically to it. We prepend a hashtag character (#) to the ID name when writing our CSS selector to properly select it.

/* Selects all <article> elements */ article { background: #fff; } /* Selects any element with id="featured" */ #featured { background: hsl(35 100% 90%); border-color: hsl(35 100% 50%); }

There we go, that makes the first article pop a little more than the others!

CodePen Embed Fallback

Before you go running out and adding IDs all over your HTML, be aware that IDs are considered a heavy-handed approach to selecting. IDs are so specific, that it is tough to override them with other styles in your CSS. IDs have so much specificity power than any selector trying to override it needs at least an ID as well. Once you’ve reached near the top of the ladder of this specificity war, it tends to lead to using !important rules and such that are in turn nearly impossible to override.

Let’s rearrange our CSS from that last example to see that in action:

/* Selects any element with id="featured" */ #featured { background: hsl(35 100% 90%); border-color: hsl(35 100% 50%); } /* Selects all <article> elements */ article { background: #fff; }

The ID selector now comes before the element selector. According to how the CSS Cascade determines styles, you might expect that the article elements all get a white background since that ruleset comes after the ID selector ruleset. But that’s not what happens.

CodePen Embed Fallback

So, you see how IDs might be a little too “specific” when it comes to selecting elements because it affects the order in which the CSS Cascade applies styles and that makes styles more difficult to manage and maintain.

The other reason to avoid IDs as selectors? We’re technically only allowed to use an ID once on a page, per ID. In other words, we can have one element with #featured but not two. That severely limits what we’re able to style if we need to extend those styles to other elements — not even getting into the difficulty of overriding the ID’s styles.

A better use case for IDs is for selecting items in JavaScript — not only does that prevent the sort of style conflict we saw above, but it helps maintain a separation of concerns between what we select in CSS for styling versus what we select in JavaScript for interaction.

Another thing about ID selectors: The ID establishes what we call an “anchor” which is a fancy term for saying we can link directly to an element on the page. For example, if we have an article with an ID assigned to it:

<article id="featured">...</article>

…then we can create a link to it like this:

<a href="featured">Jump to article below ⬇️</a> <!-- muuuuuuch further down the page. --> <article id="featured">...</article>

Clicking the link will navigate you to the element as though the link is anchored to that element. Try doing exactly that in the following demo:

CodePen Embed Fallback

This little HTML goodie opens up some pretty darn interesting possibilities when we sprinkle in a little CSS. Here are a few articles to explore those possibilities.

Class selectors

Class selectors might be the most commonly used type of CSS selector you will see around the web. Classes are ideal because they are slightly more specific than element selectors but without the heavy-handedness of IDs. You can read a deep explanation of how the CSS Cascade determines specificity, but the following is an abbreviated illustration focusing specifically (get it?!) on the selector types we’ve looked at so far.

That’s what makes class selectors so popular — they’re only slightly more specific than elements, but keep specificity low enough to be manageable if we need to override the styles in one ruleset with styles in another.

The only difference when writing a class is that we prepend a period (.) in front of the class name instead of the hashtag (#).

/* Selects all <article> elements */ article { background: #fff; } /* Selects any element with class="featured" */ .featured { background: hsl(35 100% 90%); border-color: hsl(35 100% 50%); }

Here’s how our <article> example shapes up when we swap out #featured with .featured.

CodePen Embed Fallback

Same result, better specificity. And, yes, we can absolutely combine different selector types on the same element:

<article id="someID" class="featured">...</article>

Do you see all of the possibilities we have to select an <article>? We can select it by:

  • Its element type (article)
  • Its ID (#someID)
  • Its class (.featured)

The following articles will give you some clever ideas for using class selectors in CSS.

But we have even more ways to select elements like this, so let’s continue.

Attribute selectors

ID and class selectors technically fall into this attribute selectors category. We call them “attributes” because they are present in the HTML and give more context about the element. All of the following are attributes in HTML:

<!-- ID, Class, Data Attribute --> <article id="#id" class=".class" data-attribute="attribute"> </article> <!-- href, Title, Target --> <a href="https://css-tricks.com" title="Visit CSS-Tricks" target="_blank"></a> <!-- src, Width, Height, Loading --> <img src="star.svg" width="250" height="250" loading="laxy" > <!-- Type, ID, Name, Checked --> <input type="checkbox" id="consent" name="consent" checked /> <!-- Class, Role, Aria Label --> <div class="buttons" role="tablist" aria-label="Tab Buttons">

Anything with an equals sign (=) followed by a value in that example code is an attribute. So, we can technically style all links with an href attribute equal to https://css-tricks.com:

a[href="https://css-tricks.com"] { color: orangered; }

Notice the syntax? We’re using square brackets ([]) to select an attribute instead of a period or hashtag as we do with classes and IDs, respectively.

CodePen Embed Fallback

The equals sign used in attributes suggests that there’s more we can do to select elements besides matching something that’s exactly equal to the value. That is indeed the case. For example, we can make sure that the matching selector is capitalized or not. A good use for that could be selecting elements with the href attribute as long as they do not contain uppercase letters:

/* Case sensitive */ a[href*='css-tricks' s] {}

The s in there tells CSS that we only want to select a link with an href attribute that does not contain uppercase letters.

<!-- &#x1f44e; No match --> <a href="https://CSS-Tricks.com">...</a> <!-- &#x1f44d; Match! --> <a href="https://css-tricks.com">...</a>

If case sensitivity isn’t a big deal, we can tell CSS that as well:

/* Case insensitive */ a[href*='css-tricks' i] {}

Now, either one of the link examples will match regardless of there being upper- or lowercase letters in the href attribute.

<!-- &#x1f44d; I match! --> <a href="https://CSS-Tricks.com">...</a> <!-- &#x1f44d; I match too! --> <a href="https://css-tricks.com">...</a>

There are many, many different types of HTML attributes. Be sure to check out our Data Attributes guide for a complete rundown of not only [data-attribute] but how they relate to other attributes and how to style them with CSS.

Universal selector

CSS-Tricks has a special relationship with the Universal Selector — it’s our logo!

That’s right, the asterisk symbol (*) is a selector all unto itself whose purpose is to select all the things. Quite literally, we can select everything on a page — every single element — with that one little asterisk. Note I said every single element, so this won’t pick up things like IDs, classes, or even pseudo-elements. It’s the element selector for selecting all elements.

/* Select ALL THE THINGS! &#x1f4a5; */ * { /* Styles */ }

Or, we can use it with another selector type to select everything inside a specific element.

/* Select everything in an <article> */ article * { /* Styles */ }

That is a handy way to select everything in an <article>, even in the future if you decide to add other elements inside that element to the HTML. The times you’ll see the Universal Selector used most is to set border-sizing on all elements across the board, including all elements and pseudo-elements.

*, *::before, *::after { box-sizing: border-box; }

There’s a good reason this snippet of CSS winds up in so many stylesheets, which you can read all about in the following articles.

Sometimes the Universal Selector is implied. For example, when using a pseudo selector at the start of a new selector. These are selecting exactly the same:

*:has(article) { } :has(article) { } Pseudo-selectors

Pseudo-selectors are for selecting pseudo-elements, just as element selectors are for selecting elements. And a pseudo-element is just like an element, but it doesn’t actually show up in the HTML. If pseudo-elements are new to you, we have a quick explainer you can reference.

Every element has a ::before and ::after pseudo-element attached to it even though we can’t see it in the HTML.

<div class="container"> <!-- ::before psuedo-element here --> <div>Item</div> <div>Item</div> <div>Item</div> <!-- ::after psuedo-element here --> </div>

These are super handy because they’re additional ways we can hook into an element an apply additional styles without adding more markup to the HTML. Keep things as clean as possible, right?!

We know that ::before and ::after are pseudo-elements because they are preceded by a pair of colons (::). That’s how we select them, too!

.container::before { /* Styles */ }

The ::before and ::after pseudo-elements can also be written with a single colon — i.e., :before and :after — but it’s still more common to see a double colon because it helps distinguish pseudo-elements from pseudo-classes.

But there’s a catch when using pseudo-selectors: they require the content property. That’s because pseudos aren’t “real” elements but ones that do not exist as far as HTML is concerned. That means they need content that can be displayed… even if it’s empty content:

.container::before { content: ""; }

Of course, if we were to supply words in the content property, those would be displayed on the page.

CodePen Embed Fallback Complex selectors

Complex selectors may need a little marketing help because “complex” is an awfully scary term to come across when you’re in the beginning stages of learning this stuff. While selectors can indeed become complex and messy, the general idea is super straightforward: we can combine multiple selectors in the same ruleset.

Let’s look at three different routes we have for writing these “not-so-complex” complex selectors.

Listing selectors

First off, it’s possible to combine selectors so that they share the same set of styles. All we do is separate each selector with a comma.

.selector-1, .selector-2, .selector-3 { /* We share these styles! &#x1f917; */ }

You’ll see this often when styling headings — which tend to share the same general styling except, perhaps, for font-size.

h1, h2, h3, h4, h5, h6 { color: hsl(25 80% 15%); font-family: "Poppins", system-ui; }

Adding a line break between selectors can make things more legible. You can probably imagine how complex and messy this might get. Here’s one, for example:

section h1, section h2, section h3, section h4, section h5, section h6, article h1, article h2, article h3, article h4, article h5, article h6, aside h1, aside h2, aside h3, aside h4, aside h5, aside h6, nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 { color: #BADA55; }

Ummmm, okay. No one wants this in their stylesheet. It’s tough to tell what exactly is being selected, right?

The good news is that we have modern ways of combining these selectors more efficiently, such as the :is() pseudo selector. In this example, notice that we’re technically selecting all of the same elements. If we were to take out the four section, article, aside, and nav element selectors and left the descendants in place, we’d have this:

h1, h2, h3, h4, h5, h6, h1, h2, h3, h4, h5, h6, h1, h2, h3, h4, h5, h6, h1, h2, h3, h4, h5, h6, { color: #BADA55; }

The only difference is which element those headings are scoped to. This is where :is() comes in handy because we can match those four elements like this:

:is(section, article, aside, nav) { color: #BADA55; }

That will apply color to the elements themselves, but what we want is to apply it to the headings. Instead of listing those out for each heading, we can reach for :is() again to select them in one fell swoop:

/* Matches any of the following headings scoped to any of the following elements. */ :is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) { color: #BADA55; }

While we’re talking about :is() it’s worth noting that we have the :where() pseudo selector as well and that it does the exact same thing as :is(). The difference? The specificity of :is() will equal the specificity of the most specific element in the list. Meanwhile, :where() maintains zero specificity. So, if you want a complex selector like this that’s easier to override, go with :where() instead.

Nesting selectors

That last example showing how :is() can be used to write more efficient complex selectors is good, but we can do even better now that CSS nesting is a widely supported feature.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeFirefoxIEEdgeSafari120117No12017.2Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari12612712617.2

CSS nesting allows us to better see the relationship between selectors. You know how we can clearly see the relationship between elements in HTML when we indent descendant elements?

<!-- Parent --> <article> <!-- Child --> <img src="" alt="..."> <!-- Child --> <div class="article-content"> <!-- Grandchild --> <h2>Title</h2> <!-- Grandchild --> <p>Article content.</p> </div> </article>

CSS nesting is a similar way that we can format CSS rulesets. We start with a parent ruleset and then embed descendant rulesets inside. So, if we were to select the <h2> element in that last HTML example, we might write a descendant selector like this:

article h2 { /* Styles */ }

With nesting:

article { /* Article styles */ h2 { /* Heading 2 styles */ } }

You probably noticed that we can technically go one level deeper since the heading is contained in another .article-content element:

article { /* Article styles */ .article-content { /* Container styles */ h2 { /* Heading 2 styles */ } } }

So, all said and done, selecting the heading with nesting is the equivalent of writing a descendant selector in a flat structure:

article .article-content h2 { /* Heading 2 styles */ }

You might be wondering how the heck it’s possible to write a chained selector in a nesting format. I mean, we could easily nest a chained selector inside another selector:

article { /* Article styles */ h2.article-content { /* Heading 2 styles */ } }

But it’s not like we can re-declare the article element selector as a nested selector:

article { /* Article styles */ /* Nope! &#x1f44e; */ article.article-element { /* Container styles */ /* Nope! &#x1f44e; */ h2.article-content { /* Heading 2 styles */ } } }

Even if we could do that, it sort of defeats the purpose of a neatly organized nest that shows the relationships between selectors. Instead, we can use the ampersand (&) symbol to represent the selector that we’re nesting into. We call this the nesting selector.

article { &.article-content { /* Equates to: article.article-content */ } } Compounding selectors

We’ve talked quite a bit about the Cascade and how it determines which styles to apply to matching selectors using a specificity score. We saw earlier how an element selector is less specific than a class selector, which is less specific than an ID selector, and so on.

article { /* Specificity: 0, 0, 1 */ } .featured { /* Specificity: 0, 1, 0 */ } #featured { /* Specificity: 1, 0, 0 */ }

Well, we can increase specificity by chaining — or “compounding” — selectors together. This way, we give our selector a higher priority when it comes to evaluating two or more matching styles. Again, overriding ID selectors is incredibly difficult so we’ll work with the element and class selectors to illustrate chained selectors.

We can chain our article element selector with our .featured class selector to generate a higher specificity score.

article { /* Specificity: 0, 0, 1 */ } .featured { /* Specificity: 0, 1, 0 */ } articie.featured { /* Specificity: 0, 1, 1 */ }

This new compound selector is more specific (and powerful!) than the other two individual selectors. Notice in the following demo how the compound selector comes before the two individual selectors in the CSS yet still beats them when the Cascade evaluates their specificity scores.

CodePen Embed Fallback

Interestingly, we can use “fake” classes in chained selectors as a strategy for managing specificity. Take this real-life example:

.wp-block-theme-button .button:not(.specificity):not(.extra-specificity) { }

Whoa, right? There’s a lot going on there. But the idea is this: the .specificity and .extra-specificity class selectors are only there to bump up the specificity of the .wp-block-theme .button descendant selector. Let’s compare the specificity score with and without those artificial classes (that are :not() included in the match).

.wp-block-theme-button .button { /* Specificity: 0, 2, 0 */ } .wp-block-theme-button .button:not(.specificity) { /* Specificity: 0, 3, 0 */ } .wp-block-theme-button .button:not(.specificity):not(.extra-specificity { /* Specificity: 0, 4, 0 */ }

Interesting! I’m not sure if I would use this in my own CSS but it is a less heavy-handed approach than resorting to the !important keyword, which is just as tough to override as an ID selector.

Combinators

If selectors are “what” we select in CSS, then you might think of CSS combinators as “how” we select them. they’re used to write selectors that combine other selectors in order to target elements. Inception!

The name “combinator” is excellent because it accurately conveys the many different ways we’re able to combine selectors. Why would we need to combine selectors? As we discussed earlier with Chained Selectors, there are two common situations where we’d want to do that:

  • When we want to increase the specificity of what is selected.
  • When we want to select an element based on a condition.

Let’s go over the many types of combinators that are available in CSS to account for those two situations in addition to chained selectors.

Descendant combinator

We call it a “descendant” combinator because we use it to select elements inside other elements, sorta like this:

/* Selects all elements in .parent with .child class */ .parent .child {}

…which would select all of the elements with the .child class in the following HTML example:

<div class="parent"> <div class="child"></div> <div class="child"></div> <div class="friend"></div> <div class="child"></div> <div class="child"></div> </div>

See that element with the .friend classname? That’s the only element inside of the .parent element that is not selected with the .parent .child {} descendant combinator since it does not match .child even though it is also a descendant of the .parent element.

Child combinator

A child combinator is really just an offshoot of the descendant combinator, only it is more specific than the descendant combinator because it only selects direct children of an element, rather than any descendant.

Let’s revise the last HTML example we looked at by introducing a descendant element that goes deeper into the family tree, like a .grandchild:

<div class="parent"> <div class="child"></div> <div class="child"> <div class="grandchild"></div> </div> <div class="child"></div> <div class="child"></div> </div>

So, what we have is a .parent to four .child elements, one of which contains a .grandchild element inside of it.

Maybe we want to select the .child element without inadvertently selecting the second .child element’s .grandchild. That’s what a child combinator can do. All of the following child combinators would accomplish the same thing:

/* Select only the "direct" children of .parent */ .parent > .child {} .parent > div {} .parent > * {}

See how we’re combining different selector types to make a selection? We’re combinating, dangit! We’re just doing it in slightly different ways based on the type of child selector we’re combining.

/* Select only the "direct" children of .parent */ .parent > #child { /* direct child with #child ID */ .parent > .child { /* direct child with .child class */ } .parent > div { /* direct child div elements */ } .parent > * { /* all direct child elements */ }

It’s pretty darn neat that we not only have a way to select only the direct children of an element, but be more or less specific about it based on the type of selector. For example, the ID selector is more specific than the class selector, which is more specific than the element selector, and so on.

General sibling combinator

If two elements share the same parent element, that makes them siblings like brother and sister. We saw an example of this in passing when discussing the descendant combinator. Let’s revise the class names from that example to make the sibling relationship a little clearer:

<div class="parent"> <div class="brother"></div> <div class="sister"></div> </div>

This is how we can select the .sister element as long as it is preceded by a sibling with class .brother.

/* Select .sister only if follows .brother */ .brother ~ .sister { }

The Tilda symbol (~) is what tells us this is a sibling combinator.

It doesn’t matter if a .sister comes immediately after a .brother or not — as long as a .sister comes after a brother and they share the same parent element, it will be selected. Let’s see a more complicated HTML example:

<main class="parent"> <!-- .sister immediately after .brother --> <div class="brother"></div> <div class="sister"></div> <!-- .sister immediately after .brother --> <div class="brother"></div> <div class="sister"></div> <!-- .sister immediately after .sister --> <div class="sister"></div> <!-- .cousin immediately after .brother --> <div class="brother"></div> <div class="cousin"> <!-- .sister contained in a .cousin --> <div class="sister"></div> </div> </main>

The sibling combinator we wrote only selects the first three .sister elements because they are the only ones that come after a .brother element and share the same parent — even in the case of the third .sister which comes after another sister! The fourth .sister is contained inside of a .cousin, which prevents it from matching the selector.

Let’s see this in context. So, we can select all of the elements with an element selector since each element in the HTML is a div:

CodePen Embed Fallback

From there, we can select just the brothers with a class selector to give them a different background color:

CodePen Embed Fallback

We can also use a class selector to set a different background color on all of the elements with a .sister class:

CodePen Embed Fallback

And, finally, we can use a general sibling combinator to select only sisters that are directly after a brother.

CodePen Embed Fallback

Did you notice how the last .sister element’s background color remained green while the others became purple? That’s because it’s the only .sister in the bunch that does not share the same .parent as a .brother element.

Adjacent combinator

Believe it or not, we can get even more specific about what elements we select with an adjacent combinator. The general sibling selector we just looked at will select all of the .sister elements on the page as long as it shares the same parent as .brother and comes after the .brother.

What makes an adjacent combinator different is that it selects any element immediately following another. Remember how the last .sister didn’t match because it is contained in a different parent element (i.e., .cousin)? Well, we can indeed select it by itself using an adjacent combinator:

/* Select .sister only if directly follows .brother */ .brother + .sister { }

Notice what happens when we add that to our last example:

CodePen Embed Fallback

The first two .sister elements changed color! That’s because they are the only sisters that come immediately after a .brother. The third .sister comes immediately after another .sister and the fourth one is contained in a .cousin which prevents both of them from matching the selection.

Learn more about CSS selectors Table of contents References

The vast majority of what you’re reading here is information pulled from articles we’ve published on CSS-Tricks and those are linked up throughout the guide. In addition to those articles, the following resources were super helpful for putting this guide together.

CSS Selectors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Steven Heller’s Font of the Month: Sisters

Typography - Wed, 07/10/2024 - 2:51pm

Read the book, Typographic Firsts

Steven Heller takes a closer look at the Sisters font family, designed by Laura Meseguer.

The post Steven Heller’s Font of the Month: Sisters appeared first on I Love Typography.

“If” CSS Gets Inline Conditionals

Css Tricks - Tue, 07/09/2024 - 5:18am

A few sirens went off a couple of weeks ago when the CSS Working Group (CSSWG) resolved to add an if() conditional to the CSS Values Module Level 5 specification. It was Lea Verou’s X post that same day that caught my attention:

A historical day for CSS &#x1f600;&#x1f389;

If you write any components used and/or styled by others, you know how huge this is!

background: if(style(–variant: success), var(–green));

Even if you don’t, this will allow things like:
padding: if(var(–2xl), 1em, var(–xl) or var(–m),… pic.twitter.com/cXeqwBuXvK

— Lea Verou (@LeaVerou) June 13, 2024

Lea is the one who opened the GitHub issue leading to the discussion and in a stroke of coincidence — or serendipity, perhaps — the resolution came in on her birthday. That had to be quite a whirlwind of a day! What did you get for your birthday? “Oh, you know, just an accepted proposal to the CSS spec.” Wild, just wild.

The accepted proposal is a green light for the CSSWG to work on the idea with the intent of circulating a draft specification for further input and considerations en route to, hopefully, become a recommended CSS feature. So, it’s gonna be a hot minute before any of this is baked, that is, if it gets fully baked.

But the idea of applying styles based on a conditional requirement is super exciting and worth an early look at the idea. I scribbled some notes about it on my blog the same day Lea posted to X and thought I’d distill those here for posterity while rounding up more details that have come up since then.

This isn’t a new idea

Many proposals are born from previously rejected proposals and if() is no different. And, indeed, we have gained several CSS features in recent days that allow for conditional styling — :has() and Container Style Queries being two of the more obvious examples. Lea even cites a 2018 ticket that looks and reads a lot like the accepted proposal.

The difference?

Style queries had already shipped, and we could simply reference the same syntax for conditions (plus media() and supports() from Tab’s @when proposal) whereas in the 2018 proposal how conditions would work was largely undefined.

Lea Verou, “Inline conditionals in CSS?”

I like how Lea points out that CSS goes on to describe how CSS has always been a conditional language:

Folks… CSS had conditionals from the very beginning. Every selector is essentially a conditional!

Lea Verou, “Inline conditionals in CSS?”

True! The Cascade is the vehicle for evaluating selectors and matching them to HTML elements on a page. What if() brings to the table is a way to write inline conditions with selectors.

Syntax

It boils down to this:

<if()> = if( <container-query>, [<declaration-value>]{1, 2} )

…where:

  • Values can be nested to produce multiple branches.
  • If a third argument is not provided, it becomes equivalent to an empty token stream.

All of this is conceptual at the moment and nothing is set in stone. We’re likely to see things change as the CSSWG works on the feature. But as it currently stands, the idea seems to revolve around specifying a condition, and setting one of two declared styles — one as the “default” style, and one as the “updated” style when a match occurs.

.element { background-color: /* If the style declares the following custom property: */ if(style(--variant: success), var(--color-green-50), /* Matched condition */ var(--color-blue-50); /* Default style */ ); }

In this case, we’re looking for a style() condition where a CSS variable called --variant is declared and is set to a value of success, and:

  • …if --variant is set to success, we set the value of success to --color-green-50 which is a variable mapped to some greenish color value.
  • …if --variant is not set to success, we set the value of the success to --color-blue-50 which is a variable mapped to some bluish color value.

The default style would be optional, so I think it can be omitted in some cases for slightly better legibility:

.element { background-color: /* If the style declares the following custom property: */ if(style(--variant: success), var(--color-green-50) /* Matched condition */ ); }

The syntax definition up top mentions that we could support a third argument in addition to the matched condition and default style that allows us to nest conditions within conditions:

background-color: if( style(--variant: success), var(--color-success-60), if(style(--variant: warning), var(--color-warning-60), if(style(--variant: danger), var(--color-danger-60), if(style(--variant: primary), var(--color-primary) ) ), ) );

Oomph, looks like some wild inception is happening in there! Lea goes on to suggest a syntax that would result in a much flatter structure:

<if()> = if( [ <container-query>, [<declaration-value>]{2} ]#{0, }, <container-query>, [<declaration-value>]{1, 2} )

In other words, nested conditions are much more flat as they can be declared outside of the initial condition. Same concept as before, but a different syntax:

background-color: if( style(--variant: success), var(--color-success-60), style(--variant: warning), var(--color-warning-60), style(--variant: danger), var(--color-danger-60), style(--variant: primary), var(--color-primary) );

So, rather than one if() statement inside another if() statement, we can lump all of the possible matching conditions into a single statement.

This is all related to style queries

We’re attempting to match an if() condition by querying an element’s styles. There is no corresponding size() function for querying dimensions — container queries implicitly assume size:

.element { background: var(--color-primary); /* Condition */ @container parent (width >= 60ch) { /* Applied styles */ background: var(--color-success-60); } }

And container queries become style queries when we call the style() function instead:

.element { background: orangered; /* Condition */ @container parent style(--variant: success) { /* Applied styles */ background: dodgerblue; } }

Style queries make a lot more sense to me when they’re viewed in the context of if(). Without if(), it’s easy to question the general usefulness of style queries. But in this light, it’s clear that style queries are part of a much bigger picture that goes beyond container queries alone.

There’s still plenty of things to suss out with the if() syntax. For example, Tab Atkins describes a possible scenario that could lead to confusion between what is the matched condition and default style parameters. So, who knows how this all shakes out in the end!

Conditions supporting other conditions

As we’ve already noted, if() is far from the only type of conditional check already provided in CSS. What would it look like to write an inline conditional statement that checks for other conditions, such as @supports and @media?

In code:

background-color: if( supports( /* etc. */ ), @media( /* etc. */ ) );

The challenge would be container supporting size queries. As mentioned earlier, there is no explicit size() function; instead it’s more like an anonymous function.

@andruud has a succinctly describes the challenge in the GitHub discussion:

I don’t see why we couldn’t do supports() and media(), but size queries would cause cycles with layout that are hard/impossible to even detect. (That’s why we needed the restrictions we currently have for size CQs in the first place.

“Can’t we already do this with [X] approach?”

When we were looking at the syntax earlier, you may have noticed that if() is just as much about custom properties as it is about conditionals. Several workarounds have emerged over the years to mimic what we’d gain if() we could set a custom property value conditionally, including:

  • Using custom properties as a Boolean to apply styles or not depending on whether it is equal to 0 or 1. (Ana has a wonderful article on this.)
  • Using a placeholder custom property with an empty value that’s set when another custom property is set, i.e. “the custom property toggle trick” as Chris describes it.
  • Container Style Queries! The problem (besides lack of implementation) is that containers only apply styles to their descendants, i.e., they cannot apply styles to themselves when they meet a certain condition, only its contents.

Lea gets deep into this in a separate post titled “Inline conditional statements in CSS, now?” that includes a table that outlines and compares approaches, which I’ll simply paste below. The explanations are full of complex CSS nerdery but are extremely helpful for understanding the need for if() and how it compares to the clever “hacks” we’ve used for years.

MethodInput valuesOutput valuesProsConsBinary Linear InterpolationNumbersQuantitativeCan be used as part of a valueLimited output rangeTogglesvar(--alias) (actual values are too weird to expose raw)AnyCan be used in part of a valueWeird values that need to be aliasedPaused animationsNumbersAnyNormal, decoupled declarationsTakes over animation property

Cascade weirdnessType GrindingKeywordsAny value supported by the syntax descriptorHigh flexibility for exposed APIGood encapsulationMust insert CSS into light DOM

Tedious code (though can be automated with build tools)

No Firefox support (though that’s changing)Variable animation nameKeywordsAnyNormal, decoupled declarationsImpractical outside of Shadow DOM due to name clashes

Takes over animation property

Cascade weirdness Happy birthday, Lea!

Belated by two weeks, but thanks for sharing the spoils of your big day with us! &#x1f382;

References

To Shared LinkPermalink on CSS-Tricks

“If” CSS Gets Inline Conditionals originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Poppin’ In

Css Tricks - Wed, 06/26/2024 - 6:37am

Oh, hey there! It’s been a hot minute, hasn’t it? Thought I’d pop in and say hello. &#x1f44b;

Speaking of “popping” in, I’ve been playing with the Popover API a bit. We actually first noted it wayyyyy back in 2018 when Chris linked up some information about the <dialog> element. But it’s only been since April of this year that we finally have full Popover API support in modern browsers.

There was once upon a time that we were going to get a brand-new <popover> element in HTML for this. Chromium was working on development as recently as September 2021 but reached a point where it was dropped in favor of a popover attribute instead. That seems to make the most sense given that any element can be a popover — we merely need to attach it to the attribute to enable it.

<div popover> <!-- Stuff --> </div>

This is interesting because let’s say we have some simple little element we’re using as a popover:

<div>&#x1f44b;</div>

If this is all the markup we have and we do absolutely nothing in the CSS, then the waving emoji displays as you might expect.

CodePen Embed Fallback

Add that popover attribute to the mix, however, and it’s gone!

CodePen Embed Fallback

That’s perhaps the first thing that threw me off. Most times something disappears and I assume I did something wrong. But cracking open DevTools shows this is exactly what’s supposed to happen.

The element is set to display: none by default.

There may be multiple popovers on a page and we can differentiate them with IDs.

<div popover id="tooltip"> <!-- Stuff --> </div> <div popover id="notification"> <!-- Stuff --> </div>

That’s not enough, as we also need some sort of “trigger” to make the popover, well, pop! We get another attribute that turns any button (or <input>-flavored button) into that trigger.

<button popovertarget="wave">Say Hello!</button> <div popover id="wave">&#x1f44b;</div>

Now we have a popover “targeted ” to a <button>. When the button is clicked, the popover element toggles visibility.

CodePen Embed Fallback

This is where stuff gets really fun because now that CSS is capable of handling logic to toggle visibility, we can focus more on what happens when the click happens.

Like, right now, the emoji is framed by a really thick black border when it is toggled on. That’s a default style.

Notice that the border sizing in the Box Model diagram.

A few other noteworthy things are going on in DevTools there besides the applied border. For example, notice that the computed width and height behave more like an inline element than a block element, even though we are working with a straight-up <div> — and that’s true even though the element is clearly computing as display: block. Instead, what we have is an element that’s sized according to its contents and it’s placed in the dead center of the page. We haven’t even added a single line of CSS yet!

Speaking of CSS, let’s go back to removing that default border. You might think it’s possible by declaring no border on the element.

/* Nope &#x1f44e; */ #wave { border: 0; }

There’s actually a :popover-open pseudo-class that selects the element specifically when it is in an “open” state. I’d love this to be called :popover-popped but I digress. The important thing is that :popover-open only matches the popover element when it is open, meaning these styles are applied after those declared on the element selector, thus overriding them.

CodePen Embed Fallback

Another way to do this? Select the [popover] attribute:

/* Select all popovers on the page */ [popover] { border: 0; } /* Select a specific popover: */ #wave[popover] { border: 0; } /* Same as: */ #wave:popover-open { border: 0; }

With this in mind, we can, say, attach an animation to the #wave in its open state. I’m totally taking this idea from one of Jhey’s demos.

CodePen Embed Fallback

Wait, wait, there’s more! Popovers can be a lot like a <dialog> with a ::backdrop if we need it. The ::backdrop pseudo-element can give the popover a little more attention by setting it against a special background or obscuring the elements behind it.

I love this example that Mojtaba put together for us in the Almanac, so let’s go with that.

CodePen Embed Fallback

Can you imagine all the possibilities?! Like, how much easier will it be to create tooltips now that CSS has abstracted the visibility logic? Much, much easier.

CodePen Embed Fallback

Michelle Barker notes that this is probably less of a traditional “tooltip” that toggles visibility on hover than it is a “toggletip” controlled by click. That makes a lot of sense. But the real reason I mention Michelle’s post is that she demonstrates how nicely the Popover API ought to work with CSS Anchor Positioning as it gains wider browser support. That will help clean out the magic numbers for positioning that are littering my demo.

Here’s another gem from Jhey: a popover doesn’t have to be a popover. Why not repurpose the Popover API for other UI elements that rely on toggled visibility, like a slide-out menu?

CodePen Embed Fallback

Oh gosh, look at that: it’s getting late. There’s a lot more to the Popover API that I’m still wrapping my head around, but even the little bit I’ve played with feels like it will go a long way. I’ll drop in a list of things I have bookmarked to come back to. For now, though, thanks for letting me pop back in for a moment to say hi.

Poppin’ In originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Ask LukeW: PDF Parsing with Vision Models

LukeW - Tue, 06/25/2024 - 2:00pm

Over the years, I've given more than 300 presentations on design. Most of these have been accompanied by a slide deck to illustrate my points and guide the narrative. But making the content in these decks work well with the Ask Luke conversational interface on this site has been challenging. So now I'm trying a new approach with AI vision models.

To avoid application specific formats (Keynote, PowerPoint), I've long been making my presentation slides available for download as PDF documents. These files usually consist of 100+ pages and often don't include a lot of text, leaning instead on visuals and charts to communicate information. To illustrate, here's of few of these slides from my Mind the Gap talk.

In an earlier article on how we built the Ask Luke conversational interface, I outlined the issues with extracting useful information from these documents. I wanted the content in these PDFs to be available when answering people's design questions in addition to the blog articles, videos and audio interviews that we were already using.

But even when we got text extraction from PDFs working well, running the process on any given PDF document would create many content embeddings of poor quality (like the one below). These content chunks would then end up influencing the answers we generated in less than helpful ways.

To prevent these from clogging up our limited context (how much content we can work with to create an answer) with useless results, we set up processes to remove low quality content chunks. While that improved things, the content in these presentations was no longer accessible to people asking questions on Ask Luke.

So we tried a different approach. Instead of extracting text from each page of a PDF presentation, we ran it through an AI vision model to create a detailed description of the content on the page. In the example below, the previous text extraction method (on the left) gets the content from the slide. The new vision model approach (on the right) though, does a much better job creating useful content for answering questions.

Here's another example illustrating the difference between the PDF text extraction method used before and the vision AI model currently in use. This time instead of a chart, we're generating a useful description of a diagram.

This change is now rolled out across all the PDFs the Ask Luke conversational interface can reference to answer design questions. Gone are useless content chunks and there's a lot more useful content immediately available.

Thanks to Yangguang Li for the dev help on this change.

<p>CSS Meditation #8:<code> .work +

Css Tricks - Mon, 06/24/2024 - 8:28am

CSS Meditation #8: .work + .life { border: 10px solid #000; }

originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

<p>CSS Meditation #7: Nobody is perf

Css Tricks - Mon, 06/24/2024 - 8:27am

CSS Meditation #7: Nobody is perf-ect.

originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

<p>CSS Meditation #6: The color space

Css Tricks - Mon, 06/24/2024 - 8:27am

CSS Meditation #6: The color space is always calc(rgb(0 255 0)+er) on the other side of the fence.

originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

<p>CSS Meditation #5: <code>:where(:is(

Css Tricks - Mon, 06/24/2024 - 8:26am

CSS Meditation #5: :where(:is(.my-mind))

originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

<p>CSS Meditation #4: Select, style,

Css Tricks - Mon, 06/24/2024 - 8:26am

CSS Meditation #4: Select, style, adjust. Select, style, adjust. Select, sty…

originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

<p>CSS Meditation #3: A pseudo is as a

Css Tricks - Mon, 06/24/2024 - 8:25am

CSS Meditation #3: A pseudo is as a pseudo does.

originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

<p>CSS Meditation #2: Who gives a

Css Tricks - Mon, 06/24/2024 - 8:25am

CSS Meditation #2: Who gives a flying frick what constitutes a “programming” language.

originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

<p>CSS Meditation #1: If the code works

Css Tricks - Mon, 06/24/2024 - 8:22am

CSS Meditation #1: If the code works as expected and it fits your mental model, then it’s perfect.

originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Ask LukeW: Text Generation Differences

LukeW - Thu, 06/20/2024 - 2:00pm

As the number of highly capable large language models (LLMs) released continues to quickly increase, I added the ability to test new models when they become available in the Ask Luke conversational interface on this site.

For context there's a number of places in the Ask Luke pipeline that make use of AI models to transform, clean, embed, retrieve, generate content and more. I put together a short video that explains how this pipeline is constructed and why if you're interested.

Specifically for the content generation step, once the right content is found, ranked, and assembled into a set of instructions, I can select which large language model to send these instructions to. Every model gets the same instructions unless they can support a larger context window. In which case they might get more ranked results than a model with a smaller context size.

Despite the consistent instructions, switching LLMs can have a very big impact on answer generation. I'll leave you to guess which of these two answers is powered by OpenAI's GPT-4 and which one comes from Antrhopic's new (this week) Claude 3.5 Sonnet.

Some of you might astutely point out that the instruction set could be altered in specific ways when changing models. Recently, we've found the most advanced LLMs to be more interchangeable than before. But there's still differences in how they generate content as you can clearly see in the example above. Which one is best though... could soon be a matter of personal preference.

Thanks to Yangguang Li and Sam for the dev help on this feature.

CSS Container Queries

Css Tricks - Mon, 06/10/2024 - 6:12am

Container queries are often considered a modern approach to responsive web design where traditional media queries have long been the gold standard — the reason being that we can create layouts made with elements that respond to, say, the width of their containers rather than the width of the viewport.

.parent { container-name: hero-banner; container-type: inline-size; /* or container: hero-banner / inline-size; */ } } .child { display: flex; flex-direction: column; } /* When the container is greater than 60 characters... */ @container hero-banner (width > 60ch) { /* Change the flex direction of the .child element. */ .child { flex-direction: row; } } Why care about CSS Container Queries?
  1. When using a container query, we give elements the ability to change based on their container’s size, not the viewport.
  1. They allow us to define all of the styles for a particular element in a more predictable way.
  1. They are more reusable than media queries in that they behave the same no matter where they are used. So, if you were to create a component that includes a container query, you could easily drop it into another project and it will still behave in the same predictable fashion.
  1. They introduce new types of CSS length units that can be used to size elements by their container’s size.
Table of Contents Registering Elements as Containers .cards { container-name: card-grid; container-type: inline-size; /* Shorthand */ container: card-grid / inline-size; }

This example registers a new container named card-grid that can be queried by its inline-size, which is a fancy way of saying its “width” when we’re working in a horizontal writing mode. It’s a logical property. Otherwise, “inline” would refer to the container’s “height” in a vertical writing mode.

  • The container-name property is used to register an element as a container that applies styles to other elements based on the container’s size and styles.
  • The container-type property is used to register an element as a container that can apply styles to other elements when it meets certain conditions.
  • The container property is a shorthand that combines the container-name and container-type properties into a single declaration.
Some Possible Gotchas Querying a Container @container my-container (width > 60ch) { article { flex-direction: row; } }
  • The @container at-rule property informs the browser that we are working with a container query rather than, say, a media query (i.e., @media).
  • The my-container part in there refers to the container’s name, as declared in the container’s container-name property.
  • The article element represents an item in the container, whether it’s a direct child of the container or a further ancestor. Either way, the element must be in the container and it will get styles applied to it when the queried condition is matched.
Some Possible Gotchas Container Queries Properties & Values Container Queries Properties & Values container-name container-name: none | <custom-ident>+; Value Descriptions
  • none: The element does not have a container name. This is true by default, so you will likely never use this value, as its purpose is purely to set the property’s default behavior.
  • <custom-ident>: This is the name of the container, which can be anything, except for words that are reserved for other functions, including default, none, at, no, and or. Note that the names are not wrapped in quotes.
Open in Almanac
  • Initial value: none
  • Applies to: All elements
  • Inherited: No
  • Percentages: N/A
  • Computed value: none or an ordered list of identifiers
  • Canonical order: Per grammar
  • Animation: Not animatable
container-type container-type: normal | size | inline-size; Value Descriptions
  • normal: This indicates that the element is a container that can be queried by its styles rather than size. All elements are technically containers by default, so we don’t even need to explicitly assign a container-type to define a style container.
  • size: This is if we want to query a container by its size, whether we’re talking about the inline or block direction.
  • inline-size: This allows us to query a container by its inline size, which is equivalent to width in a standard horizontal writing mode. This is perhaps the most commonly used value, as we can establish responsive designs based on element size rather than the size of the viewport as we would normally do with media queries.
Open in Almanac
  • Initial value: normal
  • Applies to: All elements
  • Inherited: No
  • Percentages: N/A
  • Computed value: As specified by keyword
  • Canonical order: Per grammar
  • Animation: Not animatable
container container: <'container-name'> [ / <'container-type'> ]? Value Definitons

If <'container-type'> is omitted, it is reset to its initial value of normalwhich defines a style container instead of a size container. In other words, all elements are style containers by default, unless we explicitly set the container-type property value to either size or inline-size which allows us to query a container’s size dimensions.

Open in Almanac
  • Initial value: none / normal
  • Applies to: All elements
  • Inherited: No
  • Percentages: N/A
  • Computed value: As specified
  • Canonical order: Per grammar
  • Animation: Not animatable
Container Length Units Container Width & Height Units UnitNameEquivalent to…cqwContainer query width1% of the queried container’s widthcqhContainer query height1% of the queried container’s height Container Logical Directions UnitNameEquivalent to…cqiContainer query inline size1% of the queried container’s inline size, which is its width in a horizontal writing mode.cqbContainer query block size1% of the queried container’s inline size, which is its height in a horizontal writing mode. Container Minimum & Maximum Lengths UnitNameEquivalent to…cqminContainer query minimum sizeThe value of cqi or cqb, whichever is smaller.cqmaxContainer query maximum sizeThe value of cqi or cqb, whichever is larger. Container Style Queries

Container Style Queries is another piece of the CSS Container Queries puzzle. Instead of querying a container by its size or inline-size, we can query a container’s CSS styles. And when the container’s styles meet the queried condition, we can apply styles to other elements. This is the sort of “conditional” styling we’ve wanted on the web for a long time: If these styles match over here, then apply these other styles over there.

CSS Container Style Queries are only available as an experimental feature in modern web browsers at the time of this writing, and even then, style queries are only capable of evaluating CSS custom properties (i.e., variables).

Browser Support

The feature is still considered experimental at the time of this writing and is not supported by any browser, unless enabled through feature flags.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeFirefoxIEEdgeSafari129NoNo126TPMobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari126No12618.0 Registering a Style Container article { container-name: card; }

That’s really it! Actually, we don’t even need the container-name property unless we need to target it specifically. Otherwise, we can skip registering a container altogether.

And if you’re wondering why there’s no container-type declaration, that’s because all elements are already considered containers. It’s a lot like how all elements are position: relative by default; there’s no need to declare it. The only reason we would declare a container-type is if we want a CSS Container Size Query instead of a CSS Container Style Query.

So, really, there is no need to register a container style query because all elements are already style containers right out of the box! The only reason we’d declare container-name, then, is simply to help select a specific container by name when writing a style query.

Using a Style Container Query @container style(--bg-color: #000) { p { color: #fff; } }

In this example, we’re querying any matching container (because all elements are style containers by default).

Notice how the syntax it’s a lot like a traditional media query? The biggest difference is that we are writing @container instead of @media. The other difference is that we’re calling a style() function that holds the matching style condition. This way, a style query is differentiated from a size query, although there is no corresponding size() function.

In this instance, we’re checking if a certain custom property named --bg-color is set to black (#000). If the variable’s value matches that condition, then we’re setting paragraph (p) text color to white (#fff).

Custom Properties & Variables .card-wrapper { --bg-color: #000; } .card { @container style(--bg-color: #000) { /* Custom CSS */ } } Nesting Style Queries @container style(--featured: true) { article { grid-column: 1 / -1; } @container style(--theme: dark) { article { --bg-color: #000; --text: #fff; } } } Specification

CSS Container Queries are defined in the CSS Containment Module Level 3 specification, which is currently in Editor’s Draft status at the time of this writing.

Browser Support

Browser support for CSS Container Size Queries is great. It’s just style queries that are lacking support at the time of this writing.

  • Chrome 105 shipped on August 30, 2022, with support.
  • Safari 16 shipped on September 12, 2022, with support.
  • Firefox 110 shipped on February 14, 2023, with support.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeFirefoxIEEdgeSafari106110No10616.0Mobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari12612712616.0 Demos!

Many, many examples on the web demonstrate how container queries work. The following examples are not unique in that regard in that they illustrate the general concept of applying styles when a container element meets a certain condition.

You will find plenty more examples listed in the References at the end of this guide, but check out Ahmad Shadeed’s Container Queries Lab for the most complete set of examples because it also serves as a collection of clever container query use cases.

Card Component

In this example, a “card” component changes its layout based on the amount of available space in its container.

CodePen Embed Fallback Call to Action Panel

This example is a lot like those little panels for signing up for an email newsletter. Notice how the layout changes three times according to how much available space is in the container. This is what makes CSS Container Queries so powerful: you can quite literally drop this panel into any project and the layout will respond as it should, as it’s based on the space it is in rather than the size of the browser’s viewport.

CodePen Embed Fallback Stepper Component

This component displays a series of “steps” much like a timeline. In wider containers, the stepper displays steps horizontally. But if the container becomes small enough, the stepper shifts things around so that the steps are vertically stacked.

CodePen Embed Fallback Icon Button

Sometimes we like to decorate buttons with an icon to accentuate the button’s label with a little more meaning and context. And sometimes we don’t know just how wide that button will be in any given context, which makes it tough to know when exactly to hide the icon or re-arrange the button’s styles when space becomes limited. In this example, an icon is displayed to the right edge of the button as long as there’s room to fit it beside the button label. If room runs out, the button becomes a square tile that stacks the icons above the label. Notice how the border-radius is set in container query units, 4cqi, which is equal to 4% of the container’s inline-size (i.e. width) and results in rounder edges as the button grows in size.

CodePen Embed Fallback Pagination

Pagination is a great example of a component that benefits from CSS Container Queries because, depending on the amount of space we have, we can choose to display links to individual pages, or hide them in favor of only two buttons, one to paginate to older content and one to paginate to newer content.

CodePen Embed Fallback Articles & Tutorials General Information Article on Jun 14, 2024 CSS Container Queries Robin Rendle Article on Jun 14, 2024 CSS Container Queries Robin Rendle Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Una Kravets Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Mathias Hülsbusch

Container Size Query Tutorials Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Jhey Tompkins Article on Jun 14, 2024 CSS Container Queries Dan Christofi Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Chris Coyier Article on Jun 14, 2024 CSS Container Queries Chris Coyier

Container Style Queries Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Geoff Graham

Almanac References Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Geoff Graham Article on Jun 14, 2024 CSS Container Queries Geoff Graham

Related Guides Article on Jun 14, 2024 CSS Container Queries Andrés Galante Article on Jun 14, 2024 CSS Container Queries Chris Coyier

References

CSS Container Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

Syndicate content
©2003 - Present Akamai Design & Development.