How To Use Before and After HTML Pseudo-elements Tutorial

Before & After CSS pseudo-elements are used within the cascading stylesheet, they can be used in conjunction with other elements such as a div. After being defined in the CSS file, the pseudo-elements are then attached to the element within the HTML page.

You can go a long time in web development without ever really having to tangle with ::before and ::after pseudo-elements. We often see in the example code as part of something bigger and just copy and paste them around without knowing how to use them on our own. In this post, we’ll talk a little bit about what they are and how to use them, as well as look at some common use cases where these pseudo-elements can make life easier for us.

First, why are they called pseudo-elements?

Unlike most other elements, ::before and ::after don’t appear in the HTML code for a page. We add them in with CSS by attaching them to parent elements with special selectors and a content property:

  Some paragraph text
p::after {
    content: "after!";
    background-color: yellow;
p::before {
    content: "before!";
    background-color: green;
    color: white;

This produces the following result: Web page screenshot, text reads "before!Some paragraph textafter!"

The content property can be any string (including just an empty pair of quotation marks), but it if it’s not present, the pseudo-element just won’t show up, ever. So, rule 1:

Always set the content property.

Here’s where you can find the pseudo-elements we just created when you inspect the DOM:

::before is the first child of the parent element and ::after is the last child of the parent. This took me a while to get used to because I’d assumed any time I’d seen them in some code that it was something inserted before and after the element itself. So I spent plenty of time confused about their behavior through just not understanding where they are supposed to exist. For this reason, I’ve ended up thinking of them, and explaining them, as “before-children” and “after-children”. This small change has made working with them, and debugging them, much simpler over the years.

There are some other psuedo-elements (like ::first-letter, ::first-line, and ::selection) that can be targeted with CSS in the same way as ::before and ::after but don’t behave like child elements. They work more like dynamic wrappers that can identify and style whatever content happens to make up the first line of a paragraph, for example.

Let’s borrow an idea from Samantha Ming and make an emoji sandwich:

See the Pen Before and after sandwich by Mark Noonan (@marktnoonan) on CodePen.0

Note that all the ingredients are inside the div, and the bread is added with a single CSS rule setting the content of both ::before and ::after to 🍞.

What are these pseudo-elements used for and why?

::before and ::after are great for decorations like custom bullet points, arrows, or other features that should go with an HTML element, but are not part of the main content of the element. Often you can achieve the same result by adding extra HTML elements to your markup, and pseudo-elements are a way to avoid clutter.

Pseudo-elements can also have other purposes: because you can put any content string in there, they are sometimes used to generate tooltips (as described in the MDN docs) or for other similar ways of rendering content that’s closely related to something else. The use of pseudo-elements as a quick way of disclosing content is not recommended from an accessibility perspective – it’s a shortcut that’s probably best avoided. (Use the details element instead!)

There’s one other use that I’ve found for ::before and ::after – they can help a lot with mouse interactions. Here’s an example of the effect we are going to use:

See the Pen pseudo-elements hover target by Mark Noonan (@marktnoonan) on CodePen.0

The .fancy-text::after pseudo-element is creating a large invisible area around the .text-content. Because that ::after behaves like a child of the parent, a mouse pointer hovering on the ::after counts as a hover on the .fancy-text parent itself. So even getting somewhere near the fancy text with your mouse will cause the ".fancy-text:hover .text-content" selector to become active.

Here’s an even more extreme example, with some outlines added to show what each element is:

See the Pen pseudo-elements by Mark Noonan (@marktnoonan) on CodePen.0

Where this has come in most useful for me is on menus that work like this:


screenshot showing "Solutions" menu with expanded tooltip style dropdown

In lots of the designs I get at work, there are these tool-tip style menus that are triggered by a user hovering on the parent menu item, but the menu content itself is visually separated by a gap from the parent that triggered it. As we’ve mentioned before, we can create accessible versions of navigation elements like this with something like the details element, and then add on the JavaScript functionality to trigger the menu opening and closing based on mouse hover.

In a tooltip scenario with visual separation, it’s possible for users to accidentally trigger a JavaScript mouseleave event and close the menu, just by moving the mouse in the wrong way. This frustrating to users and degrades the experience. It also can produce some really convoluted workarounds (I know because I’ve written them!).

To solve this hover problem we can use the same principle that we saw in the last two examples, and use a pseudo-element create a larger surface area for the parent element, so that any gaps between the trigger element and the menu content are fully covered, and the act of moving the mouse outside the area of the menu is much more predictable for the user.

Here’s a CodePen showing two menus, one broken and one working:

See the Pen Details-Summary with ::before for mouseover target by Mark Noonan (@marktnoonan) on CodePen.0

The broken menu above opens just fine because there’s a mouseover event triggered on the summary element. But in this scenario, that summary has a set height and doesn’t expand to the height of its other content when in the “open” state. So clicking on a link in the broken menu isn’t possible, except by a fluke. This kind of thing can sometimes happen due to design requirements, or fairly common cases where the request is to “add a dropdown” to part of what used to be a static menu.

On the “fixed” menu at the bottom, the height of the summary element is still hard-coded, but the gap between the summary and the .dropdown-wrapper div is covered by an invisible .dropdown-wrapper::before pseudo-element. This means that as the user moves the pointer down, it’s always over a “child” of the original summary element, and so never triggers a mouseleave on that element.

Here’s the CSS that adds that important pseudo-element:

details.good::before {
	width: 100%;
	height: 40px;
	position: absolute;

In this screenshot it is represented by the grey box:

screenshot of a grey box under a dropodown menu

And this is how it looks when the menu is expanded:

screenshot showing open menu

There’s one other pseudo-element in use in that CodePen, to create the little triangle pointing up to the menu trigger, which in this case works fine as a small square with no content, rotated 45 degrees:

.dropdown-wrapper::before {
	position: absolute;
	content: "";
	background-color: inherit;
	height: 12px;
	width: 12px;
	top: -5px;
	left: 8px;
	transform: rotate(45deg);

That produces the square highlighted in black here:

screenshot pseudo-element used to create "arrow" effect


There are many other possible uses of these pseudo-elements available, and you can learn more in Kevin Powell’s awesome video series about them on YouTube. They can come in handy pretty often if you are used to them, but sometimes they can be frustrating to work with until a few details click, so remember these two main things:

  1. Always set the content property!
  2. “before-children”, “after-children”

I hope this comes in handy next time you need to add a little flourish to an element, or fine-tune some hover behavior!

Leave a Reply