Articles – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Fri, 02 Aug 2024 16:49:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 Articles – CSS-Tricks https://css-tricks.com 32 32 45537868 HTML Web Components Make Progressive Enhancement and CSS Encapsulation Easier! https://css-tricks.com/html-web-components-make-progressive-enhancement-and-css-encapsulation-easier/ https://css-tricks.com/html-web-components-make-progressive-enhancement-and-css-encapsulation-easier/#comments Thu, 01 Aug 2024 13:21:37 +0000 https://css-tricks.com/?p=379335 I have to thank Jeremy Keith and his wonderfully insightful article from late last year that introduced me to the concept of HTML Web Components. This was the “a-ha!” moment for me:

When you wrap some existing markup in a


HTML Web Components Make Progressive Enhancement and CSS Encapsulation Easier! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I have to thank Jeremy Keith and his wonderfully insightful article from late last year that introduced me to the concept of HTML Web Components. This was the “a-ha!” moment for me:

When you wrap some existing markup in a custom element and then apply some new behaviour with JavaScript, technically you’re not doing anything you couldn’t have done before with some DOM traversal and event handling. But it’s less fragile to do it with a web component. It’s portable. It obeys the single responsibility principle. It only does one thing but it does it well.

Until then, I’d been under the false assumption that all web components rely solely on the presence of JavaScript in conjunction with the rather scary-sounding Shadow DOM. While it is indeed possible to author web components this way, there is yet another way. A better way, perhaps? Especially if you, like me, advocate for progressive enhancement. HTML Web Components are, after all, just HTML.

While it’s outside the exact scope of what we’re discussing here, Adny Bell has a recent write-up that offers his (excellent) take on what progressive enhancement means.

Let’s look at three specific examples that show off what I think are the key features of HTML Web Components — CSS style encapsulation and opportunities for progressive enhancement — without being forced to depend on JavaScript to work out of the box. We will most definitely use JavaScript, but the components ought to work without it.

The examples can all be found in my Web UI Boilerplate component library (built using Storybook), along with the associated source code in GitHub.

Example 1: <webui-disclosure>

Storybook render of webui-disclosure Web Component.q
Live demo

I really like how Chris Ferdinandi teaches building a web component from scratch, using a disclosure (show/hide) pattern as an example. This first example extends his demo.

Let’s start with the first-class citizen, HTML. Web components allow us to establish custom elements with our own naming, which is the case in this example with a <webui-disclosure> tag we’re using to hold a <button> designed to show/hide a block of text and a <div> that holds the <p> of text we want to show and hide.

<webui-disclosure
  data-bind-escape-key
  data-bind-click-outside
>
  <button
    type="button"
    class="button button--text"
    data-trigger
    hidden
  >
    Show / Hide
  </button>

  <div data-content>
    <p>Content to be shown/hidden.</p>
  </div>
</webui-disclosure>

If JavaScript is disabled or doesn’t execute (for any number of possible reasons), the button is hidden by default — thanks to the hidden attribute on it— and the content inside of the div is simply displayed by default.

Nice. That’s a really simple example of progressive enhancement at work. A visitor can view the content with or without the <button>.

I mentioned that this example extends Chris Ferdinandi’s initial demo. The key difference is that you can close the element either by clicking the keyboard’s ESC key or clicking anywhere outside the element. That’s what the two [data-attribute]s on the <webui-disclosure tag are for.

We start by defining the custom element so that the browser knows what to do with our made-up tag name:

customElements.define('webui-disclosure', WebUIDisclosure);

Custom elements must be named with a dashed-ident, such as <my-pizza> or whatever, but as Jim Neilsen notes, by way of Scott Jehl, that doesn’t exactly mean that the dash has to go between two words.

I typically prefer using TypeScript for writing JavaScript to help eliminate stupid errors and enforce some degree of “defensive” programming. But for the sake of simplicity, the structure of the web component’s ES Module looks like this in plain JavaScript:

default class WebUIDisclosure extends HTMLElement {
  constructor() {
    super();
    this.trigger = this.querySelector('[data-trigger]');
    this.content = this.querySelector('[data-content]');
    this.bindEscapeKey = this.hasAttribute('data-bind-escape-key');
    this.bindClickOutside = this.hasAttribute('data-bind-click-outside');
    
    if (!this.trigger || !this.content) return;
    
    this.setupA11y();
    this.trigger?.addEventListener('click', this);
  }

  setupA11y() {
    // Add ARIA props/state to button.
  }

  // Handle constructor() event listeners.
  handleEvent(e) {
    // 1. Toggle visibility of content.
    // 2. Toggle ARIA expanded state on button.
  }
  
  // Handle event listeners which are not part of this Web Component.
  connectedCallback() {
    document.addEventListener('keyup', (e) => {
      // Handle ESC key.
    });
  
    document.addEventListener('click', (e) => {
      // Handle clicking outside.
    });
  }

  disconnectedCallback() {
    // Remove event listeners.
  }
}

Are you wondering about those event listeners? The first one is defined in the constructor() function, while the rest are in the connectedCallback() function. Hawk Ticehurst explains the rationale much more eloquently than I can.

This JavaScript isn’t required for the web component to “work” but it does sprinkle in some nice functionality, not to mention accessibility considerations, to help with the progressive enhancement that allows the <button> to show and hide the content. For example, JavaScript injects the appropriate aria-expanded and aria-controls attributes enabling those who rely on screen readers to understand the button’s purpose.

That’s the progressive enhancement piece to this example.

For simplicity, I have not written any additional CSS for this component. The styling you see is simply inherited from existing global scope or component styles (e.g., typography and button).

However, the next example does have some extra scoped CSS.

Example 2: <webui-tabs>

That first example lays out the progressive enhancement benefits of HTML Web Components. Another benefit we get is that CSS styles are encapsulated, which is a fancy way of saying the CSS doesn’t leak out of the component. The styles are scoped purely to the web component and those styles will not conflict with other styles applied to the current page.

Let’s turn to a second example, this time demonstrating the style encapsulating powers of web components and how they support progressive enhancement in user experiences. We’ll be using a tabbed component for organizing content in “panels” that are revealed when a panel’s corresponding tab is clicked — the same sort of thing you’ll find in many component libraries.

Storybook render of the webui-tabs web component.
Live demo

Starting with the HTML structure:

<webui-tabs>
  <div data-tablist>
    <a href="#tab1" data-tab>Tab 1</a>
    <a href="#tab2" data-tab>Tab 2</a>
    <a href="#tab3" data-tab>Tab 3</a>
  </div>

  <div id="tab1" data-tabpanel>
    <p>1 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>

  <div id="tab2" data-tabpanel>
    <p>2 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>

  <div id="tab3" data-tabpanel>
    <p>3 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>
</webui-tabs>

You get the idea: three links styled as tabs that, when clicked, open a tab panel holding content. Note that each [data-tab] in the tab list targets an anchor link matching a tab panel ID, e.g., #tab1, #tab2, etc.

We’ll look at the style encapsulation stuff first since we didn’t go there in the last example. Let’s say the CSS is organized like this:

webui-tabs {
  [data-tablist] {
    /* Default styles without JavaScript */
  }
  
  [data-tab] {
    /* Default styles without JavaScript */
  }

  [role='tablist'] {
    /* Style role added by JavaScript */
  }
  
  [role='tab'] {
    /* Style role added by JavaScript */
  }
  
  [role='tabpanel'] {
    /* Style role added by JavaScript */
  }
}

See what’s happening here? We have two style rules — [data-tablist] and [data-tab] — that contain the web component’s default styles. In other words, these styles are there regardless of whether JavaScript loads or not. Meanwhile, the other three style rules are selectors that are injected into the component as long as JavaScript is enabled and supported. This way, the last three style rules are only applied if JavaScript plops the **role** attribute on those elements in the HTML. Right there, we’re already supplying a touch of progressive enhancement by setting styles only when JavasScript is needed.

All these styles are fully encapsulated, or scoped, to the <webui-tabs> web component. There is no “leakage” so to speak that would bleed into the styles of other web components, or even to anything else on the page within the global scope. We can even choose to forego classnames, complex selectors, and methodologies like BEM in favour of simple descendent selectors for the component’s children, allowing us to write styles more declaratively on semantic elements.

Quickly: “Light” DOM versus Shadow DOM

For most web projects, I generally prefer to bundle CSS (including the web component Sass partials) into a single CSS file so that the component’s default styles are available in the global scope, even if the JavaScript doesn’t execute.

However, it is possible to import a stylesheet via JavaScript that is only consumed by this web component if JavaScript is available:

import styles from './styles.css';

class WebUITabs extends HTMLElement {
  constructor() {
    super();
    this.adoptedStyleSheets = [styles];
  }
}

customElements.define('webui-tabs', WebUITabs);

Alternatively, we could inject a <style> tag containing the component’s styles instead:

class WebUITabs extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' }); // Required for JavaScript access
    this.shadowRoot.innerHTML = `
      <style> <!-- styles go here --> </style>
      // etc.
    `;
  }
}
    
customElements.define('webui-tabs', WebUITabs);

Whichever method you choose, these styles are scoped directly to the web component, preventing component styles from leaking out, but allowing global styles to be inherited.

Now consider this simple example. Everything we write in between the component’s opening and closing tags is considered part of the “Light” DOM.

<my-web-component>
  <!-- This is Light DOM -->
  <div>
    <p>Some content... styles are inherited from the global scope</p>
  </div>

  ----------- Shadow DOM Boundary -------------
  | <!-- Anything injected by JavaScript -->  |
  ---------------------------------------------
</my-web-component>

Dave Rupert has an excellent write-up that makes it really easy to see how external styles are able to “pierce” the Shadow DOM and select an element in the Light DOM. Notice how the <button> element that is written in between the custom element’s tags receives the button selector’s styles in the global CSS, while the <button> injected via JavaScript is left untouched.

If we want to style the Shadow DOM <button> we’d have to do that with internal styles like the examples above for importing a stylesheet or injecting an inline <style> block.

That doesn’t mean that all CSS style properties are blocked by the Shadow DOM. In fact, Dave outlines 37 properties that web components inherit, mostly along the lines of text, list, and table formatting.

Progressively enhance the tabbed component with JavaScript

Even though this second example is more about style encapsulation, it’s still a good opportunity to see the progressive enhancement we get practically for free from web components. Let’s step into the JavaScript now so we can see how we can support progressive enhancement. The full code is quite lengthy, so I’ve abbreviated things a bit to help make the points a little clearer.

default class WebUITabs extends HTMLElement {
  constructor() {
    super();
    this.tablist = this.querySelector('[data-tablist]');
    this.tabpanels = this.querySelectorAll('[data-tabpanel]');
    this.tabTriggers = this.querySelectorAll('[data-tab]');

    if (
      !this.tablist ||
      this.tabpanels.length === 0 ||
      this.tabTriggers.length === 0
    ) return;
    
    this.createTabs();
    this.tabTriggers.forEach((tabTrigger, index) => {
      tabTrigger.addEventListener('click', (e) => {
        this.bindClickEvent(e);
      });
      tabTrigger.addEventListener('keydown', (e) => {
        this.bindKeyboardEvent(e, index);
      });
    });
  }

  createTabs() {
    // 1. Hide all tabpanels initially.
    // 2. Add ARIA props/state to tabs & tabpanels.
  }

  bindClickEvent(e) {
    e.preventDefault();
    // Show clicked tab and update ARIA props/state.
  }
  bindKeyboardEvent(e, index) {
    e.preventDefault();
    // Handle keyboard ARROW/HOME/END keys.
  }
}

customElements.define('webui-tabs', WebUITabs);

The JavaScript injects ARIA roles, states, and props to the tabs and content blocks for screen reader users, as well as extra keyboard bindings so we can navigate between tabs with the keyboard; for example, the TAB key is reserved for accessing the component’s active tab and any focusable content inside the active tabpanel, and the tabs can be traversed with the ARROW keys. So, if JavaScript fails to load, the default experience is still an accessible one where the tabs still anchor link to their respective panels, and those panels naturally stack vertically, one on top of the other.

And if JavaScript is enabled and supported? We get an enhanced experience, complete with updated accessibility considerations.

Example 3: <webui-ajax-loader>

Storybook render of webui-ajax-loader web component.
Live demo

This final example differs from the previous two in that it is entirely generated by JavaScript, and uses the Shadow DOM. This is because it is only used to indicate a “loading” state for Ajax requests, and is therefore only needed when JavaScript is enabled.

The HTML markup is just the opening and closing component tags:

<webui-ajax-loader></webui-ajax-loader>

The simplified JavaScript structure:

default class WebUIAjaxLoader extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <svg role="img" part="svg">
        <title>loading</title>
        <circle cx="50" cy="50" r="47" />
      </svg>
    `;
  }
}

customElements.define('webui-ajax-loader',WebUIAjaxLoader);

Notice right out of the gate that everything in between the <webui-ajax-loader> tags is injected with JavaScript, meaning it’s all in the Shadow DOM, encapsulated from other scripts and styles not directly bundled with the component.

But also notice the part attribute that’s set on the <svg> element. Here’s where we’ll zoom in:

<svg role="img" part="svg">
  <!-- etc. -->
</svg>

That’s yet another way we have to style the custom element: named parts. Now we can style that SVG from outside of the template literal we used to establish the element. There’s a ::part pseudo-selector to make that happen:

webui-ajax-loader::part(svg) {
  // Shadow DOM styles for the SVG...
}

And here’s something cool: that selector can access CSS custom properties, whether they are scoped globally or locally to the element.

webui-ajax-loader {
  --fill: orangered;
}

webui-ajax-loader::part(svg) {
  fill: var(--fill);
}

As far as progressive enhancement goes, JavaScript supplies all of the HTML. That means the loader is only rendered if JavaScript is enabled and supported. And when it is, the SVG is added, complete with an accessible title and all.

Wrapping up

That’s it for the examples! What I hope is that you now have the same sort of epiphany that I had when reading Jeremy Keith’s post: HTML Web Components are an HTML-first feature.

Of course, JavaScript does play a big role, but only as big as needed. Need more encapsulation? Want to sprinkle in some UX goodness when a visitor’s browser supports it? That’s what JavaScript is for and that’s what makes HTML Web Components such a great addition to the web platform — they rely on vanilla web languages to do what they were designed to do all along, and without leaning too heavily on one or the other.


HTML Web Components Make Progressive Enhancement and CSS Encapsulation Easier! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/html-web-components-make-progressive-enhancement-and-css-encapsulation-easier/feed/ 3 379335
Where You Can Still Get A Book Apart Titles https://css-tricks.com/where-you-can-still-get-a-book-apart-titles/ https://css-tricks.com/where-you-can-still-get-a-book-apart-titles/#comments Wed, 31 Jul 2024 14:52:44 +0000 https://css-tricks.com/?p=379392 It’s been a few months out since A Book Apart closed shop. I’m sad about it, of course. You probably are, too, if you have one of their many brightly-colored paperbacks sitting on a bookshelf strategically placed as a backdrop …


Where You Can Still Get A Book Apart Titles originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
It’s been a few months out since A Book Apart closed shop. I’m sad about it, of course. You probably are, too, if you have one of their many brightly-colored paperbacks sitting on a bookshelf strategically placed as a backdrop for your video calls.

It looked for a bit like the books would still be available for purchase through third-party distributors who could print them on demand or whatever. And then a redaction on A Book Apart’s original announcement:

UPDATE: Ownership and publishing rights for all books have been given back to their respective authors. Many authors are continuing to offer their work for free or in new editions. Our hope is that these books will continue to live on forever. A Book Apart no longer sells or distributes books, please reach out to authors for information about availability.

Oh, snap. The books are on the loose and several authors are making sure they’re still available. Eric Meyer, for example, says he and co-author Sara Wachter-Boettcher still figuring out what’s next for their Design for Real Life title:

One of the things Sara and I have decided to do is to eventually put the entire text online for free, as a booksite. That isn’t ready yet, but it should be coming somewhere down the road.

In the meantime, we’ve decided to cut the price of print and e-book copies available through Ingram. [Design for Real Life] was the eighteenth book [A Book Apart] put out, so we’ve decided to make the price of both the print and e-book $18, regardless of whether those dollars are American, Canadian, or Australian.

Ethan Marcotte has followed suit by listing his three titles on his personal website and linking up where they can be purchased at a generous discount off the original price tag, including his latest, You Deserve a Tech Union.

Others have quickly responded with free online versions of their books. Mat Marquis has offered JavaScript for Web Designers free online for a long time. He helped Chris Coyier do the same with Practical SVG this past week. Jeremy Keith put out one of my personal ABA faves (and the first ever ABA-published book) for free, HTML5 for Web Designers.

What about all the other titles? I dunno. A Book Apart simply doesn’t sell or distribute them anymore. Rachel McConnell sells Leading Content Design directly. Every other book I checked seems to be a link back to A Book Apart. We’ll have to see where the proverbial dust settles. The authors now hold all the rights to their works and may or may not decide to re-offer them. Meanwhile, many of the titles are listed in places like Goodreads, Amazon, Barnes & Noble, etc.

A couple of folks have even started tracking the books on their personal sites, like Ryan Trimble and Alan Dalton. (Thanks for the tip, Chris!)

Thanks for all the great reads and years, A Book Apart! You’ve helped man, many people become better web citizens, present company included.


Where You Can Still Get A Book Apart Titles originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/where-you-can-still-get-a-book-apart-titles/feed/ 3 379392
Letter Spacing is Broken and There’s Nothing We Can Do About It… Maybe https://css-tricks.com/letter-spacing-is-broken-and-theres-nothing-we-can-do-about-it-maybe/ https://css-tricks.com/letter-spacing-is-broken-and-theres-nothing-we-can-do-about-it-maybe/#comments Mon, 29 Jul 2024 16:41:34 +0000 https://css-tricks.com/?p=379318 This post came up following a conversation I had with Emilio Cobos — a senior developer at Mozilla and member of the CSSWG — about the last CSSWG group meeting. I wanted to know what he thought were the …


Letter Spacing is Broken and There’s Nothing We Can Do About It… Maybe originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This post came up following a conversation I had with Emilio Cobos — a senior developer at Mozilla and member of the CSSWG — about the last CSSWG group meeting. I wanted to know what he thought were the most exciting and interesting topics discussed at their last meeting, and with 2024 packed with so many new or coming flashy things like masonry layout, if() conditionals, anchor positioning, view transitions, and whatnot, I thought his answers had to be among them.

He admitted that my list of highlights was accurate on what is mainstream in the community, especially from an author’s point of view. However, and to my surprise, his favorite discussion was on something completely different: an inaccuracy on how the letter-spacing property is rendered across browsers. It’s a flaw so ingrained on the web that browsers have been ignoring the CSS specification for years and that can’t be easily solved by a lack of better options and compatibility issues.

Emilios’s answer makes sense — he works on Gecko and rendering fonts is an art in itself. Still, I didn’t get what the problem is exactly, why he finds it so interesting, and even why it exists in the first place since letter-spacing is a property as old as CSS. It wasn’t until I went into the letter-spacing rabbit hole that I understood how amazingly complex the issue gets and I hope to get you as interested as I did in this (not so) simple property.

What’s letter spacing?

The question seems simple: letter spacing is the space between letters. Hooray! That was easy, for humans. For a computer, the question of how to render the space between letters has a lot more nuance. A human just writes the next letter without putting in much thought. Computers, on the other hand, need a strategy on how to render that space: should they add the full space at the beginning of the letter, at the end, or halve it and add it on both sides of the letter? Should it work differently from left-to-right (LTR) languages, like English, to right-to-left (RTL) like Hebrew? These questions are crucial since choosing one as a standard shapes how text measurement and line breaks work across the web.

Which of the three strategies is used on the web? Depends on who you ask. The implementation in the CSS specifications completely differs from what the browsers do, and there is even incompatibility between browsers rendering engines, like Gecko (Firefox), Blink (Chrome, Brave, Opera, etc.), and WebKit (Safari).

What the CSS spec says

Let’s backpedal a bit and first know how the spec says letter spacing should work. At the time of writing, letter-spacing:

Specifies additional spacing between typographic character units. Values may be negative, but there may be implementation-dependent limits.

The formal specification has more juice to it, but this one gives us enough to understand how the CSS spec wants letter-spacing to behave. The keyword is between, meaning that the letter spacing should only affect the space between characters. I know, sounds pretty obvious.

So, as the example given on the spec, the following HTML:

<p>a<span>bb</span>c</p>

…with this CSS:

p {
  letter-spacing: 1em;
}

span {
  letter-spacing: 2em;
}

…should give an equal space between the two “b” letters:

Letter spacing on paper. The letter spacing is only applied between the letters "b"s

However, if we run the same code on any browser (e.g., Chrome, Firefox, or Safari), we’ll see the spacing isn’t contained between the “b” letters, but also at the end of the complete word.

Letter spacing on browsers. The letter spacing is applied between the letters "b"s and on the right-hand side of the last letter "b"

What browsers do

I thought it was normal for letter-spacing to attach spacing at the end of a character and didn’t know the spec said otherwise. However, if you think about it, the current behavior does seem off… it’s just that we’re simply used to it.

Why would browsers not follow the spec on this one?

As we saw before, letter spacing isn’t straightforward for computers since they must stick to a strategy for where spacing is applied. In the case of browsers, the standard has been to apply an individual space at the end of each character, ignoring if that space goes beyond the full word. It may have not been the best choice, but it’s what the web has leaned into, and changing it now would result in all kinds of text and layout shifts across the web.

This leaves a space at the end of elements with bigger letter spacing, which is somewhat acceptable for LTR text, but it leaves a hole at the beginning of the text in an RTL writing mode.

The issue is more obvious with centered text, where the ending space pushes the text away from the element’s dead center. You’ve probably had to add padding on the opposite side of an element to make up for any letter-spacing you’ve applied to the text at least one time, like on a button.

As you can see, the blue highlight creates a symmetrical pyramid which our text sadly doesn’t follow.

What’s worse, the “end of each character” means something different to browsers, particularly when working in an RTL writing mode. Chrome and Safari (Blink/WebKit) say the end of a character is always on the right-hand side. Firefox (Gecko), on the other hand, adds space to the “reading” end — which in Hebrew and Arabic is the left-hand side. See the difference yourself:

Side-by-side comparison of letter spacing on Gecko and Blink/Webkit

Can this be fixed?

The first thought that comes to mind is to simply follow what the spec says and trim the unnecessary space at the ending character, but this (anti) solution brings compatibility risks that are simply too big to even consider; text measurement and line breaks would change, possibly causing breakage on lots of websites. Pages that have removed that extra space with workarounds probably did it by offsetting the element’s padding/margin, which means changing the behavior as it currently stands makes those offsets obsolete or breaking.

There are two real options for how letter-spacing can be fixed: reworking how the space is distributed around the character or allowing developers an option to choose where we want the ending space.

Option 1: Reworking the space distribution

The first option would be to change the current letter-spacing definition so it says something like this:

Specifies additional spacing applied to each typographic character unit except those with zero advance. The additional spacing is divided equally between the inline-start and -end sides of the typographic character unit. Values may be negative, but there may be implementation-dependent limits.

Simply put, instead of browsers applying the additional space at the end of the character, they would divide it equally at the start and end, and the result is symmetrical text. This would also change text measurements and line breaks, albeit to a lesser degree.

Letter spacing with the symmetrical fix. The letter spacing is equally applied around the letters "b"s

Now text that is center-aligned text is correctly aligned to the center:

Different examples of letter spacing being distributed between letters and achieving a symmetrical look

Option 2: Allowing developers an option to choose

Even if the offset is halved, it could still bring breaking layout shifts to pages which to some is still (rightfully) unacceptable. It’s a dilemma: most pages need, or at least would benefit, from leaving letter-spacing as-is, while new pages would enjoy symmetrical letter spacing. Luckily, we could do both by giving developers the option to choose how the space is applied to characters. The syntax is anybody’s guess, but we could have a new property to choose where to place the spacing:

letter-spacing-justify: [ before | after | left | right | between | around];

Each value represents where the space should be added, taking into account the text direction:

  • before: the spacing is added at the beginning of the letter, following the direction of the language.
  • after: the spacing is added at the end of the letter, following the direction of the language.
  • left: the spacing is added at the left of the letter, ignoring the direction of the language.
  • right: the spacing is added at the right of the letter, ignoring the direction of the language.
  • between: the spacing is added between characters, following the spec.
  • around: the spacing is divided around the letter.

Logically, the current behavior would be the default to not break anything and letter-spacing would become a shorthand for both properties (length and placing).

letter-spacing: 1px before;
letter-spacing: 1px right;
letter-spacing: 1px around;

letter-spacing: 1px;
/* same as: */
letter-spacing: 1px before;

What about a third option?

And, of course, the third option is to leave things as they are. I’d say this is unlikely since the CSSWG resolved to take action on the issue, and they’ll probably choose the second option if I had to bet the nickel in my pocket on it.

Now you know letter-spacing is broken… and we have to live with it, at least for the time being. But there are options that may help correct the problem down the road.


Letter Spacing is Broken and There’s Nothing We Can Do About It… Maybe originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/letter-spacing-is-broken-and-theres-nothing-we-can-do-about-it-maybe/feed/ 6 379318
Pop(over) the Balloons https://css-tricks.com/popover-the-balloons/ https://css-tricks.com/popover-the-balloons/#comments Thu, 25 Jul 2024 13:31:46 +0000 https://css-tricks.com/?p=379281 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 …


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

]]>
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:

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.

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>🎈</summary>
  <button>🎈</button>
  <button>🎈</button>
  <button>🎈</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>🎈</summary>
  <button popovertarget="lvl1">🎈</button>
</details>

<!-- Level 1 -->
<div id="lvl1" popover="manual">
  <h2>Level 1 Popup</h2>
</div>

This is the idea when everything is wired together:

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>🎈</summary>
  <!-- Show Level 1 Popup -->
  <button popovertarget="lvl1" popovertargetaction="show">🎈</button>
  <!-- Hide Level 1 Popup -->
  <button popovertarget="lvl1" popovertargetaction="hide">🎈</button>
</details>

<!-- Level 1 -->
<div id="lvl1" popover="manual">
  <h2>Level 1 Popup</h2>
  <!-- Open/Close Level 2 Poppup -->
  <button popovertarget="lvl2">🎈</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:

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;
}

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. 👉🎈💥

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);
}

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!

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.

]]>
https://css-tricks.com/popover-the-balloons/feed/ 2 379281
CSS Stuff I’m Excited After the Last CSSWG Meeting https://css-tricks.com/css-stuff-im-excited-after-the-last-csswg-meeting/ https://css-tricks.com/css-stuff-im-excited-after-the-last-csswg-meeting/#comments Fri, 19 Jul 2024 17:16:57 +0000 https://css-tricks.com/?p=379180 From June 11–13, the CSS Working Group (CSSWG) held its second face-to-face meeting of the year in Coruña, Spain, with a long agenda of new features and improvements coming to language. If 2023 brought us incredible advances like …


CSS Stuff I’m Excited After the Last CSSWG Meeting originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
From , the CSS Working Group (CSSWG) held its second face-to-face meeting of the year in Coruña, Spain, with a long agenda of new features and improvements coming to language. If 2023 brought us incredible advances like out-of-the-box nesting, container and style queries, or the has: selector, then 2024 is going to be even more packed with even more ground-breaking additions. Whether a new feature like inline conditionals is just starting or long-term projects are wrapping up, 2024 is already filled with exciting developments — and we’re still in July!

I wanted to share what I think are some of the most interesting and significant features coming to CSS that were examined in the meeting. However, I don’t want you to take the following as an exact recap of the discussions. Instead, I want to bring up the broader topics coming to CSS that had a spotlight at the last meeting. In reality, the features examined have been cooking up for even years and the discussions are geared towards specific cases and new enhancements, rather than defining a whole specification; a work that would be impossible in one meeting.

You can see the exact issues discussed on the CSSWG meeting agenda.

Feature 1: What if we get if()?

Since CSS custom properties gained reliable support around 2016, there have been many attempts to apply certain styles depending on a custom property value without, of course, appealing to JavaScript. One of the earliest workarounds for conditional styles was posted by Roman Komarov back in 2016 in “Conditions for CSS Variables”. From there, many other hacks have been documented for making conditional declarations in CSS (including this extremely clever one by Ana Tudor here on CSS-Tricks). In fact, you can find a full list that discusses and compares those workarounds by CSSWG member Lea Verou in her recent article, “Inline conditionals in CSS, now?”.

What’s for sure is that the community has craved a conditional way to apply styles using custom properties. Nowadays, we have a specification for Style Queries that’s capable of the task, but they come with limitations not related to browser support. The biggest of those limitations? We can’t directly style the container that’s queried, so we need some sort of wrapper element around that wrapper in HTML.

<div class="news-container" style="--variant: info">
  <p>Here is some good <strong>news</strong></p>
</div>

…in addition to writing the style query:

.news-container {
  container-name: news-container;
}

@container news-container style(--variant: info) {
  p {
    color: blue;
    border: 1px solid blue;
  }
}

What if() might look like

On the CSSWG side, there have been discussions about adding an if() function as far back as 2018. It was of this year — yes, six years later — that the CSSWG resolved to begin working on if() for CSS. As good as it may look, don’t expect to see if() in a browser in at least two years! (That’s Lea’s unofficial estimate.) We’ll likely need to wait even longer for enough browser support to begin using it reliably in production. The spec draft is only barely getting started and many things have to pass a test first. For context, the CSS variables working draft began in 2012 and only received wide browser support in 2016.

Syntax-wise, if() is probably going to borrow the ternary operator from JavaScript and other programming languages, structured like this:

if(a ? b : c)

…where a is the custom property we are checking and b are c are the possible conditional return values. To check for styles, an inline style(--my-property: value) would be used.

.forecast {
  background-color: if(style(--weather: clouds) ? var(--clouds-color): var(--default-color));
}

Even if ? isn’t used in CSS and : has a different meaning everywhere else, I think this syntax is the one most people are familiar with, not to mention it also allows seamless conditional chaining.

.forecast {
  background-color: if(
    style(--weather: clouds) ? var(--clouds-color): 
    style(--weather: sunny) ? var(--sunny-color);
    style( --weather: rain) ? var(--rain-color): var(--default-color)
  );
}

Future if() improvements

Although these probably won’t make it in the initial release, it’s interesting to see how if() might change between now and sometime further in the future:

  • Support for other inline conditionals. We are supposed to check for custom properties using the style() query, but we may as well check for media features with an inline media() query or if a user agent supports a specific property with an inline support().
.my-element {
  width: if(media(width > 1200px) ? var(--size-l): var(--size-m));
}
  • Using conditional inside other CSS functions. In future drafts, we may use ternaries inside other functions without having to wrap them around if(), e.g. just as we can make calculations without calc() if we are inside a clamp() or round() function.

Feature 2: Cross-document view transitions

Last year, the View Transition API gave us the power to create seamless transitions when navigating between web pages and states. No components or frameworks, no animation libraries — just vanilla HTML and CSS with a light sprinkle of JavaScript. The first implementation of View Transitions was baked into browsers a while back, but it was based on an experimental function defined by Chrome and was limited to transitions between two states (single-page view transitions) without support for transitioning between different pages (i.e., multi-page view transitions), which is what most of us developers are clamoring for. The possibilities for mimicking the behavior of native apps are exciting!

That’s why the CSS View Transitions Module Level 2 is so amazing and why it’s my favorite of all the CSS additions we’re covering in this article. Yes, the feature brings out-of-the-box seamless transitions between pages, but the real deal is it removes the need for a framework to achieve it. Instead of using a library — say React + some routing library — we can backtrack into plain CSS and JavaScript.

Of course, there are levels of complexity where the View Transition API may fall short, but it’s great for countless cases where we just want page transitions without the performance cost of dropping in a framework.

Opting into view transitions

View transitions are triggered when we navigate between two pages from the same-origin. In this context, navigation might be clicking a link, submitting a form, or going back and forth with browser buttons. By contrast, something like using a search bar between same-origin pages won’t trigger a page transition.

Both pages — the one we’re navigating away from and the one we’re navigating to — need to opt into the transition using the @view-transition at-rule and setting the navigation property to auto

@view-transition {
  navigation: auto;
}

When both pages opt into a transition, the browser takes a “snapshot” of both pages and smoothly fades the “before” page into the “after” page.

Transitioning between “snapshots”

In that video, you can see how the old page fades into the new page, and it works thanks to an entire tree of new pseudo-elements that persist through the transition and use CSS animations to produce the effect. The browser will group snapshots of elements with a unique view-transition-name property that sets a unique identifier on the transition that we can reference, and which is captured in the ::view-transition pseudo-element holding all of the transitions on the page.

You can think of ::view-transition as the :root element for all page transitions, grouping all of the parts of a view transition on the same default animation.

::view-transition
├─ ::view-transition-group(name)
│  └─ ::view-transition-image-pair(name)
│     ├─ ::view-transition-old(name)
│     └─ ::view-transition-new(name)
├─ ::view-transition-group(name)
│  └─ ::view-transition-image-pair(name)
│     ├─ ::view-transition-old(name)
│     └─ ::view-transition-new(name)
└─ /* and so one... */

Notice that each transition lives in a ::view-transition-group that holds a ::view-transition-image-pair that, in turn, consists of the “old” and “new” page snapshots. We can have as many groups in there as we want, and they all contain an image pair with both snapshots.

Quick example: let’s use the ::view-transition “root” as a parameter to select all of the transitions on the page and create a sliding animation between the old and new snapshots.

@keyframes slide-from-right {
  from {
    transform: translateX(100vw);
  }
}

@keyframes slide-to-left {
  to {
    transform: translateX(-100vw);
  }
}

::view-transition-old(root) {
  animation: 300ms ease-in both slide-to-left;
}

::view-transition-new(root) {
  animation: 300ms ease-out both slide-from-right;
}

If we navigate between pages, the entire old page slides out to the left while the entire new page slides in from the right. But we may want to prevent some elements on the page from participating in the transition, where they persist between pages while everything else moves from the “old” snapshot to the “new” one.

That’s where the view-transition-name property is key because we can take snapshots of certain elements and put them in their own ::view-transition-group apart from everything else so that it is treated individually.

nav {
  view-transition-name: navigation;

  /* 
    ::view-transition
    ├─ ::view-transition-group(navigation)
    │  └─ ::view-transition-image-pair(navigation)
    │     ├─ ::view-transition-old(navigation)
    │     └─ ::view-transition-new(navigation)
    └─ other groups...
  */
}

You can find a live demo of it on GitHub. Just note that browser support is limited to Chromium browsers (i.e., Chrome, Edge, Opera) at the time I’m writing this.

There are many things we can look forward to with cross-document view transitions. For example, If we have several elements with a different view-transition-name, we could give them a shared view-transition-class to style their animations in one place — or even customize the view transitions further with JavaScript to check from which URL the page is transitioning and animate accordingly.

Feature 3: Anchor Positioning

Positioning an element relative to another element in CSS seems like one of those no-brainer, straightforward things, but in reality requires mingling with inset properties (top, bottom, left, right) based on a series of magic numbers to get things just right. For example, getting a little tooltip that pops in at the left of an element when hovered might look something like this in HTML:

<p class="text">
  Hover for a surprise
  <span class="tooltip">Surprise! I'm a tooltip</span>
</p>

…and in CSS with current approaches:

.text {
  position: relative;
}


.tooltip {
  position: absolute;
  display: none;

  /* vertical center */
  top: 50%;
  transform: translateY(-50%);

  /* move to the left */
  right: 100%;
  margin-right: 15px; */
}

.text:hover .tooltip {
  display: block;
}

Having to change the element’s positioning and inset values isn’t the end of the world, but it sure feels like there should be an easier way. Besides, the tooltip in that last example is extremely fragile; if the screen is too small or our element is too far to the left, then the tooltip will hide or overflow beyond the edge of the screen.

CSS Anchor Positioning is yet another new feature that was discussed in the CSSWG meetings and it promises to make this sort of thing much, much easier.

Creating an anchor

The basic idea is that we establish two elements:

  • one that acts as an anchor, and
  • one that is a “target” anchored to that element.

This way, we have a more declarative way to associate one element and position it relative to the anchored element.

To begin we need to create our anchor element using a new anchor-name property.

Changing our markup a little:

<p>
  <span class="anchor">Hover for a surprise</span>
  <span class="tooltip">Surprise! I'm a tooltip</span>
</p>

We give it a unique dashed-indent as its value (just like a custom property):

.anchor {
  anchor-name: --tooltip;
}

Then we relate the .tooltip to the .anchor using the position-anchor property with either fixed or absolute positioning.

.toolip {
  position: fixed;
  position-anchor: --tooltip;
}

The .tooltip is currently positioned on top of the .anchor, but we ought to move it somewhere else to prevent that. The easiest way to move the .tooltip is using a new inset-area property. Let’s imagine that the .anchor is placed in the middle of a 3×3 grid and we can position the tooltip inside the grid by assigning it a row and column.

Three by three grid with yellow element in the center tile labeled 'anchor'.

The inset-area property takes two values for the .tooltip‘s in a specific row and column on the grid. It counts with physical values, like left, right, top and bottom, as well logical values depending on the user’s writing mode, like start and end, in addition to a center shared value. It also accepts values referencing x- and y-coordinates, like x-start and y-end. All these value types are ways of representing a space on the 3×3 grid.

For example, if we want the .tooltip to be positioned relative to the top-right edge of the anchor, we can set the inset-area property like this:

.toolip {
  /* physical values */
  inset-area: top right;

  /* logical values */
  inset-area: start end;

  /* mix-n-match values! */
  inset-area: top end;
}

Lastly, if we want our tooltip to span across two regions of the grid, we can use a span- prefix. For example, span-top will place the .tooltip in the grid’s top and center regions. If instead we want to span across an entire direction, we can use the span-all value.

Three by three grid with a yellow element in the center labeled 'anchor' surrounded by three tooltip examples demonstrating how tooltips are placed on the grid, including code examples for each example.

One of the problems with our anchor-less example is that the tooltip can overflow outside the screen. We can solve this using another new property, this time called position-try-options, in combination with a new inset-area() function.

(Yes, there is inset-area the property and inset-area() the function. That’s one we’ll have to commit to memory!)

The position-try-options property accepts a comma-separated list of fallback positions for the .tooltip when it overflows outside the screen. We can provide a list of inset-area() functions, each holding the same values that the inset-area property would. Now, each time the tooltip goes out off-screen, the next declared position is “tried”, and if that position causes an overflow, the next declared position is tried, and so on.

.toolip {
  inset-area: top left;
  position-try-options: inset-area(top), inset-area(top right);
}

This is a pretty wild concept that will take some time to grok. CSSWG member Miriam Suzanne sat down to discuss and tinker with anchor positioning with James Stuckey Weber in a video that’s well worth watching.

Geoff Graham took notes on the video if you’re looking for a TL;DW.

There are still many aspects to anchor positioning we aren’t covering here for brevity, notably the new anchor() function and @try-position at-rule. The anchor() function returns the computed position of the edge of an anchor, which provides more control over a tooltip’s inset properties. The @try-position at-rule is for defining custom positions to set on the position-try-options property.

My hunch is that using inset-area will be plenty robust for the vast majority of use cases.

The CSSWG is a collective effort

Earlier I said that this article wouldn’t be an exact retelling of the discussions that took place at the CSSWG meetings, but rather a broad representation of new specs coming to CSS that, due to their novelty, were bound to come up in those meetings. There are even some features that we simply hadn’t the time to review in this roundup that are still subject to debate (cough, masonry).

One thing is for sure: specs aren’t made in some vacuum over one or two meetings; it takes the joined effort of tens of amazing authors, developers, and user agents to bring to life what we use every day in our CSS work — not to mention the things we will use in the future.

I also had the opportunity to talk with some amazing developers from the CSSWG, and I found it interesting what their biggest takeaways were from the meetings. You might expect if() is at the top of their lists since that’s what is buzzing in socials. But CSSWG member Emilio Cobos told me, for example, that the letter-spacing property is essentially flawed and there isn’t a simple solution for fixing it that’s copasetic with how letter-spacing is currently defined by CSS and used in browsers. That includes the fact that converting normal properties into shorthand properties can be dangerous to a codebase.

Every tiny detail we might think of as trivial is carefully analyzed for the sake of the web and for the love of it. And, like I mentioned earlier, this stuff is not happening in a closed vacuum. If you’re at all interested in the future of CSS — whether that simply keeping up with it or getting actively involved — then consider any of the following resources.


CSS Stuff I’m Excited After the Last CSSWG Meeting originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-stuff-im-excited-after-the-last-csswg-meeting/feed/ 4 https://www.youtube.com/embed/76hIB2L_vs4 CSS Anchor Positioning in Practice - Winging It Live nonadult 379180
CSS Selectors https://css-tricks.com/css-selectors/ https://css-tricks.com/css-selectors/#comments Mon, 15 Jul 2024 16:13:15 +0000 https://css-tricks.com/?p=378745 A complete guide covering all of the various methods we have to select elements in CSS and how to use them for applying styles.


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

]]>

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.

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!

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.

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:

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.

Showing element, class, and ID selectors in a horizontal row from least specific to most specific.

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.

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.

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.

<!-- 👎 No match -->
<a href="https://CSS-Tricks.com">...</a>

<!-- 👍 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.

<!-- 👍 I match! -->
<a href="https://CSS-Tricks.com">...</a>

<!-- 👍 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! 💥 */
* {
  /* 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.


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! 🤗 */
}

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.

Desktop

ChromeFirefoxIEEdgeSafari
120117No12017.2

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712717.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! 👎 */
  article.article-element {
    /* Container styles */  

    /* Nope! 👎 */
    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.

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:

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

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

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

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:

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


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.

]]>
https://css-tricks.com/css-selectors/feed/ 2 378745
Poppin’ In https://css-tricks.com/poppin-in/ https://css-tricks.com/poppin-in/#comments Wed, 26 Jun 2024 16:37:05 +0000 https://css-tricks.com/?p=378637 Oh, hey there! It’s been a hot minute, hasn’t it? Thought I’d pop in and say hello while we get to know the Popover API a bit.


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

]]>
Oh, hey there! It’s been a hot minute, hasn’t it? Thought I’d pop in and say hello. 👋

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>👋</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.

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

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.

DevTools inspector showing the computed values for an element with the popover attribute.
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">👋</div>

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

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 👎 */
#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.

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.

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.

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.

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?

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.

]]>
https://css-tricks.com/poppin-in/feed/ 2 378637
CSS Container Queries https://css-tricks.com/css-container-queries/ https://css-tricks.com/css-container-queries/#comments Mon, 10 Jun 2024 16:12:50 +0000 https://css-tricks.com/?p=378111 The main idea of CSS Container Queries is to register an element as a “container” and apply styles to other elements when the container element meets certain conditions.


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

]]>

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.

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 defaultnoneatno, and or. Note that the names are not wrapped in quotes.
  • 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.
  • 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.

  • 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 width
cqhContainer 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.

Desktop

ChromeFirefoxIEEdgeSafari
130NoNo127TP

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
127No12718.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.

Desktop

ChromeFirefoxIEEdgeSafari
106110No10616.0

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712716.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.

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.

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.

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.

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.

Articles & Tutorials

General Information

Container Size Query Tutorials

Container Style Queries

References


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

]]>
https://css-tricks.com/css-container-queries/feed/ 3 378111
CSS Length Units https://css-tricks.com/css-length-units/ https://css-tricks.com/css-length-units/#comments Mon, 03 Jun 2024 19:22:02 +0000 https://css-tricks.com/?p=375815 A comprehensive guide covering nine types of lengths that CSS uses to size elements in terms of dimensions, space, time, and even sound.


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

]]>

Overview

Many CSS properties accept numbers as values. Sometimes those are whole numbers. Sometimes they’re decimals and fractions. Other times, they’re percentages. Whatever they are, the unit that follows a number determines the number’s computed length. And by “length” we mean any sort of distance that can be described as a number, such as the physical dimensions of an element, a measure of time, geometric angles… all kinds of things!

At the time of this writing, the CSS Values and Units Module Level 4 specification defines a bunch of different CSS units — and many of those are relatively new (this pun will make sense later).

Quick Reference

Absolute units
UnitName
cmCentimeters
mmMillimeters
QQuarter-millimeters
inInches
pcPicas
ptPoints
pxPixels
Font units
UnitRelative to…
emThe font size of the element, or its nearest parent container
exThe 0-height of the element’s font 
capThe cap height (the nominal height of capital letters) of the element’s font 
chThe width of the 0 character of the font in use
icThe average width of a full glyph of the font in use, as represented by the “水” (U+6C34) glyph
remThe font-size value that’s set on the root (html) element
lhThe line-height value that’s set on the element 
rlhThe line-height that’s set on the root (html) element 
vw1% of viewport’s width
vh1% of viewport’s height
vi1% of viewport’s size in the root element’s inline axis
vb1% of viewport’s size in the root element’s block axis
vminEqual to 1% of the vw or vh, whichever is smaller
vmaxEqual to 1% of the vw or vh, whichever is larger
Viewport units
vw1% of viewport’s width
vh1% of viewport’s height
vi1% of viewport’s size in the root element’s inline axis
vb1% of viewport’s size in the root element’s block axis
vmin1% of the vw or vh, whichever is smaller
vmax1% of the vw or vh, whichever is larger
Container units
UnitRelative to
cqw1% of a query container’s width
cqh1% of a query container’s height
cqi1% of a query container’s inline size
cqb1% of a query container’s block size
cqminThe smaller value of cqi or cqb
cqmaxThe larger value of cqi or cqb
Angle units
UnitDescription
degThere are 360 degrees in a full circle.
gradThere are 400 gradians in a full circle.
radThere are 2π radians in a full circle.
turnThere is 1 turn in a full circle.
Time units
UnitDescription
sThere are 60 seconds in a minute, but there is no unit for minutes.
msThere are 1,000 milliseconds in a second.
Fractional units
UnitDescription
frOne fraction of the free space in a grid container.
Resolution units
UnitDescription
dpiDots per inch
dpcmDots per centimeter
dppxxDots per pixel unit
Frequency units
UnitDescription
HzRepresents the number of occurrences per second
kHzOne kiloHertz is equal to 1000 Hertz.

Introduction

You’re going to see a lot numbers in CSS. Here are a few examples?

/* Integers */
1

/* Pixels */
14px

/* em */
1.5em

/* rem */
3rem

/* Percentage */
50%

/* Characters */
650ch

/* Viewport units */
100vw
80vh
50dvh

/* Container units */
100cqi
50cqb

While these all mean different things, they essentially do the same thing: define an element’s dimensions in CSS. We need units in CSS because they determine how to size elements on a page, whether it’s the height of a box, the width of an image, the font-size of a heading, the margin between two elements, how long an animation runs, etc. Without them, the browser would have no way of knowing how to apply numbers to an element.

So, what the heck is px? What’s up with this thing called rem? How are these different than other length units? The unit defines what type of number we’re dealing with, and each one does something different, giving us lots of ways to size things in CSS.


Types of numbers

You may think a number is just a number, and you’re not wrong. Numbers are numbers! But we can distinguish between a few different types of numbers, which is helpful context for discussing the different types of units we attach them to since “number” can mean, well, a number of different things.

  • Integers (literally a unit-less number, e.g. 3)
  • Numbers (same as an integer, only measured in decimals, e.g. 3.2)
  • Dimensions (either a number or integer with a unit, e.g. 3.2rem)
  • Ratios (the quotient between two divided numbers, e.g. 3/2)
  • Percentages (e.g. 3%)

Got that? They’re all numbers but with nuances that make them ever-so-slightly different.

From here, we can think of numbers in CSS as falling into two specific types of unitsabsolute and relative. Let’s start things off our deep dive on CSS length units by breaking those down.


Absolute units

An absolute unit is like Bill Murray in the movie Groundhog Day: it’s always the same. In other words, whatever the number is, that’s exactly how it computes in the browser regardless of how other elements are sized.

The most common absolute value you’ll see is the pixel value. It’s sort of hard to define, but a pixel is the smallest building block of a graphical display, like a computer screen. And it’s based on the resolution of the screen. So, if you’re on a super high-resolution screen, a pixel will be smaller than it would be on a low-resolution screen, as the resolution can pack more pixels into a smaller amount of space for higher clarity. But look at the example below. All of the boxes are sized with pixels, so you can get a sense of how large 50px is compared to 250px.

Absolute values are nice in that they are predictable. That could, however, change in some situations, particularly when it comes to zooming. If, say, a user zooms into a page using browser settings, then anything defined with an absolute value is going to increase its absolute size accordingly. So, if the font-size of a paragraph is set to 20px, the paragraph is going to be larger as the user zooms closer into the page. And because zooming is often used to make content more readable, using absolute values that retain their sizing could be a good approach for making pages more accessible by allowing users to zoom things up to a spot that more comfortable to read.

But then again, see Josh Collinsworth’s click-baity, but fantastic, post titled “Why you should never use px to set font-size in CSS” for an exhaustive explanation of how pixels behave when used to set the font-size of an element. It’s a great read to better understand the behavior and limitations of pixel units.

And, hey: pixels are only one of many types of absolute lengths that are available in CSS. In fact, we can group them by the types of things they measure:

Length units

Length units are a little funny because they can technically be either an absolute unit or a relative unit. But we’re discussing them in absolute terms at the moment and will revisit them when we get further along to relative length units.

A length is essentially a dimension, which is any integer proceeded by a unit, according to the list of types of numbers we looked at earlier. And when we talk about dimensions, we’re really talking about the physical size of an element.

UnitName
cmCentimeters
mmMillimeters
QQuarter-millimeters
inInches
pcPicas
ptPoints
pxPixels

What we’re looking at here are the types of units you might use see on a tape measure (e.g., cm and in) or in print design (e.g. pc and pt). They are what they are and what you see is what you get.

Angle units

Angle units are purely geometric. They’re good for setting shape dimensions — like a circle’s radius, setting the direction of a linear-gradient(), or setting the how much we want to rotate() something.

UnitNameDescriptionExample
degDegreesA full circle is equal to 360deg.rotate(180deg)
gradGradiensA full circle is equal to 400grad.rotate(200grad)
radRadiensA full circle is equal to (i.e., 2 × 3.14), or about 6.2832rad.rotate(3.14rad)
turnTurnsA full circle is 1turn, like a bicycle wheel making one full rotation.rotate(.5turn)
Time units

Time units are what you’d expect to find on a clock or watch, but only measure in seconds and milliseconds. Apparently the web cannot be measured in minutes, hours, days, weeks, months, or years. Perhaps we’ll get a new category of “calendar units” at some point, or maybe there’s no good use case for that sort of thing. 🤷‍♂️

UnitNameDescriptionExample
sSecondsOne full minute of time is equal to 60s.animation-duration: 2s
msMillisecondsOne full second of time os equal to 1000ms.animation-duration: 2000ms
Frequency units

You won’t see frequency units used very often and for good reason: they’re not supported by any browser at the time of this writing. But they’re specced to change sound frequency, such as a sound’s pitch. The best I can make of it as it currently stands is that frequencies can be used to manipulate an audio file with a higher or lower pitch measured in hertz and kilohertz.

UnitNameDescriptionExample
HzHertzMeasures the number of frequencies per second<source src="tubthumping.mp3" type="audio/mpeg" frequency="100Hz">
kHzKilohertzA value of 1Hz is equal to 0.001kHz.<source src="tubthumping.mp3" type="audio/mpeg" frequency="0.1kHz">

If you’re wondering what constitutes a “low” pitch from a “high” one, the spec explains it like this:

[W]hen representing sound pitches, 200Hz (or 200hz) is a bass sound, and 6kHz (or 6khz) is a treble sound.

Resolution units

Resolution is how many little dots are packed into a screen — such as the screen you’re looking at right now — where more dots per inch of space improves the clarity and quality of the display. The fewer dots there are, the more pixelated and blurry the display.

Why would you need something like this? Well, it’s great for targeting styles to specific screens that support certain resolutions in a media query.

img {
  max-width: 500px;
}

/* Double the resolution and above */
@media (min-resolution >= 2dppx) {
  img {
    max-width: 100%;
  }
}
UnitNameDescriptionExample
dpiDots per inchThe number of dots packed into one inch of space.@media
(min-resolution: 96dpi) {}
dpcmDots per centimeterThe number of dots packed into one centimeter of space.@media
(min-resolution: 960dpcm) {}
dppx (or x)Dots per pixelThe number of dots packed into one pixel of space.@media
(min-resolution: 1dppx) {}

Interestingly, the specification makes mention of an infinite value that is supported by resolution media queries for targeting screens without resolution constraints. It’s not so much of a “catch-all” value for targeting any sort of screen, but for cases when we’re using the media query range syntax to evaluate whether a certain value is greater than, less than, or equal to it:

For output mediums that have no physical constraints on resolution (such as outputting to vector graphics), this feature must match the infinite value. For the purpose of evaluating this media feature in the range contextinfinite must be treated as larger than any possible <resolution>. (That is, a query like (resolution > 1000dpi)will be true for an infinite media.)

W3C Media Queries Level 4 specification

Relative units

relative unit is extremely well-named because whatever value we use for a relative unit depends on the size of something else. Say we have an HTML element, a <div>, and we give it an absolute height value (not a relative one) of 200px.

<div class="box">
  I am 200 pixels tall
</div>
.box {
  height: 200px;
}

That height will never change. The .box element will be 200px tall no matter what. But let’s say we give the element a relative width (not an absolute one) of 50%.

<div class="box">
  I am 200 pixels tall and 50% wide
</div>
.box {
  height: 200px;
  width: 50%;
}

What happens to our box? It takes up 50%, or half, of the available space on the screen.

See that? Go ahead and open that demo in a new window and change the width of the screen. And notice, too, how the height never changes because it’s an absolute length unit in pixels. The width, meanwhile, is fluidly resized as “50% of the available space” changes with the width of the screen.

That’s what we mean when talking about computed values with relative numbers. A relative number acts sort of like a multiplier that calculates the value used to set a length based on what type of unit it is relative to. So, a value of 3rem is going to wind up becoming a different value when it is computed.

Percentages, like 50%, are only one kind of relative unit. We have many, many others. Once again, it’s helpful to break things out into separate groups to understand the differences just as we did earlier with absolute units.

Percentages

We’ve already discussed percentages in pretty good detail. What makes a percentage relative is that it computes to a number value based on the length of another element. So, an element that is given width: 25% in a container that is set to width: 1000px computes to width: 250px.

UnitNameRelative to…
%PercentThe size of the element’s parent container.
Font relative units

The em and rem units we looked at earlier are prime examples of relative units that you will see all over the place. They’re incredibly handy, as we saw, because changing the font-size value of an element’s parent or the <html> element, respectively, causes the element’s own font-size value to update in accordance with the updated value.

In other words, we do not need to directly change an element’s font-size when updating the font-size of other elements — it’s relative and scales with the change.

UnitNameRelative to…
emElementThe font-size value of the element’s parent container.
remRoot elementThe font-size value of the <html> element.
chCharacterThe width of the 0 character relative to the parent element’s font. The computed width may update when replacing one font with another, except for monospace fonts that are consistently sized.
rchRoot characterThe same thing as a ch unit except it is relative to the font of the root element, i.e. <html>.
lhLine heightThe line-height value of the element’s parent container.
rlhRoot element line heightThe line-height value of the <html> element.
capCapital letterThe height of a capital letter for a particular font relative to the parent element.
rcapRoot capital letterThe same measure as cap but relative to the root element, i.e. <html>.
icInternational characterThe width of a CJK character, or foreign glyph, e.g. from a Chinese font, relative to an element’s parent container.
ricRoot international characterThe same measure as ic but relative to the root element, i.e. <html>.
exX-heightThe height of the letter x of a particular font, or an equivalent for fonts that do not contain an x character, relative to the parent element.
rexRoot x-heightThe same measure as ex but relative to the root element, i.e. <html>.

Some of those terms will make more sense to typography nerds than others. The following diagram highlights the lines that correspond to relative font units.

Four lines drawn over a line of text illustrating the ascender height, x-height, baseline, and descender height.

So, at the expense of beating this concept into the ground, if width: 10ch computes to a certain number of pixels when using one font, it’s likely that the computed value will change if the font is swapped out with another one with either larger or smaller characters.

Viewport relative units
UnitNameRelative to…
vh / vwViewport Height / Viewport WidthThe height and width of the viewport (i.e., visible part of the screen), respectively.
vmin / vmaxViewport Minimum / Viewport MaximumThe lesser and greater of vh and vw, respectively.
lvh / lvwLarge Viewport Height / Large Viewport WidthThe height and width of the viewport when the device’s virtual keyboard or browser UI is out of view, leaving a larger amount of available space.
lvb / lviLarge Viewport Block / Large Viewport InlineThese are the logical equivalents of lvh and lvw, indicating the block and inline directions.
svh / svwSmall Viewport Height / Small Viewport WidthThe height and width of the viewport when the device’s virtual keyboard or browser UI is in view, making the amount of available space smaller.
svb / sviSmall Viewport Block / Small Viewport InlineThese are the logical equivalents of svh and svw, indicating the block and inline directions.
dvh / dvwDynamic Viewport Height / Dynamic Viewport WidthThe height and width of the viewport accounting for the available space changing if, say, the device’s virtual keyboard or browser UI is in view.
dvb / dviDynamic Viewport Block / Dynamic Viewport InlineThese are the logical equivalents of dvh and dvw, indicating the block and inline directions.
dvmin / dvmaxDynamic Viewport Minimum / Dynamic Viewport MaximumThe lesser and greater of dvh/dvb and dvw/dvi, respectively.

Ah, viewport units! When we say that something should be 100% wide, that means it takes up the full width of the contain it is in. That’s because a percentage unit is always relative to its nearest parent element. But a  viewport unit is always relative to the size of the viewport, or browser window. If an element has a viewport height of 100vh and a viewport width of 100vw, then it will be as tall and wide as the full browser window.

This can be a neat way to create something like a hero banner at the top of your website. For example, we can make a banner that is always one half (50vh) the height of the viewport making it prominent no matter how tall someone’s browser is. Change the CSS in the top-left corner of the following demo from height: 50vh to something else, like 75vh to see how the banner’s height responds.

There’s something else that’s very important to know when working with viewport units. You know how mobile phones, like iPhone or an Android device, have virtual keyboards where you type directly on the screen? That keyboard changes the size of the viewport. That means that whenever the keyboard opens, 100vh is no longer the full height of the screen but whatever space is leftover while the keyboard is open, and the layout could get super squished as a result.

That’s why we have the svh, lvh, and dvh units in addition to vh:

  • svh is equal to the “small” viewport height, which occurs when the keyboard is open.
  • lvh is equal to the “large” viewport height, which is when the keyboard is disabled and out of view.
  • dvh is a happy medium between svh and lvh in that it is “dynamic” and updates its value accordingly when the keyboard is displayed or not.
  • dvmin / dvmax is the greater ore lesser of dvh, respectively.

It’s a bit of a tightrope walk in some cases and a good reason why container queries and their units (which we’ll get to next) are becoming more popular. Check out Ahmed Shader’s article “New Viewport Units” for a comprehensive explanation about viewport units with detailed examples of the issues you may run into. You may also be interested in Sime Vidas’s “New CSS Viewport Units Do Not Solve The Classic Scrollbar Problem” for a better understanding of how viewport units compute values.

Container relative units
UnitNameEquivalent to…
cqwContainer query width1% of the queried container’s width
cqhContainer query height1% of the queried container’s height
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.
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 units are designed to be used with container queries. Just as we are able to target a specific screen size with a media query, we can target the specific size of a particular element and apply styles using a container query.

We won’t do a big ol’ deep dive into container queries here. The relevant bit is that we have CSS length units that are relative to a container’s size. For example, if we were to define a container:

.parent-container {
  container-type: inline-size;
}

…then we’re watching that element’s inline-size — which is equivalent to width in a horizontal writing mode — and can apply styles to other elements when it reaches certain sizes.

.child-element {
  background: rebeccapurple;
  width: 100%;

  @container parent (width > 30ch) {
    .child-element {
      background: dodgeblue;
      width: 50cqi;
    }
  }
}

Try resizing the following demo. When the parent container is greater than 30ch, the child element will change backgrounds and shrink to one-half the parent container’s width, or 50cqi.


What about unit-less numbers?

Oh yeah, there are times when you’re going to see numbers in CSS that don’t have any unit at all — just a single integer or number without anything appended to it.

aspect-ratio: 2 / 1; /* Ratio */
column-count: 3; /* Specifies a number of columns */
flex-grow: 1; /* Allows the element to stretch in a flex layout */
grid-column-start: 4; /* Places the element on a specific grid line */
order: 2; /* Sets the order of elemnents in a flex or grid layout */
scale: 2; /* The elementis scaled up or down by a factor */
z-index: 100; /* Element is placed in a numbered layer for stacking */
zoom: 0.2;  /* The element zooms in or out by a factor */

This isn’t a comprehensive list of all the CSS properties that accept unit-less numeric values, but it is a solid picture of when you would use them. You’ll see that in most cases a unit-less number is an explicit detail, such as a specific column to position an element, a specific layer in a stacking context, a boolean that enables or disables a feature, or the order of elements. But note that anytime we declare “zero” as a number, we can write it with or without a prepended unit, as zero always evaluates to zero no matter what unit we’re dealing with.

border: 0; /* No border */
box-shadow: 0 0 5px #333; /* No shadow offset */
margin: 0; /* No margin */
padding: 0; /* No padding */

We can create our own custom units!

In some cases, we may want to work with a numeric value, but CSS doesn’t exactly recognize it as one. In these cases, the number is recognized as a “string” value instead, regardless of whether or not it contains alphabetical characters. That’s where we can use @property to create what’s called a “custom property” that evaluates a numeric value in a certain way.

Here’s a good example. There was a time when it was virtually impossible to animate a gradient that changes colors over time because to do so would require (1) a color function that allows us to change a color value’s hue (which we have with hsl()) and (2) being able to interpolate that hue value around the color spectrum, between a range of 0deg and 360deg.

Sounds simple enough, right? Define a variable that starts at 0 and then cycles through 360 degrees at the end of an animation. But this doesn’t work:

/* 👎 */
.element {
  --hue: 0;
  
  animation: rainbow 10s linear infinite;
  background: linear-gradient(hsl(--hue 50% 50%);
}

@keyframes rainbow {
  from { --hue: 0; }
  to { --hue: 360deg; }
}

That’s because CSS reads the variable as a string instead of a number. We have to register that variable as a custom property so that CSS aptly reads it as a numeric value.

@property --hue {
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}

There we go! Now that we’ve given CSS a hint that the --hue syntax is that of a <number>, we can animate away!

/* 👍 */
@property --hue {
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}

.element {
  --hue: 0;
  
  animation: rainbow 10s linear infinite;
  background: linear-gradient(hsl(--hue 50% 50%);
}

@keyframes rainbow {
  from { --huw: 0; }
  to { --hue: 360deg; }
}

Find a deeper explanation of this same example in “Interpolating Numeric CSS Variables” by Geoff Graham.


When to use one unit over another

This is super tricky because CSS is extremely flexible and there are no definitive or hard-and-fast rules for when to use a specific type of CSS length unit over another. In some cases, you absolutely have to use a specific unit because that’s how a certain CSS feature is specced, like using angle units to set the direction of a linear gradient:

.element {
  background: linear-gradient(
    135deg, red, blue;
  )
}

The same goes for the values we use in certain color functions, like using percentages to set the saturation and lightness levels in the hsl() function:

.element {
  background: hsl(0 100% 10%);
}

Speaking of color functions, we define alpha transparency with either an integer or number:

.element {
  background: hsl(0 100% 10% / 0.5); /* or simply .5 */
}

All that being said, many cases are going to be some degree of “it depends” but there are some instances where it makes sense to use a specific unit in your work.

Generally set font sizes in rem and em units

This way, you can set things up in a way where changing the font-size value of the <html> or a parent element updates the font sizes of their descendants.

html {
  font-size: 18px; /* Inherited by all other elements */
}

.parent {
  font-size: 1rem; /* Updates when the `html` size changes */
}

.child {
  font-size: 1em; /* Updates when the parent size changes */
}

Declare “zero” without units if you’d like

It’s not a big deal or anything, but leaving off the units shortens the code a smidge, and anytime we’re able to write shorter code it’s an opportunity for faster page performance. The impact may be negligible, but we’re able to do it since 0 always computes to 0, no matter what unit we’re working with.

Use container units for responsive design, where possible

Container queries in general are so gosh-darn great for responsive layouts because they look at the size of the container and let us apply styles to its descendants when the container is a certain size.

.parent {
  container: my-container / inline-size; /* Looks at width */
}

.child {
  display: flex;
  flex-direction: column;
  max-width: 100vh; /* 100% of the viewport */
}

/* When the container is greater than 600px wide */
@container my-container (width >= 600px) {
  .child {
    flex-direction: row;
    max-width: 50%; /* 50% of the parent elenent */
  }
}

So, if we going to size the .child element — or any of its children — it’s worth specifying sizes in relation to the container’s size with container units than, say, the viewport’s size with viewport units.

.parent {
  container: my-container / inline-size; /* Looks at width */
}

.child {
  display: flex;
  flex-direction: column;
  max-width: 100cqi; /* 100% of the container */
}

/* When the container is greater than 600px wide */
@container my-container (width >= 600px) {
  .child {
    flex-direction: row;
    max-width: 50cqi; /* 50% of the container */
  }
}

Use percentages when you’re unsure of the context

Yes, use container units for responsive design, but that only does you good if you know you are in the context of a container. It’s possible, though, that you use the same component in different places, and one of those places might not be a registered container.

In that case, go with a percentage value because percentages are relative to whatever parent element you’re in, regardless of whether or not it’s a container. This way, you can declare an element’s size as 100% to take up the full space of whatever parent element contains it.

The only word of caution is to note that we’re only basing the size on the parent. Meanwhile, container units can style any descendant in the container, no matter where it is located.

Viewport units are great for laying out containers

You may be thinking that viewport units are a bad thing since we’ve been advising against them in so many cases, like font sizing, but they are still incredibly useful, particularly when it comes to establishing a full-page layout.

I say “full-page” layout because container queries are the gold standard for sizing elements according to the space they have in their container. But if we’re working with a full page of containers, this is where viewport units can be used to establish a responsive layout at a higher level.

If the elements of individual containers look at their container for sizing information, then the sizing and placement of individual containers themselves probably ought to look at the viewport since it directly influences the amount of space on the page.

Examples

Element (em) and Relative element (rem) units

Let’s talk specifically about these two little buggers. We saw how a percentage unit calculates its size by the size of something else. em and rem units are sort of the same, but they are calculated based on the relative font size of specific elements.

Let’s start with em units and say we have an HTML element, a <div> with a .box class, and we set its font size to 20px. That means any text inside of that element is 20px.

Great, now what if we decide we need additional text in the box, and give it a relative font size of 1.5em?

See how a font size of 1.5em is larger than the 20px text? That’s because the larger text is based on the box’s font size. Behind the scenes, this is what the browser is calculating:

20px * 1.5 = 30px

So, the relative font size is multiplied by the pixel font size, which winds up being 30px.

And guess what? rem units do the exact same thing. But instead of multiplying itself by the pixel font size of the parent container, it looks at the pixel font size of the actual <html> element. By default, that is 16px.

/* This is the browser's default font size */
html {
  font-size: 16px;
}

.box {
  font-size: 1.5rem; /* 16px * 1.5 = 24px */
}

And if we change the HTML element’s font size to something else?

html {
  font-size: 18px;
}

.box {
  font-size: 1.5rem; /* 18px * 1.5 = 27px */
}
Character unit (ch)

The character unit (ch) is another is another unit relative to font size and, while it isn’t used all that often, it is amazingly great at sizing things based on the amount of content displayed in an element, because one character unit equals the width of one character of content. Here’s how I wrap my own head around it. If we have this in our HTML:

<p>The big brown dog lazily jumped over the fence.</p>

…and this CSS:

p {
  width: 10ch;
}

What we get is a paragraph that is 10 characters wide. That means the text will break after the tenth character, including spaces.

But note that the words themselves do not break. If the content is supposed to break after 10 characters, the browser will start the next line after a complete word instead of breaking the word into multiple lines, keeping everything easy to read.

If you’re wondering when you might reach for the ch unit, it’s absolutely boss at establishing line lengths that are more pleasant and legible, especially for long form content like this guide you’re reading.

Line height unit (lh)

The line-height unit (lh) looks at the line-height property value of the element’s containing (i.e., parent) element and uses that value to size things up.

.parent {
  line-height: 1.5;
}

.child {
  height: 3lh; /* 3 * 1.5 = 4.5 */
}

When would you use this? Personally, I find lh useful for setting an exact height on something based on the number of lines needed for the text. You can see this clearly demonstrated in Temani Afif’s “CSS Ribbon” effect that uses lh to establish dimensions for each row of text that makes for consistently-sized lines that adapt to whatever the parent element’s font-size value happens to be,


Absolute vs. Relative Units
Container Units
Viewport Units
Typography
Angle Units
Time Units
Resolution Units

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

]]>
https://css-tricks.com/css-length-units/feed/ 9 375815
Demystifying Screen Readers: Accessible Forms & Best Practices https://css-tricks.com/demystifying-screen-readers-accessible-forms-best-practices/ https://css-tricks.com/demystifying-screen-readers-accessible-forms-best-practices/#comments Fri, 19 Apr 2024 14:26:47 +0000 https://css-tricks.com/?p=377733 This is the 3rd post in a small series we did on form accessibility. If you missed the second post, check out “Managing User Focus with :focus-visible. In this post we are going to look at using a …


Demystifying Screen Readers: Accessible Forms & Best Practices originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is the 3rd post in a small series we did on form accessibility. If you missed the second post, check out “Managing User Focus with :focus-visible. In this post we are going to look at using a screen reader when navigating a form, and also some best practices.

Editor’s Note: Edits were made throughout in regard to some of the best practices and code sample additions. If you have ideas and feedback to build on this post, please let us know!

What is a Screen Reader?

You may have heard the term “screen reader” as you have been moving around the web. You might even be using a screen reader at this moment to run manual accessibility tests on the experiences you are building. A screen reader is a type of AT or assistive technology.

A screen reader converts digital text into synthesized speech or Braille output, commonly seen with a Braille reader.

In this example, I will be using Mac VO. Mac VO (VoiceOver) is built-in to all Mac devices; iOS, iPadOS, and macOS systems. Depending on the type of device you are running macOS on, opening VO could differ. The Macbook Pro that is running VO I am writing this on doesn’t have the touch bar, so I will be using the shortcut keys according to the hardware.

Spinning Up VO on macOS

If you are using an updated Macbook Pro, the keyboard on your machine will look something like the image below.

You will start by holding down the cmd key and then pressing the Touch ID three times quickly.

MacBook Pro Keyboard with steps on how to start mac voiceover.

If you are on a MBP (MacBook Pro) with a TouchBar, you will use the shortcut cmd+fn+f5 to turn on VO. If you are using a traditional keyboard with your desktop or laptop, the keys should be the same or you will have to toggle VO on in the Accessibility settings.. Once VO is turned on, you will be greeted with this dialog along with a vocalized introduction to VO.

Welcome to VoiceOver dialog when opening up voiceover.

If you click the “Use VoiceOver” button you are well on your way to using VO to test your websites and apps. One thing to keep in mind is that VO is optimized for use with Safari. That being said, make sure when you are running your screen reader test that Safari is the browser you are using. That goes for the iPhone and iPad as well.

There are two main ways you can use VO from the start. The way I personally use it is by navigating to a website and using a combination of the tab, control, option, shift and arrow keys, I can navigate through the experience efficiently with these keys alone.

Another common way to navigate the experience is by using the VoiceOver Rotor. The Rotor is a feature designed to navigate directly to where you want to be in the experience. By using the Rotor, you eliminate having to traverse through the whole site, think of it as a “Choose Your Own Adventure”.

Modifier Keys

Modifier keys are the way you use the different features in VO. The default modifier key or VO is control + option but you can change it to caps lock or choose both options to use interchangeably.

VoiceOver utility to change the modifier keys.

Using the Rotor

In order to use the Rotor you have to use a combination of your modifier key(s) and the letter “U”. For me, my modifier key is caps lock. I press caps lock + U and the Rotor spins up for me. Once the Rotor comes up I can navigate to any part of the experience that I want using the left and right arrows.

VoiceOver rotor feature showing the Headings navigation.
Using the Rotor in VoiceOver

Another neat way to navigate the experience is by heading level. If you use the combination of your modifier keys + cmd + H you can traverse the document structure based on heading levels. You can also move back up the document by pressing shift in the sequence like so, modifier keys + shift + cmd + H.

Using the Heading Level Shortcut with VoiceOver

History & Best Practices

Forms are one of the most powerful native elements we have in HTML. Whether you are searching for something on a page, submitting a form to purchase something or submit a survey. Forms are a cornerstone of the web, and were a catalyst that introduced interactivity to our experiences.

The history of the web form dates back to September 1995 when it was introduced in the HTML 2.0 spec. Some say the good ole days of the web, at least I say that. Stephanie Stimac wrote an awesome article on Smashing Magazine titled, “Standardizing Select And Beyond: The Past, Present And Future Of Native HTML Form Controls”.

In my opinion, the following are some best practices to follow when building an accessible form for the web.

  1. Labelling implicitly is 100% ok to do. Check out the https://www.w3.org/WAI/tutorials/forms/labels/ article. If the form author is unaware of the id, it can be labeled implicitly. I personally prefer the explicit way.
<!-- Implicit Label -->

<label>
  First Name:
  <input type="text" name="firstname">
</label>
<!-- Explicit Label -->

<label for="name">Name:</label>
<input type="text" id="name" name="name" required/>
  1. If a field is required in order for the form to be complete, use the required attribute. Be sure to have a visual indication that a field is required, too, because non-screenreader users need to know a field is required as well.
<input type="text" id="name" name="name" required />
  1. A button is used to invoke an action, like submitting a form. Use it! Don’t create buttons using div’s. A div is a container without semantic meaning by itself.

Demo

Navigating a Web Form with VoiceOver

If you want to check out the code, navigate to the VoiceOver Demo GitHub repo. If you want to try out the demo above with your screen reader of choice, check out Navigating a Web Form with VoiceOver.

Screen Reader Software

Below is a list of various types of screen reader software you can use on your given operating system. If a Mac is not your machine of choice, there are options out there for Windows and Linux, as well as for Android devices.

NVDA

NVDA is a screen reader from NV Access. It is currently only supported on PC’s running Microsoft Windows 7 SP1 and later. For more access, check out the NVDA version 2024.1 download page on the NV Access website!

JAWS

“We need a better screen reader”

— Anonymous

If you understood the reference above, you are in good company. According to the JAWS website, this is what it is in a nutshell:

“JAWS, Job Access With Speech, is the world’s most popular screen reader, developed for computer users whose vision loss prevents them from seeing screen content or navigating with a mouse. JAWS provides speech and Braille output for the most popular computer applications on your PC. You will be able to navigate the Internet, write a document, read an email and create presentations from your office, remote desktop, or from home.”

JAWS website

Check out JAWS for yourself and if that solution fits your needs, definitely give it a shot!

Narrator

Narrator is a built-in screen reader solution that ships with WIndows 11. If you choose to use this as your screen reader of choice, the link below is for support documentation on its usage.

Complete guide to Narrator

Orca

Orca is a screen reader that can be used on different Linux distributions running GNOME.

“Orca is a free, open source, flexible, and extensible screen reader that provides access to the graphical desktop via speech and refreshable braille.

Orca works with applications and toolkits that support the Assistive Technology Service Provider Interface (AT-SPI), which is the primary assistive technology infrastructure for Linux and Solaris. Applications and toolkits supporting the AT-SPI include the GNOME Gtk+ toolkit, the Java platform’s Swing toolkit, LibreOffice, Gecko, and WebKitGtk. AT-SPI support for the KDE Qt toolkit is being pursued.”

Orca Website

TalkBack

Google TalkBack is the screen reader that is used on Android devices. For more information on turning it on and using it, check out this article on the Android Accessibility Support Site.

Browser Support

If you are looking for actual browser support for HTML elements and ARIA (Accessible Rich Internet Application) attributes, I suggest caniuse.com for HTML and Accessibility Support for ARIA to get the latest 4-1-1 on browser support. Remember, if the browser doesn’t support the tech, chances are the screen reader won’t either.

DigitalA11Y can help summarize browser and screen reader info with their article,  Screen Readers and Browsers! Which is the Best Combination for Accessibility Testing?

Thanks!

Thanks to Adrian Roselli, Karl Groves, Todd Libby, Scott O’Hara, Kev Bonnett, and others for clarifications and feedback!


Demystifying Screen Readers: Accessible Forms & Best Practices originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/demystifying-screen-readers-accessible-forms-best-practices/feed/ 10 https://www.youtube.com/embed/3oRqTDX6VuU Using the Rotor in VoiceOver nonadult 377733
Managing User Focus with :focus-visible https://css-tricks.com/managing-user-focus-with-focus-visible/ https://css-tricks.com/managing-user-focus-with-focus-visible/#comments Fri, 05 Apr 2024 22:13:47 +0000 https://css-tricks.com/?p=377702 This is going to be the 2nd post in a small series we are doing on form accessibility. If you missed the first post, check out Accessible Forms with Pseudo Classes. In this post we are going to look …


Managing User Focus with :focus-visible originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is going to be the 2nd post in a small series we are doing on form accessibility. If you missed the first post, check out Accessible Forms with Pseudo Classes. In this post we are going to look at :focus-visible and how to use it in your web sites!

Focus Touchpoint

Before we move forward with :focus-visible, let’s revisit how :focus works in your CSS. Focus is the visual indicator that an element is being interacted with via keyboard, mouse, trackpad, or assistive technology. Certain elements are naturally interactive, like links, buttons, and form elements. We want to make sure that our users know where they are and the interactions they are making.

Remember don’t do this in your CSS!

:focus {
  outline: 0;
}

/*** OR ***/

:focus {
  outline: none;
}

When you remove focus, you remove it for EVERYONE! We want to make sure that we are preserving the focus.

If for any reason you do need to remove the focus, make sure there is also fallback :focus styles for your users. That fallback can match your branding colors, but make sure those colors are also accessible. If marketing, design, or branding doesn’t like the default focus ring styles, then it is time to start having conversations and collaborate with them on the best way of adding it back in.

What is focus-visible?

The pseudo class, :focus-visible, is just like our default :focus pseudo class. It gives the user an indicator that something is being focused on the page. The way you write :focus-visible is cut and dry:

:focus-visible {
  /* ... */
}

When using :focus-visible with a specific element, the syntax looks something like this:

.your-element:focus-visible {
  /*...*/
}

The great thing about using :focus-visible is you can make your element stand out, bright and bold! No need to worry about it showing if the element is clicked/tapped. If you choose not to implement the class, the default will be the user agent focus ring which to some is undesirable.

Backstory of focus-visible

Before we had the :focus-visible, the user agent styling would apply :focus to most elements on the page; buttons, links, etc. It would apply an outline or “focus ring” to the focusable element. This was deemed to be ugly, most didn’t like the default focus ring the browser provided. As a result of the focus ring being unfavorable to look at, most authors removed it… without a fallback. Remember, when you remove :focus, it decreases usability and makes the experience inaccessible for keyboard users.

In the current state of the web, the browser no longer visibly indicates focus around various elements when they have focus. The browser instead uses varying heuristics to determine when it would help the user, providing a focus ring in return. According to Khan Academy, a heuristic is, “a technique that guides an algorithm to find good choices.”

What this means is that the browser can detect whether or not the user is interacting with the experience from a keyboard, mouse, or trackpad and based on that input type, it adds or removes the focus ring. The example in this post highlights the input interaction.

In the early days of :focus-visible we were using a polyfill to handle the focus ring created by Alice Boxhall and Brian Kardell, Mozilla also came out with their own pseudo class, :moz-focusring, before the official specification. If you want to learn more about the early days of the focus-ring, check out A11y Casts with Rob Dodson.

Focus Importance

There are plenty of reasons why focus is important in your application. For one, like I stated above, we as ambassadors of the web have to make sure we are providing the best, accessible experience we can. We don’t want any of our users guessing where they are while they are navigation through the experience.

One example that always comes to mind is the Two Blind Brothers website. If you go to the website and click/tap (this works on mobile), the closed eye in the bottom left corner, you will see the eye open and a simulation begins. Both the brothers, Bradford and Bryan Manning, were diagnosed at a young age with Stargardt’s Disease. Stargardt’s disease is a form of macular degeneration of the eye. Over time both brothers will be completely blind. Visit the site and click the eye to see how they see.

If you were in their shoes and you had to navigate through a page, you would want to make sure you knew exactly where you were throughout the whole experience. A focus ring gives you that power.

Image of the home page from the Two Blind Brothers website.

Demo

The demo below shows how :focus-visible works when added to your CSS. The first part of the video shows the experience when navigating through with a mouse the second shows navigating through with just my keyboard. I recorded myself as well to show that I did switch from using my mouse, to my keyboard.

Video showing how the heuristics of the browser works based on input and triggering the focus visible pseudo class.
Video showing how the heuristics of the browser works based on input and triggering the focus visible pseudo class.

The browser is predicting what to do with the focus ring based on my input (keyboard/mouse), and then adding a focus ring to those elements. In this case, when I am navigating through this example with the keyboard, everything receives focus. When using the mouse, only the input gets focus and the buttons don’t. If you remove :focus-visible, the browser will apply the default focus ring.

The code below is applying :focus-visible to the focusable elements.

:focus-visible {
  outline-color: black;
  font-size: 1.2em;
  font-family: serif;
  font-weight: bold;
}

If you want to specify the label or the button to receive :focus-visible just prepend the class with input or button respectively.

button:focus-visible {
  outline-color: black;
  font-size: 1.2em;
  font-family: serif;
  font-weight: bold;
}

/*** OR ***/

input:focus-visible {
  outline-color: black;
  font-size: 1.2em;
  font-family: serif;
  font-weight: bold;
}

Support

If the browser does not support :focus-visible you can have a fall back in place to handle the interaction. The code below is from the MDN Playground. You can use the @supports at-rule or “feature query” to check support. One thing to keep in mind, the rule should be placed at the top of the code or nested inside another group at-rule.

<button class="button with-fallback" type="button">Button with fallback</button>
<button class="button without-fallback" type="button">Button without fallback</button>
.button {
  margin: 10px;
  border: 2px solid darkgray;
  border-radius: 4px;
}

.button:focus-visible {
  /* Draw the focus when :focus-visible is supported */
  outline: 3px solid deepskyblue;
  outline-offset: 3px;
}

@supports not selector(:focus-visible) {
  .button.with-fallback:focus {
    /* Fallback for browsers without :focus-visible support */
    outline: 3px solid deepskyblue;
    outline-offset: 3px;
  }
}

Further Accessibility Concerns

Accessibility concerns to keep in mind when building out your experience:

  • Make sure the colors you choose for your focus indicator, if at all, are still accessible according to the information documented in the WCAG 2.2 Non-text Contrast (Level AA)
  • Cognitive overload can cause a user distress. Make sure to keep styles on varying interactive elements consistent

Browser 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.

Desktop

ChromeFirefoxIEEdgeSafari
864*No8615.4

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712715.4

Managing User Focus with :focus-visible originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/managing-user-focus-with-focus-visible/feed/ 7 377702
The Power of :has() in CSS https://css-tricks.com/the-power-of-has-in-css/ https://css-tricks.com/the-power-of-has-in-css/#comments Sat, 30 Mar 2024 02:07:57 +0000 https://css-tricks.com/?p=377635 Hey all you wonderful developers out there! In this post we are going to explore the use of :has() in your next web project. :has() is relatively newish but has gained popularity in the front end community by delivering control …


The Power of :has() in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Hey all you wonderful developers out there! In this post we are going to explore the use of :has() in your next web project. :has() is relatively newish but has gained popularity in the front end community by delivering control over various elements in your UI. Let’s take a look at what the pseudo class is and how we can utilize it.

Syntax

The :has() CSS pseudo-class helps style an element if any of the things we’re searching for inside it are found and accounted for. It’s like saying, “If there’s something specific inside this box, then style the box this way AND only this way.”

:has(<direct-selector>) {
  /* ... */
}

“The functional :has() CSS pseudo-class represents an element if any of the relative selectors that are passed as an argument match at least one element when anchored against this element. This pseudo-class presents a way of selecting a parent element or a previous sibling element with respect to a reference element by taking a relative selector list as an argument.”

For a more robust explanation, MDN does it perfectly

The Styling Problem

In years past we had no way of styling a parent element based on a direct child of that parent with CSS or an element based on another element. In the chance we had to do that, we would need to use some JavaScript and toggle classes on/off based on the structure of the HTML. :has() solved that problem.


Let’s say that you have a heading level 1 element (h1) that is the title of a post or something of that nature on a blog list page, and then you have a heading level 2 (h2) that directly follows it. This h2 could be a sub-heading for the post. If that h2 is present, important, and directly after the h1, you might want to make that h1 stand out. Before you would have had to write a JS function.

Old School Way – JavaScript

const h1Elements = document.querySelectorAll('h1');

h1Elements.forEach((h1) => {
  const h2Sibling = h1.nextElementSibling;
  if (h2Sibling && h2Sibling.tagName.toLowerCase() === 'h2') {
    h1.classList.add('highlight-content');
  }
});

This JS function is looking for all the h1’s that have a h2 proceeding it, and applying a class of highlight-content to make the h1 stand out as an important article.

New and improved with modern day CSS coming in hot! The capabilities of what we can do in the browser have come a long way. We now can take advantage of CSS to do things that we traditionally would have to do with JavaScript, not everything, but some things.

New School Way – CSS

h1:has(+ h2) {
    color: blue;
}

Throw Some :has() On It!

Now you can use :has() to achieve the same thing that the JS function did. This CSS is checking for any h1 and using the sibling combinator checking for an h2 that immediately follows it, and adds the color of blue to the text. Below are a couple use cases of when :has() can come in handy.

:has Selector Example 1

HTML

<h1>Lorem, ipsum dolor.</h1>
<h2>Lorem ipsum dolor sit amet.</h2>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?</p>
	
<!-- WITHOUT HAS BELOW -->

<h1>This is a test</h1>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eius, odio voluptatibus est vero iste ad?</p>

CSS

h1:has(+ h2) {
    color: blue;
}
Example of showing :has being used with pseudo classes.

:has Selector Example 2

A lot of times we as workers on the web are manipulating or working with images. We could be using tools that Cloudinary provides to make use of various transformations on our images, but usually we want to add drop shadows, border-radii, and captions (not to be confused with alternative text in an alt attribute).


The example below is using :has() to see if a figure or image has a figcaption element and if it does, it applies some background and a border radius to make the image stand out.

HTML

<section>
  <figure>
    <img src="https://placedog.net/500/280" alt="My aunt sally's dog is a golden retreiver." />
    <figcaption>My Aunt Sally's Doggo</figcaption>
  </figure>
</section>

CSS

figure:has(figcaption) {
  background: #c3baba;
  padding: 0.6rem;
  max-width: 50%;
  border-radius: 5px;
}
Example of :has selector highlighting background an image with an caption vs one that does not.

Can I :has() that?

You can see that :has() has great support across modern browsers.

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

Desktop

ChromeFirefoxIEEdgeSafari
105121No10515.4

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712715.4

:has() in the Community!

I reached out to my network on Twitter to see how my peers were using :has() in their day-to-day work and this is what they had to say about it.

“One example I have is styling a specific SVG from a 3rd party package in @saucedopen because I couldn’t style it directly.”

This is what Nick Taylor from OpenSauced had to say about using :has().
svg:has(> #Mail) {
  stroke-width: 1;
}

Lol the last time I used it I was building keyboard functionality into a tree view, so I needed to detect states and classes of sibling elements, but it wasn’t in Firefox yet so I had to find another solution. 🫠

Abbey Perini from Nexcor Food Safety Technologies, Inc.

It is great to see how community members are using modern CSS to solve real world problems, and also a shout out to Abbey using it for accessibility reasons!

Things to Keep in Mind

There are a few key points to keep in mind when using :has() Bullet points referenced from MDN.

  • The pseudo-class takes on specificity of the most specific selector in its argument
  • If the :has() pseudo-class itself is not supported in a browser, the entire selector block will fail unless :has() is in a forgiving selector list, such as in :is() and :where()
  • The :has() pseudo-class cannot be nested within another :has() 
  • Pseudo-elements are also not valid selectors within :has() and pseudo-elements are not valid anchors for :has()

Conclusion

Harnessing the power of CSS, including advanced features like the :has() pseudo-class, empowers us to craft exceptional web experiences. CSS’s strengths lie in its cascade and specificity…the best part, allowing us to leverage its full potential. By embracing the capabilities of CSS, we can drive web design and development forward, unlocking new possibilities and creating groundbreaking user interfaces.

Links:


The Power of :has() in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-power-of-has-in-css/feed/ 6 377635
Accessible Forms with Pseudo Classes https://css-tricks.com/accessible-forms-with-pseudo-classes/ https://css-tricks.com/accessible-forms-with-pseudo-classes/#comments Fri, 22 Mar 2024 18:52:31 +0000 https://css-tricks.com/?p=377565 Hey all you wonderful developers out there! In this post, I am going to take you through creating a simple contact form using semantic HTML and an awesome CSS pseudo class known as :focus-within. The :focus-within class allows for …


Accessible Forms with Pseudo Classes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Hey all you wonderful developers out there! In this post, I am going to take you through creating a simple contact form using semantic HTML and an awesome CSS pseudo class known as :focus-within. The :focus-within class allows for great control over focus and letting your user know this is exactly where they are in the experience. Before we jump in, let’s get to the core of what web accessibility is.


Form Accessibility?


You have most likely heard the term “accessibility” everywhere or the numeronym, a11y. What does it mean? That is a great question with so many answers. When we look at the physical world, accessibility means things like having sharps containers in your bathrooms at your business, making sure there are ramps for wheel assisted people, and having peripherals like large print keyboards on hand for anyone that needs it.

The gamut of accessibility doesn’t stop there, we have digital accessibility that we need to be cognizant of as well, not just for external users, but internal colleagues as well. Color contrast is a low hanging fruit that we should be able to nip in the bud. At our workplaces, making sure that if any employee needs assistive tech like a screen reader, we have that installed and available. There are a lot of things that need to be kept into consideration. This article will focus on web accessibility by keeping the WCAG (web content accessibility guidelines) in mind.

MDN (Mozilla Developer Network)

The :focus-within CSS pseudo-class matches an element if the element or any of its descendants are focused. In other words, it represents an element that is itself matched by the :focus pseudo-class or has a descendant that is matched by :focus. (This includes descendants in shadow trees.)

This pseudo class is really great when you want to emphasize that the user is in fact interacting with the element. You can change the background color of the whole form, for example. Or, if focus is moved into an input, you can make the label bold and larger of an input element when focus is moved into that input. What is happening below in the code snippets and examples is what is making the form accessible. :focus-within is just one way we can use CSS to our advantage.

How To Focus


Focus, in regards to accessibility and the web experience, is the visual indicator that something is being interacted with on the page, in the UI, or within a component. CSS can tell when an interactive element is focused.

“The :focus CSS pseudo-class represents an element (such as a form input) that has received focus. It is generally triggered when the user clicks or taps on an element or selects it with the keyboard’s Tab key.”

MDN (Mozilla Developer Network)

Always make sure that the focus indicator or the ring around focusable elements maintains the proper color contrast through the experience.

Focus is written like this and can be styled to match your branding if you choose to style it.

:focus {
  * / INSERT STYLES HERE /*
}

Whatever you do, never set your outline to 0 or none. Doing so will remove a visible focus indicator for everyone across the whole experience. If you need to remove focus, you can, but make sure to add that back in later. When you remove focus from your CSS or set the outline to 0 or none, it removes the focus ring for all your users. This is seen a lot when using a CSS reset. A CSS reset will reset the styles to a blank canvas. This way you are in charge of the empty canvas to style as you wish. If you wish to use a CSS reset, check out Josh Comeau’s reset.

*DO NOT DO what is below!

:focus {
  outline: 0;
}

:focus {
  outline: none;
}


Look Within!


One of the coolest ways to style focus using CSS is what this article is all about. If you haven’t checked out the :focus-within pseudo class, definitely give that a look! There are a lot of hidden gems when it comes to using semantic markup and CSS, and this is one of them. A lot of things that are overlooked are accessible by default, for instance, semantic markup is by default accessible and should be used over div’s at all times.

<header>
  <h1>Semantic Markup</h1>
  <nav>
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>

<section><!-- Code goes here --></section>

<section><!-- Code goes here --></section>

<aside><!-- Code goes here --></aside>

<footer><!-- Code goes here --></footer>

The header, nav, main, section, aside, and footer are all semantic elements. The h1 and ul are also semantic and accessible.

Unless there is a custom component that needs to be created, then a div is fine to use, paired with ARIA (Accessible Rich Internet Applications). We can do a deep dive into ARIA in a later post. For now let’s focus…see what I did there…on this CSS pseudo class.

The :focus-within pseudo class allows you to select an element when any descendent element it contains has focus.


:focus-within in Action!

HTML

<form>
  <div>
    <label for="firstName">First Name</label><input id="firstName" type="text">
  </div>
  <div>
    <label for="lastName">Last Name</label><input id="lastName" type="text">
  </div>
  <div>
    <label for="phone">Phone Number</label><input id="phone" type="text">
  </div>
  <div>
    <label for="message">Message</label><textarea id="message"></textarea>
  </div>
</form>

CSS

form:focus-within {
  background: #ff7300;
  color: black;
  padding: 10px;
}

The example code above will add a background color of orange, add some padding, and change the color of the labels to black.

The final product looks something like below. Of course the possibilities are endless to change up the styling, but this should get you on a good track to make the web more accessible for everyone!

First example of focus-within css class highlighting the form background and changing the label text color.

Another use case for using :focus-within would be turning the labels bold, a different color, or enlarging them for users with low vision. The example code for that would look something like below.

HTML

<form>
  <h1>:focus-within part 2!</h1>
  <label for="firstName">First Name: <input name="firstName" type="text" /></label>
  <label for="lastName">Last Name: <input name="lastName" type="text" /></label>
  <label for="phone">Phone number: <input type="tel" id="phone" /></label>
  <label for="message">Message: <textarea name="message" id="message"/></textarea></label>
</form>

CSS

label {
  display: block;
  margin-right: 10px;
  padding-bottom: 15px;
}

label:focus-within {
  font-weight: bold;
  color: red;
  font-size: 1.6em;
}
Showing how to bold, change color and font size of labels in a form using :focus-within.

:focus-within also has great browser support across the board according to Can I use.

Focus within css pseudo class browser support according to the can i use website.

Conclusion

Creating amazing, accessible user experience should always be a top priority when shipping software, not just externally but internally as well. We as developers, all the way up to senior leadership need to be cognizant of the challenges others face and how we can be ambassadors for the web platform to make it a better place.

Using technology like semantic markup and CSS to create inclusive spaces is a crucial part in making the web a better place, let’s continue moving forward and changing lives.

Check out another great resource here on CSS-Tricks on using :focus-within.


Accessible Forms with Pseudo Classes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/accessible-forms-with-pseudo-classes/feed/ 15 377565
Passkeys: What the Heck and Why? https://css-tricks.com/passkeys-what-the-heck-and-why/ https://css-tricks.com/passkeys-what-the-heck-and-why/#comments Wed, 12 Apr 2023 17:41:53 +0000 https://css-tricks.com/?p=377305 These things called passkeys sure are making the rounds these days. They were a main attraction at W3C TPAC 2022, gained support in Safari 16, are finding their way into macOS and iOS, and are slated to …


Passkeys: What the Heck and Why? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
These things called passkeys sure are making the rounds these days. They were a main attraction at W3C TPAC 2022, gained support in Safari 16, are finding their way into macOS and iOS, and are slated to be the future for password managers like 1Password. They are already supported in Android, and will soon find their way into Chrome OS and Windows in future releases.

Geeky OS security enhancements don’t exactly make big headlines in the front-end community, but it stands to reason that passkeys are going to be a “thing”. And considering how passwords and password apps affect the user experience of things like authentication and form processing, we might want to at least wrap our minds around them, so we know what’s coming.

That’s the point of this article. I’ve been studying and experimenting with passkeys — and the WebAuthn API they are built on top of — for some time now. Let me share what I’ve learned.

Table of contents

Terminology

Here’s the obligatory section of the terminology you’re going to want to know as we dig in. Like most tech, passkeys are wrought with esoteric verbiage and acronyms that are often roadblocks to understanding. I’ll try to de-mystify several for you here.

  • Relying Party: the server you will be authenticating against. We’ll use “server” to imply the Relying Party in this article.
  • Client: in our case, the web browser or operating system.
  • Authenticator: Software and/or hardware devices that allow generation and storage for public key pairs.
  • FIDO: An open standards body that also creates specifications around FIDO credentials.
  • WebAuthn: The underlying protocol for passkeys, Also known as a FIDO2 credential or single-device FIDO credentials.
  • Passkeys: WebAuthn, but with cloud syncing (also called multi-device FIDO credentials, discoverable credentials, or resident credentials).
  • Public Key Cryptography: A generated key pair that includes a private and public key. Depending on the algorithm, it should either be used for signing and verification or encrypting and decrypting. This is also known as asymmetric cryptography.
  • RSA: An acronym of the creators’ names, Rivest Shamir and Adel. RSA is an older, but still useful, family of public key cryptography based on factoring primes.
  • Elliptic Curve Cryptography (ECC): A newer family of cryptography based on elliptic curves.
  • ES256: An elliptic curve public key that uses an ECDSA signing algorithm (PDF) with SHA256 for hashing.
  • RS256: Like ES256, but it uses RSA with RSASSA-PKCS1-v1.5 and SHA256.

What are passkeys?

Before we can talk specifically about passkeys, we need to talk about another protocol called WebAuthn (also known as FIDO2). Passkeys are a specification that is built on top of WebAuthn. WebAuthn allows for public key cryptography to replace passwords. We use some sort of security device, such as a hardware key or Trusted Platform Module (TPM), to create private and public keys.

The public key is for anyone to use. The private key, however, cannot be removed from the device that generated it. This was one of the issues with WebAuthn; if you lose the device, you lose access.

Passkeys solves this by providing a cloud sync of your credentials. In other words, what you generate on your computer can now also be used on your phone (though confusingly, there are single-device credentials too).

Currently, at the time of writing, only iOS, macOS, and Android provide full support for cloud-synced passkeys, and even then, they are limited by the browser being used. Google and Apple provide an interface for syncing via their Google Password Manager and Apple iCloud Keychain services, respectively.

How do passkeys replace passwords?

In public key cryptography, you can perform what is known as signing. Signing takes a piece of data and then runs it through a signing algorithm with the private key, where it can then be verified with the public key.

Anyone can generate a public key pair, and it’s not attributable to any person since any person could have generated it in the first place. What makes it useful is that only data signed with the private key can be verified with the public key. That’s the portion that replaces a password — a server stores the public key, and we sign in by verifying that we have the other half (e.g. private key), by signing a random challenge.

As an added benefit, since we’re storing the user’s public keys within a database, there is no longer concern with password breaches affecting millions of users. This reduces phishing, breaches, and a slew of other security issues that our password-dependent world currently faces. If a database is breached, all that’s stored in the user’s public keys, making it virtually useless to an attacker.

No more forgotten emails and their associated passwords, either! The browser will remember which credentials you used for which website — all you need to do is make a couple of clicks, and you’re logged in. You can provide a secondary means of verification to use the passkey, such as biometrics or a pin, but those are still much faster than the passwords of yesteryear.

More about cryptography

Public key cryptography involves having a private and a public key (known as a key pair). The keys are generated together and have separate uses. For example, the private key is intended to be kept secret, and the public key is intended for whomever you want to exchange messages with.

When it comes to encrypting and decrypting a message, the recipient’s public key is used to encrypt a message so that only the recipient’s private key can decrypt the message. In security parlance, this is known as “providing confidentiality”. However, this doesn’t provide proof that the sender is who they say they are, as anyone can potentially use a public key to send someone an encrypted message.

There are cases where we need to verify that a message did indeed come from its sender. In these cases, we use signing and signature verification to ensure that the sender is who they say they are (also known as authenticity). In public key (also called asymmetric) cryptography, this is generally done by signing the hash of a message, so that only the public key can correctly verify it. The hash and the sender’s private key produce a signature after running it through an algorithm, and then anyone can verify the message came from the sender with the sender’s public key.

How do we access passkeys?

To access passkeys, we first need to generate and store them somewhere. Some of this functionality can be provided with an authenticator. An authenticator is any hardware or software-backed device that provides the ability for cryptographic key generation. Think of those one-time passwords you get from Google Authenticator1Password, or LastPass, among others.

For example, a software authenticator can use the Trusted Platform Module (TPM) or secure enclave of a device to create credentials. The credentials can be then stored remotely and synced across devices e.g. passkeys. A hardware authenticator would be something like a YubiKey, which can generate and store keys on the device itself.

To access the authenticator, the browser needs to have access to hardware, and for that, we need an interface. The interface we use here is the Client to Authenticator Protocol (CTAP). It allows access to different authenticators over different mechanisms. For example, we can access an authenticator over NFC, USB, and Bluetooth by utilizing CTAP.

One of the more interesting ways to use passkeys is by connecting your phone over Bluetooth to another device that might not support passkeys. When the devices are paired over Bluetooth, I can log into the browser on my computer using my phone as an intermediary!

The difference between passkeys and WebAuthn

Passkeys and WebAuthn keys differ in several ways. First, passkeys are considered multi-device credentials and can be synced across devices. By contrast, WebAuthn keys are single-device credentials — a fancy way of saying you’re bound to one device for verification.

Second, to authenticate to a server, WebAuthn keys need to provide the user handle for login, after which an allowCredentials list is returned to the client from the server, which informs what credentials can be used to log in. Passkeys skip this step and use the server’s domain name to show which keys are already bound to that site. You’re able to select the passkey that is associated with that server, as it’s already known by your system.

Otherwise, the keys are cryptographically the same; they only differ in how they’re stored and what information they use to start the login process.

The process… in a nutshell

The process for generating a WebAuthn or a passkey is very similar: get a challenge from the server and then use the navigator.credentials.create web API to generate a public key pair. Then, send the challenge and the public key back to the server to be stored.

Upon receiving the public key and challenge, the server validates the challenge and the session from which it was created. If that checks out, the public key is stored, as well as any other relevant information like the user identifier or attestation data, in the database.

The user has one more step — retrieve another challenge from the server and use the navigator.credentials.get API to sign the challenge. We send back the signed challenge to the server, and the server verifies the challenge, then logs us in if the signature passes.

There is, of course, quite a bit more to each step. But that is generally how we’d log into a website using WebAuthn or passkeys.

The meat and potatoes

Passkeys are used in two distinct phases: the attestation and assertion phases.

The attestation phase can also be thought of as the registration phase. You’d sign up with an email and password for a new website, however, in this case, we’d be using our passkey.

The assertion phase is similar to how you’d log in to a website after signing up.

Attestation

View full size

The navigator.credentials.create API is the focus of our attestation phase. We’re registered as a new user in the system and need to generate a new public key pair. However, we need to specify what kind of key pair we want to generate. That means we need to provide options to navigator.credentials.create.

// The `challenge` is random and has to come from the server
const publicKey: PublicKeyCredentialCreationOptions = {
  challenge: safeEncode(challenge),
  rp: {
    id: window.location.host,
    name: document.title,
  },
  user: {
    id: new TextEncoder().encode(crypto.randomUUID()), // Why not make it random?
    name: 'Your username',
    displayName: 'Display name in browser',
  },
  pubKeyCredParams: [
    {
      type: 'public-key',
      alg: -7, // ES256
    },
    {
      type: 'public-key',
      alg: -256, // RS256
    },
  ],
  authenticatorSelection: {
    userVerification: 'preferred', // Do you want to use biometrics or a pin?
    residentKey: 'required', // Create a resident key e.g. passkey
  },
  attestation: 'indirect', // indirect, direct, or none
  timeout: 60_000,
};
const pubKeyCredential: PublicKeyCredential = await navigator.credentials.create({
  publicKey
});
const {
  id // the key id a.k.a. kid
} = pubKeyCredential;
const pubKey = pubKeyCredential.response.getPublicKey();
const { clientDataJSON, attestationObject } = pubKeyCredential.response;
const { type, challenge, origin } = JSON.parse(new TextDecoder().decode(clientDataJSON));
// Send data off to the server for registration

We’ll get PublicKeyCredential which contains an AuthenticatorAttestationResponse that comes back after creation. The credential has the generated key pair’s ID.

The response provides a couple of bits of useful information. First, we have our public key in this response, and we need to send that to the server to be stored. Second, we also get back the clientDataJSON property which we can decode, and from there, get back the typechallenge, and origin of the passkey.

For attestation, we want to validate the typechallenge, and origin on the server, as well as store the public key with its identifier, e.g. kid. We can also optionally store the attestationObject if we wish. Another useful property to store is the COSE algorithm, which is defined above in our  PublicKeyCredentialCreationOptions with alg: -7 or alg: -256, in order to easily verify any signed challenges in the assertion phase.

Assertion

View full size

The navigator.credentials.get API will be the focus of the assertion phase. Conceptually, this would be where the user logs in to the web application after signing up.

// The `challenge` is random and has to come from the server
const publicKey: PublicKeyCredentialRequestOptions = {
  challenge: new TextEncoder().encode(challenge),
  rpId: window.location.host,
  timeout: 60_000,
};
const publicKeyCredential: PublicKeyCredential = await navigator.credentials.get({
  publicKey,
  mediation: 'optional',
});
const {
  id // the key id, aka kid
} = pubKeyCredential;
const { clientDataJSON, attestationObject, signature, userHandle } = pubKeyCredential.response;
const { type, challenge, origin } = JSON.parse(new TextDecoder().decode(clientDataJSON));
// Send data off to the server for verification

We’ll again get a PublicKeyCredential with an AuthenticatorAssertionResponse this time. The credential again includes the key identifier.

We also get the typechallenge, and origin from the clientDataJSON again. The signature is now included in the response, as well as the authenticatorData. We’ll need those and the clientDataJSON to verify that this was signed with the private key.

The authenticatorData includes some properties that are worth tracking First is the SHA256 hash of the origin you’re using, located within the first 32 bytes, which is useful for verifying that request comes from the same origin server. Second is the signCount, which is from byte 33 to 37. This is generated from the authenticator and should be compared to its previous value to ensure that nothing fishy is going on with the key. The value should always 0 when it’s a multi-device passkey and should be randomly larger than the previous signCount when it’s a single-device passkey.

Once you’ve asserted your login, you should be logged in — congratulations! Passkeys is a pretty great protocol, but it does come with some caveats.

Some downsides

There’s a lot of upside to Passkeys, however, there are some issues with it at the time of this writing. For one thing, passkeys is somewhat still early support-wise, with only single-device credentials allowed on Windows and very little support for Linux systems. Passkeys.dev provides a nice table that’s sort of like the Caniuse of this protocol.

Also, Google’s and Apple’s passkeys platforms do not communicate with each other. If you want to get your credentials from your Android phone over to your iPhone… well, you’re out of luck for now. That’s not to say there is no interoperability! You can log in to your computer by using your phone as an authenticator. But it would be much cleaner just to have it built into the operating system and synced without it being locked at the vendor level.

Where are things going?

What does the passkeys protocol of the future look like? It looks pretty good! Once it gains support from more operating systems, there should be an uptake in usage, and you’ll start seeing it used more and more in the wild. Some password managers are even going to support them first-hand.

Passkeys are by no means only supported on the web. Android and iOS will both support native passkeys as first-class citizens. We’re still in the early days of all this, but expect to see it mentioned more and more.

After all, we eliminate the need for passwords, and by doing so, make the world safer for it!

Resources

Here are some more resources if you want to learn more about Passkeys. There’s also a repository and demo I put together for this article.


Passkeys: What the Heck and Why? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/passkeys-what-the-heck-and-why/feed/ 8 377305
Some Cross-Browser DevTools Features You Might Not Know https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/ https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/#comments Wed, 22 Mar 2023 20:22:42 +0000 https://css-tricks.com/?p=377264 I spend a lot of time in DevTools, and I’m sure you do too. Sometimes I even bounce between them, especially when I’m debugging cross-browser issues. DevTools is a lot like browsers themselves — not all of the features in …


Some Cross-Browser DevTools Features You Might Not Know originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I spend a lot of time in DevTools, and I’m sure you do too. Sometimes I even bounce between them, especially when I’m debugging cross-browser issues. DevTools is a lot like browsers themselves — not all of the features in one browser’s DevTools will be the same or supported in another browser’s DevTools.

But there are quite a few DevTools features that are interoperable, even some lesser-known ones that I’m about to share with you.

For the sake of brevity, I use “Chromium” to refer to all Chromium-based browsers, like Chrome, Edge, and Opera, in the article. Many of the DevTools in them offer the exact same features and capabilities as one another, so this is just my shorthand for referring to all of them at once.

Search nodes in the DOM tree

Sometimes the DOM tree is full of nodes nested in nodes that are nested in other nodes, and so on. That makes it pretty tough to find the exact one you’re looking for, but you can quickly search the DOM tree using Cmd + F (macOS) or Ctrl + F (Windows).

Additionally, you can also search using a valid CSS selector, like .red, or using an XPath, like //div/h1.

DevTools screenshots of all three browsers.
Searching text in Chrome DevTools (left), selectors in Firefox DevTools (center), and XPath in Safari DevTools (right)

In Chromium browsers, the focus automatically jumps to the node that matches the search criteria as you type, which could be annoying if you are working with longer search queries or a large DOM tree. Fortunately, you can disable this behavior by heading to Settings (F1) → PreferencesGlobalSearch as you typeDisable.

After you have located the node in the DOM tree, you can scroll the page to bring the node within the viewport by right-clicking on the nod, and selecting “Scroll into view”.

Showing a highlighted node on a webpage with a contextual menu open to scroll into view

Access nodes from the console

DevTools provides many different ways to access a DOM node directly from the console.

For example, you can use $0 to access the currently selected node in the DOM tree. Chromium browsers take this one step further by allowing you to access nodes selected in the reverse chronological order of historic selection using, $1, $2, $3, etc.

Currently selected node accessed from the Console in Edge DevTools

Another thing that Chromium browsers allow you to do is copy the node path as a JavaScript expression in the form of document.querySelector by right-clicking on the node, and selecting CopyCopy JS path, which can then be used to access the node in the console.

Here’s another way to access a DOM node directly from the console: as a temporary variable. This option is available by right-clicking on the node and selecting an option. That option is labeled differently in each browser’s DevTools:

  • Chromium: Right click → “Store as global variable”
  • Firefox: Right click → “Use in Console”
  • Safari: Right click → “Log Element”
Screenshot of DevTools contextual menus in all three browsers.
Access a node as a temporary variable in the console, as shown in Chrome (left), Firefox (center), and Safari (right)

Visualize elements with badges

DevTools can help visualize elements that match certain properties by displaying a badge next to the node. Badges are clickable, and different browsers offer a variety of different badges.

In Safari, there is a badge button in the Elements panel toolbar which can be used to toggle the visibility of specific badges. For example, if a node has a display: grid or display: inline-grid CSS declaration applied to it, a grid badge is displayed next to it. Clicking on the badge will highlight grid areas, track sizes, line numbers, and more, on the page.

A grid overlay visualized on top of a three-by-three grid.
Grid overlay with badges in Safari DevTools

The badges that are currently supported in Firefox’s DevTools are listed in the Firefox source docs. For example, a scroll badge indicates a scrollable element. Clicking on the badge highlights the element causing the overflow with an overflow badge next to it.

Overflow badge in Firefox DevTools located in the HTML panel

In Chromium browsers, you can right-click on any node and select “Badge settings…” to open a container that lists all of the available badges. For example, elements with scroll-snap-type will have a scroll-snap badge next to it, which on click, will toggle the scroll-snap overlay on that element.

Taking screenshots

We’ve been able to take screenshots from some DevTools for a while now, but it’s now available in all of them and includes new ways to take full-page shots.

The process starts by right-clicking on the DOM node you want to capture. Then select the option to capture the node, which is labeled differently depending on which DevTools you’re using.

Screenshot of DevTools in all three browsers.
Chrome (left), Safari (middle), and Firefox (right)

Repeat the same steps on the html node to take a full-page screenshot. When you do, though, it’s worth noting that Safari retains the transparency of the element’s background color — Chromium and Firefox will capture it as a white background.

Two screenshots of the same element, one with a background and one without.
Comparing screenshots in Safari (left) and Chromium (right)

There’s another option! You can take a “responsive” screenshot of the page, which allows you to capture the page at a specific viewport width. As you might expect, each browser has different ways to get there.

  • Chromium: Cmd + Shift + M (macOS) or Ctrl + Shift + M (Windows). Or click the “Devices” icon next to the “Inspect” icon.
  • Firefox: Tools → Browser Tools → “Responsive Design Mode”
  • Safari: Develop → “Enter Responsive Design Mode”
Enter responsive mode options in DevTools for all three browsers.
Launching responsive design mode in Safari (left), Firefox (right), and Chromium (bottom)

Chrome tip: Inspect the top layer

Chrome lets you visualize and inspect top-layer elements, like a dialog, alert, or modal. When an element is added to the #top-layer, it gets a top-layer badge next to it, which on click, jumps you to the top-layer container located just after the </html> tag.

The order of the elements in the top-layer container follows the stacking order, which means the last one is on the top. Click the reveal badge to jump back to the node.

Firefox tip: Jump to ID

Firefox links the element referencing the ID attribute to its target element in the same DOM and highlights it with an underline. Use CMD + Click (macOS) or CTRL + Click (Windows) )to jump to the target element with the identifier.

Wrapping up

Quite a few things, right? It’s awesome that there are some incredibly useful DevTools features that are supported in Chromium, Firefox, and Safari alike. Are there any other lesser-known features supported by all three that you like?

There are a few resources I keep close by to stay on top of what’s new. I thought I’d share them with here:


Some Cross-Browser DevTools Features You Might Not Know originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/feed/ 4 377264