Tag: Pseudoelements

Web Component Pseudo-Classes and Pseudo-Elements are Easier Than You Think

We’ve discussed a lot about the internals of using CSS in this ongoing series on web components, but there are a few special pseudo-elements and pseudo-classes that, like good friends, willingly smell your possibly halitotic breath before you go talk to that potential love interest. You know, they help you out when you need it most. And, like a good friend will hand you a breath mint, these pseudo-elements and pseudo-classes provide you with some solutions both from within the web component and from outside the web component — the website where the web component lives.

I’m specifically referring to the ::part and ::slotted pseudo-elements, and the :defined, :host, and :host-context pseudo-classes. They give us extra ways to interact with web components. Let’s examine them closer.

Article series

The ::part pseudo-element

::part, in short, allows you to pierce the shadow tree, which is just my Lord-of-the-Rings-y way to say it lets you style elements inside the shadow DOM from outside the shadow DOM. In theory, you should encapsulate all of your styles for the shadow DOM within the shadow DOM, i.e. within a <style> element in your <template> element.

So, given something like this from the very first part of this series, where you have an <h2> in your <template>, your styles for that <h2> should all be in the <style> element.

<template id="zprofiletemplate">   <style>     h2 {       font-size: 3em;       margin: 0 0 0.25em 0;       line-height: 0.8;     }     /* other styles */   </style>   <div class="profile-wrapper">     <div class="info">       <h2>         <slot name="zombie-name">Zombie Bob</slot>       </h2>       <!-- other zombie profile info -->     </div> </template>

But sometimes we might need to style an element in the shadow DOM based on information that exists on the page. For instance, let’s say we have a page for each zombie in the undying love system with matches. We could add a class to profiles based on how close of a match they are. We could then, for instance, highlight a match’s name if he/she/it is a good match. The closeness of a match would vary based on whose list of potential matches is being shown and we won’t know that information until we’re on that page, so we can’t bake the functionality into the web component. Since the <h2> is in the shadow DOM, though, we can’t access or style it from outside the shadow DOM meaning a selector of zombie-profile h2 on the matches page won’t work.

But, if we make a slight adjustment to the <template> markup by adding a part attribute to the <h2>:

<template id="zprofiletemplate">   <style>     h2 {       font-size: 3em;       margin: 0 0 0.25em 0;       line-height: 0.8;     }     /* other styles */   </style>   <div class="profile-wrapper">     <div class="info">       <h2 part="zname">         <slot name="zombie-name">Zombie Bob</slot>       </h2>       <!-- other zombie profile info -->     </div> </template>

Like a spray of Bianca in the mouth, we now have the superpowers to break through the shadow DOM barrier and style those elements from outside of the <template>:

/* External stylesheet */ .high-match::part(zname) {   color: blue; } .medium-match::part(zname) {   color: navy; } .low-match::part(zname) {   color: slategray; }

There are lots of things to consider when it comes to using CSS ::part. For example, styling an element inside of a part is a no-go:

/* frowny-face emoji */ .high-match::part(zname) span { ... }

But you can add a part attribute on that element and style it via its own part name.

What happens if we have a web component inside another web component, though? Will ::part still work? If the web component appears in the page’s markup, i.e. you’re slotting it in, ::part works just fine from the main page’s CSS.

<zombie-profile class="high-match">   <img slot="profile-image" src="https://assets.codepen.io/1804713/leroy.png" />   <span slot="zombie-name">Leroy</span>   <zombie-details slot="zdetails">     <!-- Leroy's details -->   </zombie-details> </zombie-profile>

But if the web component is in the template/shadow DOM, then ::part cannot pierce both shadow trees, just the first one. We need to bring the ::part into the light… so to speak. We can do that with an exportparts attribute.

To demonstrate this we’ll add a “watermark” behind the profiles using a web component. (Why? Believe it or not this was the least contrived example I could come up with.) Here are our templates: (1) the template for <zombie-watermark>, and (2) the same template for <zombie-profile> but with added a <zombie-watermark> element on the end.

<template id="zwatermarktemplate">   <style>     div {     text-transform: uppercase;       font-size: 2.1em;       color: rgb(0 0 0 / 0.1);       line-height: 0.75;       letter-spacing: -5px;     }     span {       color: rgb( 255 0 0 / 0.15);     }   </style>   <div part="watermark">     U n d y i n g  L o v e  U n d y i n g  L o v e  U n d y i n g  L o v e  <span part="copyright">©2 0 2 7 U n d y i n g  L o v e  U n L t d .</span>   <!-- Repeat this a bunch of times so we can cover the background of the profile -->   </div>  </template> <template id="zprofiletemplate">   <style>     ::part(watermark) {       color: rgb( 0 0 255 / 0.1);     }     /* More styles */   </style>   <!-- zombie-profile markup -->   <zombie-watermark exportparts="copyright"></zombie-watermark> </template> <style>   /* External styles */   ::part(copyright) {     color: rgb( 0 100 0 / 0.125);   } </style>

Since ::part(watermark) is only one shadow DOM above the <zombie-watermark>, it works fine from within the <zombie-profile>’s template styles. Also, since we’ve used exportparts="copyright" on the <zombie-watermark>, the copyright part has been pushed up into the <zombie-profile>‘s shadow DOM and ::part(copyright) now works even in external styles, but ::part(watermark) will not work outside the <zombie-profile>’s template.

We can also forward and rename parts with that attribute:

<zombie-watermark exportparts="copyright: cpyear"></zombie-watermark>
/* Within zombie-profile's shadow DOM */  /* happy-face emoji */ ::part(cpyear) { ... }  /* frowny-face emoji */ ::part(copyright) { ... }

Structural pseudo-classes (:nth-child, etc.) don’t work on parts either, but you can use pseudo-classes like :hover. Let’s animate the high match names a little and make them shake as they’re lookin’ for some lovin’. Okay, I heard that and agree it’s awkward. Let’s… uh… make them more, shall we say, noticeable, with a little movement.

.high::part(name):hover {   animation: highmatch 1s ease-in-out; }

The ::slotted pseudo-element

The ::slotted CSS pseudo-element actually came up when we covered interactive web components. The basic idea is that ::slotted represents any content in a slot in a web component, i.e. the element that has the slot attribute on it. But, where ::part pierces through the shadow DOM to make a web component’s elements accessible to outside styles, ::slotted remains encapsulated in the <style> element in the component’s <template> and accesses the element that’s technically outside the shadow DOM.

In our <zombie-profile> component, for example, each profile image is inserted into the element through the slot="profile-image".

<zombie-profile>   <img slot="profile-image" src="photo.jpg" />    <!-- rest of the content --> </zombie-profile>

That means we can access that image — as well as any image in any other slot — like this:

::slotted(img) {   width: 100%;   max-width: 300px;   height: auto;   margin: 0 1em 0 0; }

Similarly, we could select all slots with ::slotted(*) regardless of what element it is. Just beware that ::slotted has to select an element — text nodes are immune to ::slotted zombie styles. And children of the element in the slot are inaccessible.

The :defined pseudo-class

:defined matches all defined elements (I know, surprising, right?), both built-in and custom. If your custom element is shuffling along like a zombie avoiding his girlfriend’s dad’s questions about his “living” situation, you may not want the corpses of the content to show while you’re waiting for them to come back to life errr… load.

You can use the :defined pseudo-class to hide a web component before it’s available — or “defined” — like this:

:not(:defined) {   display: none; }

You can see how :defined acts as a sort of mint in the mouth of our component styles, preventing any broken content from showing (or bad breath from leaking) while the page is still loading. Once the element’s defined, it’ll automatically appear because it’s now, you know, defined and not not defined.

I added a setTimeout of five seconds to the web component in the following demo. That way, you can see that <zombie-profile> elements are not shown while they are undefined. The <h1> and the <div> that holds the <zombie-profile> components are still there. It’s just the <zombie-profile> web component that gets display: none since they are not yet defined.

The :host pseudo-class

Let’s say you want to make styling changes to the custom element itself. While you could do this from outside the custom element (like tightening that N95), the result would not be encapsulated, and additional CSS would have to be transferred to wherever this custom element is placed.

It’d be very convenient then to have a pseudo-class that can reach outside the shadow DOM and select the shadow root. That CSS pseudo-class is :host.

In previous examples throughout this series, I set the <zombie-profile> width from the main page’s CSS, like this:

zombie-profile {   width: calc(50% - 1em); }

With :host, however, I can set that width from inside the web component, like this:

:host {   width: calc(50% - 1em); }

In fact, there was a div with a class of .profile-wrapper in my examples that I can now remove because I can use the shadow root as my wrapper with :host. That’s a nice way to slim down the markup.

You can do descendant selectors from the :host, but only descendants inside the shadow DOM can be accessed — nothing that’s been slotted into your web component (without using ::slotted).

Showing the parts of the HTML that are relevant to the :host pseudo-element.

That said, :host isn’t a one trick zombie. It can also take a parameter, e.g. a class selector, and will only apply styling if the class is present.

:host(.high) {   border: 2px solid blue; }

This allows you to make changes should certain classes be added to the custom element.

You can also pass pseudo-classes in there, like :host(:last-child) and :host(:hover).

The :host-context pseudo-class

Now let’s talk about :host-context. It’s like our friend :host(), but on steroids. While :host gets you the shadow root, it won’t tell you anything about the context in which the custom element lives or its parent and ancestor elements.

:host-context, on the other hand, throws the inhibitions to the wind, allowing you to follow the DOM tree up the rainbow to the leprechaun in a leotard. Just note that at the time I’m writing this, :host-context is unsupported in Firefox or Safari. So use it for progressive enhancement.

Here’s how it works. We’ll split our list of zombie profiles into two divs. The first div will have all of the high zombie matches with a .bestmatch class. The second div will hold all the medium and low love matches with a .worstmatch class.

<div class="profiles bestmatch">   <zombie-profile class="high">     <!-- etc. -->   </zombie-profile>   <!-- more profiles --> </div>  <div class="profiles worstmatch">   <zombie-profile class="medium">     <!-- etc. -->   </zombie-profile>   <zombie-profile class="low">     <!-- etc. -->   </zombie-profile>   <!-- more profiles --> </div>

Let’s say we want to apply different background colors to the .bestmatch and .worstmatch classes. We are unable to do this with just :host:

:host(.bestmatch) {   background-color: #eef; } :host(.worstmatch) {   background-color: #ddd; }

That’s because our best and worst match classes are not on our custom elements. What we want is to be able to select the profiles’s parent elements from within the shadow DOM. :host-context pokes past the custom element to match the, er, match classes we want to style.

:host-context(.bestmatch) {   background-color: #eef; } :host-context(.worstmatch) {   background-color: #ddd; }

Well, thanks for hanging out despite all the bad breath. (I know you couldn’t tell, but above when I was talking about your breath, I was secretly talking about my breath.)

How would you use ::part, ::slotted, :defined, :host, and :host-context in your web component? Let me know in the comments. (Or if you have cures to chronic halitosis, my wife would be very interested in to hear more.)

Web Component Pseudo-Classes and Pseudo-Elements are Easier Than You Think originally published on CSS-Tricks. You should get the newsletter.


, , , , ,

7 Practical Uses for the ::before and ::after Pseudo-Elements in CSS

CSS ::before and ::after pseudo-elements allow you to insert “content” before and after any non-replaced element (e.g. they work on a <div> but not an <input>). This effectively allows you to show something on a web page that might not be present in the HTML content. You shouldn’t use it for actual content because it’s not very accessible in that you can’t even select and copy text inserted on the page this way —  it’s just decorative content.

In this article, I’ll walk you through seven different examples that showcase how ::before and ::after can be used to create interesting effects.

Note that for most examples, I am only explaining the parts of the code that deal specifically with CSS pseudo-elements. That said, all of the CSS is available in the embedded demos if you want to see the code for additional styling.

Styling Broken images

When a user visits your website, their internet connection (or a factor beyond your control) might prevent your images from downloading and, as a result, the browser shows a broken image icon and and the image’s alt text (if it’s actually there).

A broken image icon with alt text that says Red and white Chevrolet.

How about showing a custom placeholder instead? You can pull this off using ::before and ::after with a bit of CSS positioning.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text centered in a gray placeholder area that says Red and white Chevrolet.

First, we need to use relative positioning on the image element. We are going to use absolute positioning on one of the pseudo-elements in a bit, so this relative position makes sure make sure the pseudo-element is positioned within the content of the image element, rather than falling completely out of the document flow.

img {   display: block; /* Avoid the space under the image caused by line height */   position: relative;   width: 100% }

Next, let’s create the region of the broken image effect using the image’s ::before pseudo-element. We’re going to style this with a light gray background and slightly darker border to start.

img::before {   background-color: hsl(0, 0%, 93.3%);   border: 1px dashed hsl(0, 0%, 66.7%);   /* ... */ }

<img> is a replaced element. Why are you using ::before pseudo-element on it? It wont work!. Correct. In this scenario the pseudo-element will show in Chrome and Firefox when the image fails to load, which is exactly what you want. Meanwhile, Safari only shows the styling applied to the alt text.

The styling is applied to the top-left corner of the broken image.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text that says Red and white Chevrolet.

So far, so good. Now we can make it a block-level element (display: block) and give it a height that fills the entire available space.

img::before {   /* ... */   display: block;   height: 100%; }
Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text that says Red and white Chevrolet inside of a gray placeholder area. That area is highlighted with a red border.
Things are already starting to look better!

We can refine the style a little more. For example, let’s round the corners. We should also give the alt text a little breathing room by giving the pseudo-element full width and absolute positioning for better control placing things where we want.

img::before {   /* ... */   border-radius: 4px;   content: "";   position: absolute;   width: 100%; }

If you stopped here and checked your work, you might be scratching your head because the alt text is suddenly gone.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows a gray placeholder area.

That’s because we set content to an empty string (which we need to display our generated content and styles) and cover the entire space, including the actual alt text. It’s there, we just can’t see it.

We can see it if we display the alt text in an alternate (get it?) way, this time with help form the ::after pseudo-element. The content property is actually capable of displaying the image’s alt attribute text using the attr() function:

img::after {   content: attr(alt);    /* Some light styling */   font-weight: bold;   position: absolute;   height: 100%;   left: 0px;   text-align: center;   top: 1px;   width: 100%; }

This is awesome! In Chrome, at least.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text inside of a gray placeholder area.

But, in Firefox, not so much.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text on top of alt text inside of a gray placeholder area.
The generated content is colliding with the actual alt text in Firefox.

A quick fix is to target the alt attribute directly using an attribute selector (in this case, img[alt]), and target similar styles there so things match up with Chrome.

img[alt] {   text-align: center;   font-weight: bold;   color: hsl(0, 0%, 60%); }

Now we have a great placeholder that’s consistent in Chrome and Firefox.

Custom blockquote

Blockquotes are quotes or an excerpts from a cited work. They’re also provide a really great opportunity to break up a wall of text with something that’s visually interesting.

There are all kinds of ways to style blockquotes. Chris has a set of five styles that go all the way back to 2007.

I want to look at another technique, one that incorporates ::before and ::after. Like we saw with the last example, we can use the content property to display generated content, and apply other properties to dress it up. Let’s put large quotation marks at the start and end of a blockquote.

Firefox 91

The HTML is straightforward:

<blockquote>   <!-- Your text here --> </blockquote>

A few cosmetics in the CSS:

blockquote {   font-style: italic;   line-height: 1.618;   font-size: 1.2em;   width: 30em;   position: relative;   padding: 40px 80px; }

Note the position: relative in there because, as you’ll learn, it’s essential for positioning the blockquotes.

As you’ve probably guessed, we’re going to use ::before for the first quotation mark, and ::after for the closing one. Now, we could simply call the content property on both and generate the marks in there. But, CSS has us covered with open-quote and close-quote values.

blockquote::before {   content: open-quote;   /* Place it at the top-left */   top: 0;   left: 0; }  blockquote::after {   content: close-quote;   /* Place it at thee bottom-right */   bottom: 0;   right: 0; }

This gets us the quotation marks we want, but allow me to button up the styles a bit:

blockquote::before, blockquote::after {   background-color: #cccccc;   display: block;   width: 60px;   height: 60px;   line-height: 1.618;   font-size: 3em;   border-radius: 100%;   text-align: center;   position: absolute; }

Icon Bullet List

We have ordered (<ol>) and unordered (<ul>) lists in HTML. Both have default styling dictated by the browser’s User Agent stylesheet. But with ::before pseudo-element, we can override those “default” styles with something of our own. And guess what? We can use emojis (😊) on the content property!

.name-list li::before {   content: "😊";   margin-right: 15px;   font-size: 20px; }

While this is great and all, it’s worth noting that we could actually reach for the ::marker pseudo-element, which is designed specifically for styling list markers. Eric Meyer shows us how that works and it’s probably a better way to go in the long run.

Animated toggle switch

One of the neatest tricks for styling forms is creating a toggle switch out of a standard HTML checkbox. In fact, Preethi Sam recently shared one approach for it when showing off a handful of other checkbox styling tricks using CSS masks.

True to its name, a toggle switch is used to toggle or switch between the checked and unchecked states of a checkbox element.

<form class="container">   <label class="switch">     <input type="checkbox" />   </label> </form>

The customization is all thanks to modifications added to the <input> element via the ::before and ::after pseudo-elements. But first, here is some baseline CSS for the <form> element:

.container {   background: #212221;   background: linear-gradient(to right, #1560bd, #e90);   border-radius: 50px;   height: 40px;   position: relative;   width: 75px;     }
We’re not quite there, but see how the checkbox element is displayed.

We’re going to “hide” the checkbox’s default appearance while making it take up the full amount of space. Weird, right? It’s invisible but still technically there. We do that by:

  • changing its position to absolute,
  • setting the appearance to none, and
  • setting its width and height to 100%.
input {   -webkit-appearance: none; /* Safari */   cursor: pointer; /* Show it's an interactive element */   height: 100%;   position: absolute;   width: 100%; }

Now, let’s style the <input> element with its ::before pseudo-element. This styling will change the appearance of the input, bringing us closer to the final result.

input::before {   background: #fff;   border-radius: 50px;   content: "";   height: 70%;   position: absolute;   top: 50%;   transform: translate(7px, -50%); /* Move styling to the center of the element */   width: 85%; }

What, wait? You’d think that ::before wouldn’t work with a replaced element, like <input>. And that’s true, but only when the input type is image which is equivalent to an <img> element. Other form controls, like a checkbox, are defined as non-replaced elements in the HTML spec.

Next, we need to create the “toggle” button and it just so happens we still have the ::after pseudo-element available to make it. But, there are two things worth mentioning:

  1. The background is a linear-gradient.
  2. The “toggle” button is moved to the center of the <input> with the transform property.
input::after {   background: linear-gradient(to right, orange, #8e2de2);   border-radius: 50px;   content: "";   height: 25px;   opacity: 0.6;   position: absolute;   top: 50%;   transform: translate(7px, -50%);   transition: all .4s;   width: 25px; }

Try clicking on the toggle button. Nothing happens. That’s because we’re not actually changing the checked state of the <input>. And even if we were, the result is… unpleasant.

The fix is to apply the :checked attribute to the ::after pseudo-element of the <input>. By specifically targeting the checked state of the checkbox and chaining it to the ::after pseudo-element, we can move the toggle back into place.

input:checked::after {   opacity: 1;   transform: translate(170%, -50%); }

Gradient border

We can decorate images with borders to make them stand out or fit more seamlessly within a design. Did you know we can use a gradient on a border? Well, we can with ::before (there are other ways, too, of course).

The core idea is to create a gradient over the image and use the CSS z-index property with a negative value. The negative value pulls the gradient below the image in the stacking order. This means the image always appears on top as long as the gradient has a negative z-index.

.gradient-border::before {   /* Renders the styles */   content: "";   /* Fills the entire space */   position: absolute;   top: 0;   left: 0;   bottom: 0;   right: 0;   /* Creates the gradient */   background-image: linear-gradient(#1a1a1a, #1560bd);   /* Stacks the gradient behind the image */   z-index: -1; }  figure {   /* Removes the default margin */   margin: 0;   /* Squeezes the image, revealing the gradient behind it */   padding: 10px; }

Gradient overlays

This is similar to what we did in the previous example, but here, we’re applying the gradient on top of the image. Why would we do that? It can be a nice way to add a little texture and depth to the image. Or perhaps it can be used to either lighten or darken an image if there’s text on top it that needs extra contrast for legibility.

While this is similar to what we just did, you’ll notice a few glaring differences:

figure::before {   background-image: linear-gradient(to top right, #1a1a1a, transparent);   content: "";   height: 100%;   position: absolute;   width: 100%; }

See that? There’s no z-index because it’s OK for the gradient to stack on top of the image. We’re also introducing transparency in the background gradient, which lets the image bleed through the gradient. You know, like an overlay.

Custom radio buttons

Most, if not all, of us try to customize the default styles of HTML radio buttons, and that’s usually accomplished with ::before and ::after, like we did with the checkbox earlier.

Firefox 91

We’re going to set a few base styles first, just to set the stage:

/* Centers everything */ .flex-center {   align-items: center;   display: flex;   justify-content: center; }  /* Styles the form element */ .form {   background: #ccc;   height: 100vh;   width: 100%; }  /* Styles the inputs */ .form-row {   background: #fff;   border-radius: 50px;   height: 40px;   margin: 10px;   overflow: hidden;   position: relative;   width: 150px; }

Now let’s remove the default styling of the radio buttons, again, with appearance: none;

.form-input {   -webkit-appearance: none; /* Safari */   appearance: none; }

::before should be positioned at the top-left corner of the radio button, and when it’s checked, we change its background color.

.form-input::before {   /* Renders the styles */   content: '';   /* Shows that it's interactive */   cursor: pointer;   /* Positions it to the top-left corner of the input */   position: absolute;   top: 0;   left: 0;   /* Takes up the entire space */   height: 100%;   width: 100%; }  /* When the input is in a checked state... */ .form-input:checked::before {   /* Change the background color */   background: #21209c; }

We still need to iron a few things out using ::after. Specifically, when the radio button is checked, we want to change the color of the circular ring to white because, in its current state, the rings are blue.

.form-input::after {   /* Renders the styles */   content: '';   /* Shows that it's interactive */   cursor: pointer;   /* A little border styling */   border-radius: 50px;   border: 4px solid #21209c;   /* Positions the ring */   position: absolute;   left: 10%;   top: 50%;   transform: translate(0, -50%);   /* Sets the dimensions */   height: 15px;   width: 15px; }  /* When the input is in a checked state... */ .form-input:checked::after {   /* Change ::after's border to white */   border: 4px solid #ffffff; }

The form label is still unusable here. We need to target the form label directly to add color, and when the form input is checked, we change that color to something that’s visible.

.form-label {   color: #21209c;   font-size: 1.1rem;   margin-left: 10px; }

Click the buttons, and still nothing happens. Here what’s going on. The position: absolute on ::before and ::after is covering things up when the radio buttons are checked, as anything that occurs in the HTML document hierarchy is covered up unless they are moved to a new location in the HTML document, or their position is altered with CSS. So, every time the radio button is checked, its label gets covered.

You probably already know how to fix this since we solved the same sort of thing earlier in another example? We apply z-index: 1 (or position: absolute) to the form label.

.form-label {   color: #21209c;   font-size: 1.1rem;   margin-left: 10px;   z-index: 1; /* Makes sure the label is stacked on top */   /* position: absolute; This is an alternative option */ }

Wrapping up

We covered seven different ways we can use the ::before and ::after pseudo-elements to create interesting effects, customize default styles, make useful placeholders, and add borders to images.

By no means did we cover all of the possibilities that we can unlock when we take advantage of these additional elements that can be selected with CSS. Lynn Fisher, however, has made a hobby out of it, making amazing designs with a single element. And let’s not forget Diana Smith’s CSS art that uses pseudo-elements in several places to get realistic, painting-like effects.

The post 7 Practical Uses for the ::before and ::after Pseudo-Elements in CSS appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , , ,

Pseudo-elements in the Web Animations API

To use the Web Animations API (e.g. el.animate()) you need a reference to a DOM element to target. So, how do you use it on pseudo-elements, which don’t really offer a direct reference? Dan Wilson covers a (newish?) part of the API itself:

const logo = document.getElementById('logo');  logo.animate({ opacity: [0, 1] }, {   duration: 100,   pseudoElement: '::after' });

I noticed in Dan’s article that ::marker is supported. I was just playing with that recently while doing our List Style Recipes page. I figured I’d give it a spin by testing the WAAPI and @keyframes on both a ::marker and and ::after element:

At first, I confused myself because it seemed like the WAAPI wasn’t working on ::after, but Dan reminded me that when using a transform, the element can’t be display: inline. Instead, I made it inline-block and it worked fine. However, I did uncover that @keyframes don’t seem to work on ::marker elements in Firefox — hopefully they’ll fix that (and we get Chrome and Safari support for ::marker ASAP).

Direct Link to ArticlePermalink

The post Pseudo-elements in the Web Animations API appeared first on CSS-Tricks.