Pseudo-classes & Pseudo-elements
Some things you want to style do not exist in the markup. A link has a hovered state. A list has a first item. Those states and parts are invisible to normal selectors, so CSS gives you pseudo-classes and pseudo-elements to reach them.
Pseudo-classes start with one colon and match a state or position. :hover fires when the pointer is over the element. :focus fires when the element is active via keyboard or click. :first-child matches the first child of a parent. They add interactivity and polish without any JavaScript.
Pseudo-elements start with two colons and create virtual parts of an element. ::before inserts content before the element, and ::after inserts content after it. Both need a content property to render at all. This is how you draw badges, icons, and decorative lines without touching the HTML. The classic example is a badge label on a card: the CSS draws it, the markup stays clean.
One colon versus two
The rule is easy to memorize: pseudo-classes use one colon, pseudo-elements use two. Pseudo-classes match a state or position the element is in, like :hover, :focus, :checked, and :first-child. Pseudo-elements create a virtual part of the element, like ::first-letter or ::selection. Older browsers accepted ::before with one colon, but two is the standard and works everywhere modern.
a:hover { color: #b91c1c; } /* pseudo-class: a state */
p::first-letter { font-size: 24px; } /* pseudo-element: a part */
::before and ::after need content
Pseudo-elements that insert content must declare a content property, or the browser renders nothing at all. That trips up everyone once. content can hold text, or an empty string when you only want a decorative shape. Because the inserted content is not in the markup, screen readers may ignore it, so keep real text in the HTML and use pseudo-elements for decoration.
.card::before {
content: "New"; /* without content, nothing renders */
color: white;
}
Focus is for everyone
:focus matters more than it gets credit for. Keyboard users move through a page with the Tab key, and the focus ring is their cursor. The habit of removing outlines, outline: none, on focus makes pages impossible to navigate without a mouse. If you style away the default ring, replace it with a visible one of your own.
button:focus-visible {
outline: 3px solid #0891b2;
outline-offset: 2px;
}
Syntax
button:hover {
background-color: #0e7490;
}
p::first-letter {
font-size: 24px;
}
::before Needs content:"" or It Renders Nothing
Pseudo-elements ::before/::after require content property or they are invisible — the #1 'why isn't my badge showing' bug.
.card::before{content:"New"; }
Good to know
- Pseudo-classes use one colon and match states like hover and focus.
- Pseudo-elements use two colons and create virtual parts.
- ::before and ::after render nothing without a content property.
- Keyboard users depend on :focus, so never remove the focus outline.