Pseudo-classes let you style elements based on their state or position in the DOM — without adding extra classes to the HTML. Pseudo-elements create virtual child elements using only CSS.
State pseudo-classes
:hover — on mouse hover
.nav-link:hover {
color: #2563eb;
}
.post-card:hover {
border-color: #2563eb;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.1);
}
.btn:hover {
background: #1d4ed8;
}
:focus — when focused (keyboard/click)
Essential for accessibility — keyboard users need to see where they are.
input:focus,
textarea:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}
.nav-link:focus {
outline: 2px solid #2563eb;
outline-offset: 2px;
border-radius: 2px;
}
Never use outline: none without a replacement — it breaks keyboard navigation.
:active — while being pressed
.btn:active {
transform: scale(0.98); /* slight "press" effect */
background: #1e40af;
}
Structural pseudo-classes
:first-child and :last-child
/* Remove top border from the first card */
.post-card:first-child {
border-top: none;
}
/* Remove bottom margin from the last card */
.post-card:last-child {
margin-bottom: 0;
}
:nth-child()
/* Every other item (even) */
.post-card:nth-child(even) {
background: #f9fafb;
}
/* Every third item */
.post-card:nth-child(3n) {
border-top: 2px solid #2563eb;
}
/* First three items */
.post-card:nth-child(-n+3) {
font-weight: 600;
}
:not() — negation
/* All links except the active one */
.nav-link:not(.nav-link--active):hover {
text-decoration: underline;
}
/* All inputs except submit */
input:not([type="submit"]) {
border: 1px solid #d1d5db;
}
:focus-within
Applied to a parent when focus is anywhere inside it — useful for forms:
.search-wrapper:focus-within {
box-shadow: 0 0 0 2px #2563eb;
border-radius: 6px;
}
Pseudo-elements
Pseudo-elements use a double colon :: and create supplementary content without changing the HTML.
::before and ::after
Insert a virtual element before/after the content. Require the content property.
/* Arrow before each table-of-contents link */
.toc-link::before {
content: "→ ";
color: #2563eb;
}
/* Horizontal line under a section heading */
.section-title::after {
content: "";
display: block;
width: 40px;
height: 3px;
background: #2563eb;
margin-top: 8px;
}
::placeholder
input::placeholder,
textarea::placeholder {
color: #9ca3af;
font-style: italic;
}
::selection
Color of text selected by the mouse:
::selection {
background: #bfdbfe;
color: #1e40af;
}
DevBlog: button with interaction effects
<a href="/articles/" class="btn btn-primary">All articles</a>
.btn {
display: inline-block;
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
text-decoration: none;
transition: all 0.15s ease;
cursor: pointer;
}
.btn-primary {
background: #2563eb;
color: white;
}
.btn-primary:hover {
background: #1d4ed8;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(37, 99, 235, 0.3);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: none;
}
.btn-primary:focus {
outline: 2px solid #2563eb;
outline-offset: 3px;
}
DevBlog: article list with ::after dividers
.post-list {
list-style: none;
padding: 0;
margin: 0;
}
.post-list li {
padding: 16px 0;
position: relative;
}
/* Divider between items */
.post-list li:not(:last-child)::after {
content: "";
display: block;
height: 1px;
background: #e5e7eb;
margin-top: 16px;
}
Quick reference
| Pseudo-class/element | When it applies |
|---|---|
:hover |
Mouse hover |
:focus |
Focus (keyboard/click) |
:active |
Being pressed |
:first-child |
First child element |
:last-child |
Last child element |
:nth-child(n) |
The nth element |
:not(selector) |
Does not match selector |
:focus-within |
Focus anywhere inside |
::before |
Before content |
::after |
After content |
::placeholder |
Input placeholder |
::selection |
Selected text |
💬 Comments (0)
No comments yet
Be the first to share your opinion about this article!