Pseudo-Elements
Prerequisites: Styling links and interactive states
Pseudo-elements style a part of an element, or add generated content. They use two colons.
::before and ::after
These create a child box at the start or end of an element's content. They
need a content property, even if it's empty:
.required-label::after {
content: " *";
color: #c62828;
}
.quote::before {
content: "“";
font-size: 3rem;
line-height: 0;
vertical-align: -0.4em;
}
.card { position: relative; }
.card::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 8px 24px rgb(0 0 0 / 20%);
opacity: 0;
transition: opacity 200ms;
}
.card:hover::after { opacity: 1; }
- Generated text isn't selectable, and screen readers may or may not
read it — never put important content in
content. (You can give generated content alternative text, or an empty one for decoration:content: "★" / "";— supported in all current browsers.) - Empty-content pseudo-elements are great for decoration: overlays, icons, underlines, and shadows you can animate cheaply.
::before/::afterdon't work on void elements likeimgandinput.
<style>
.price::before { content: "£"; }
.price::after { content: " " attr(data-unit); color: gray; }
</style>
<p class="price" data-unit="per loaf">4.20</p>
<script>
const p = document.querySelector(".price");
console.log(getComputedStyle(p, "::before").content);
console.log(getComputedStyle(p, "::after").content);
console.log(JSON.stringify(p.textContent));
</script>
"£"
" per loaf"
"4.20"
The DOM text is still just 4.20 — generated content exists only in
rendering. attr() inserted the data-unit attribute value.
Other pseudo-elements
| Pseudo-element | Styles |
|---|---|
::first-line / ::first-letter |
The first line / letter of a block (drop caps) |
::marker |
List bullets and numbers, summary triangles |
::placeholder |
Placeholder text in inputs |
::selection |
Text the user has selected |
::backdrop |
The layer behind a modal dialog, popover, or full-screen element |
::file-selector-button |
The button of input type="file" |
::part() / ::slotted() |
Parts of web components |
.article > p:first-of-type::first-letter {
float: left;
font-size: 3.5em;
line-height: 1;
margin-inline-end: 0.1em;
}
::selection { background: #ffe58f; color: #111; }
dialog::backdrop { background: rgb(0 0 0 / 50%); backdrop-filter: blur(2px); }
input[type="file"]::file-selector-button {
border: 0;
padding: 0.4rem 0.8rem;
border-radius: 6px;
background: #eee;
}
Practice
Add an arrow "→" after every link with the class more, spaced a little
from the text, and make it move right on hover.
Show solution
.more::after {
content: "→";
display: inline-block;
margin-inline-start: 0.25em;
transition: transform 150ms;
}
.more:hover::after {
transform: translateX(3px);
}
display: inline-block is needed because transforms don't apply to
inline boxes.