Pseudo-classes and Pseudo-elements
Styling based on state and interaction with pseudo-classes, and styling parts of an element with pseudo-elements.
2 phút đọc
Pseudo-classes and pseudo-elements both extend a selector, but they answer different questions: a pseudo-class asks "what state is this element in?" while a pseudo-element asks "what part of this element am I styling?"
Interaction pseudo-classes
.button {
background: #2563eb;
transition: background 0.15s ease;
}
.button:hover {
background: #1d4ed8;
}
.button:active {
background: #1e40af;
}
.button:focus-visible {
outline: 2px solid #93c5fd;
outline-offset: 2px;
}:hover applies while the pointer is over an element, :active while it's being clicked/pressed, and :focus-visible when it has keyboard focus and the browser judges a visible focus ring is appropriate (unlike :focus, which also fires on a mouse click, :focus-visible avoids showing a ring after a mouse click while still showing one for keyboard users — the best of both for accessibility).
Structural pseudo-classes
li:first-child {
margin-top: 0;
}
li:last-child {
border-bottom: none;
}
tr:nth-child(even) {
background: #f8fafc;
}These select elements based on their position among siblings, with no class needed. :nth-child(even) (or :nth-child(2n)) is the standard way to create zebra-striped table rows. :first-child/:last-child are handy for removing a border or margin that only makes sense between items, not at the very start or end of a list.
Form and validation pseudo-classes
input:focus {
border-color: #2563eb;
}
input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
input:invalid {
border-color: #dc2626;
}:invalid matches an input that fails its own HTML validation (like a type="email" field without an @) — letting you style broken form state with pure CSS, no JavaScript required to add an "error" class.
Pseudo-elements: styling a part of an element
p::first-letter {
font-size: 2em;
font-weight: bold;
}
.quote::before {
content: "\201C";
}
.quote::after {
content: "\201D";
}Pseudo-elements use a double colon (::) by convention, distinguishing them from pseudo-classes. ::first-letter and ::first-line target part of an element's existing text. ::before and ::after insert generated content immediately inside the start or end of an element — they require a content property (even content: ""; for an empty one) or they won't render at all.
A practical use of ::before/::after
.card {
position: relative;
}
.card::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
}Generated content is commonly used for purely decorative elements — a subtle inner border, an icon, a tooltip arrow — that would otherwise require an extra, meaningless <div> in the HTML just to hold a style. Because it's pure decoration, screen readers correctly ignore ::before/::after content, which is exactly the right behavior for this use case.