Skip to content

CSS Pseudo-classes: The Art of Dynamic Styles and User Interaction ​

Understanding Pseudo-classes ​

Pseudo-classes are special selectors in CSS that select elements based on their state or position, rather than the element's attributes themselves. You can think of pseudo-classes as "style switches that activate under specific conditions."

Pseudo-classes use single colon : syntax, such as :hover, :first-child, :checked, etc. They allow us to implement rich interactive effects and precise style control without modifying HTML structure or using JavaScript.

css
/* Basic syntax */
selector:pseudo-class {
  property: value;
}

/* Example */
button:hover {
  background-color: #3498db;
}

User Behavior Pseudo-classes ​

These pseudo-classes respond to user actions and are core tools for creating interactive experiences.

:hover - Mouse Hover ​

Triggered when the mouse pointer hovers over an element:

css
/* Basic hover effect */
a:hover {
  color: #e74c3c;
  text-decoration: underline;
}

/* Button hover */
.button:hover {
  background-color: #2980b9;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  transition: all 0.3s ease;
}

/* Card hover */
.card:hover {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}

.card:hover img {
  transform: scale(1.05);
}

/* Navigation menu */
.nav-item:hover .dropdown-menu {
  display: block;
  opacity: 1;
  visibility: visible;
}

:active - Active State ​

The momentary state when an element is clicked:

css
button:active {
  transform: scale(0.98);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.link:active {
  color: #c0392b;
}

/* Ripple effect foundation */
.ripple:active::after {
  animation: ripple-animation 0.6s ease-out;
}

:focus - Focus State ​

When an element receives focus (usually form elements or focusable elements):

css
/* Input box focus */
input:focus {
  outline: none;
  border-color: #3498db;
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}

textarea:focus {
  border-color: #3498db;
  background-color: #f8f9fa;
}

/* Accessibility enhancement */
.button:focus {
  outline: 2px solid #3498db;
  outline-offset: 2px;
}

/* Focus visibility control */
.nav-link:focus-visible {
  outline: 2px dashed #3498db;
  outline-offset: 4px;
}

:focus-within - Internal Focus ​

Triggered when the element itself or any of its descendants receives focus:

css
/* Form group focus highlight */
.form-group:focus-within {
  background-color: #f8f9fa;
  border-left: 3px solid #3498db;
  padding-left: 17px;
}

/* Search box container */
.search-container:focus-within {
  box-shadow: 0 4px 12px rgba(52, 152, 219, 0.2);
}

.search-container:focus-within .search-icon {
  color: #3498db;
}

/* Sidebar highlight */
.sidebar:focus-within {
  border-right: 2px solid #3498db;
}

Structural Pseudo-classes ​

Structural pseudo-classes select elements based on their position in the DOM tree.

:first-child and :last-child ​

css
/* First child element */
li:first-child {
  border-top: none;
}

.card:first-child {
  margin-top: 0;
}

/* Last child element */
li:last-child {
  border-bottom: none;
}

.section:last-child {
  margin-bottom: 0;
}

/* Combined usage */
.breadcrumb li:first-child::before {
  content: none; /* Remove separator from first item */
}

:nth-child() - Flexible Position Selection ​

nth-child() is one of the most powerful structural pseudo-classes, supporting multiple parameters:

css
/* Select even items */
tr:nth-child(even) {
  background-color: #f8f9fa;
}

/* Select odd items */
tr:nth-child(odd) {
  background-color: white;
}

/* Select the 3rd item */
li:nth-child(3) {
  color: #e74c3c;
}

/* Select first 3 items */
li:nth-child(-n + 3) {
  font-weight: bold;
}

/* Start from the 4th item */
li:nth-child(n + 4) {
  opacity: 0.7;
}

/* Every 3 items in a group, select the first one */
.item:nth-child(3n + 1) {
  clear: left;
}

/* Practical case: grid layout */
.gallery-item:nth-child(4n) {
  margin-right: 0; /* Last item in each row doesn't need right margin */
}

/* Advanced: select middle items */
li:nth-child(n + 3):nth-child(-n + 7) {
  color: #3498db; /* Select items 3 to 7 */
}

:nth-of-type() ​

Similar to :nth-child(), but only counts elements of the same type:

css
/* Select all even paragraphs */
p:nth-of-type(even) {
  background-color: #f8f9fa;
}

/* Select first 2 h2 headings */
h2:nth-of-type(-n + 2) {
  color: #e74c3c;
  font-size: 2em;
}

/* Practical application: article styling */
article p:nth-of-type(1) {
  font-size: 1.2em; /* First paragraph lead-in effect */
  font-style: italic;
}

:only-child and :only-of-type ​

css
/* Only child element */
.container p:only-child {
  margin: 0; /* No margin needed when only one paragraph */
}

/* Only element of this type */
li:only-of-type {
  list-style: none; /* Remove bullet when list has only one item */
}

/* Practical scenario */
.button-group button:only-child {
  border-radius: 4px; /* All corners rounded when only one button */
}

:empty - Empty Element ​

Selects elements that have no children (including text nodes):

css
/* Hide empty containers */
.notification:empty {
  display: none;
}

/* Empty state prompt */
.list:empty::before {
  content: "No data available";
  color: #999;
  font-style: italic;
}

/* Empty table cells */
td:empty {
  background-color: #f5f5f5;
}

td:empty::after {
  content: "-";
  color: #ccc;
}

Form Pseudo-classes ​

These pseudo-classes are specifically for handling various states of form elements.

:disabled and :enabled ​

css
/* Disabled state */
input:disabled,
button:disabled {
  background-color: #e9ecef;
  color: #6c757d;
  cursor: not-allowed;
  opacity: 0.6;
}

/* Enabled state */
input:enabled {
  background-color: white;
  cursor: text;
}

/* Disabled button doesn't respond to hover */
button:disabled:hover {
  background-color: #e9ecef;
  transform: none;
}

:checked - Checked State ​

Used for radio buttons, checkboxes, and <option> elements:

css
/* Custom checkbox */
input[type="checkbox"]:checked + label::before {
  background-color: #3498db;
  border-color: #3498db;
}

input[type="checkbox"]:checked + label::after {
  content: "✓";
  color: white;
  position: absolute;
  left: 4px;
  top: 0;
}

/* Toggle switch effect */
.switch input:checked + .slider {
  background-color: #3498db;
}

.switch input:checked + .slider::before {
  transform: translateX(20px);
}

/* Tabs */
.tab-input:checked + .tab-label {
  background-color: white;
  border-bottom-color: white;
  color: #3498db;
}

.tab-input:checked ~ .tab-content {
  display: block;
}

:required and :optional ​

css
/* Required field indicator */
input:required {
  border-left: 3px solid #e74c3c;
}

input:required + label::after {
  content: " *";
  color: #e74c3c;
}

/* Optional field */
input:optional {
  border-left: 3px solid #95a5a6;
}

:valid and :invalid ​

Based on HTML5 form validation rules:

css
/* Valid input */
input:valid {
  border-color: #27ae60;
}

input:valid + .icon::before {
  content: "✓";
  color: #27ae60;
}

/* Invalid input */
input:invalid {
  border-color: #e74c3c;
}

input:invalid + .error-message {
  display: block;
  color: #e74c3c;
  font-size: 0.875em;
  margin-top: 4px;
}

/* Avoid showing error in initial state */
input:invalid:not(:focus):not(:placeholder-shown) {
  border-color: #e74c3c;
}

:in-range and :out-of-range ​

Used for input boxes with min and max attributes:

css
input[type="number"]:in-range {
  border-color: #27ae60;
  background-color: #d5f4e6;
}

input[type="number"]:out-of-range {
  border-color: #e74c3c;
  background-color: #fadbd8;
}

:placeholder-shown ​

When the input box displays placeholder text:

css
input:placeholder-shown {
  border-color: #ddd;
}

/* Floating label effect */
input:not(:placeholder-shown) + label {
  transform: translateY(-24px) scale(0.85);
  color: #3498db;
}

Pseudo-classes specifically for <a> elements:

css
/* Unvisited link */
a:link {
  color: #3498db;
}

/* Visited link */
a:visited {
  color: #9b59b6;
}

/* Link pseudo-class order is important: LVHA */
/* :link - :visited - :hover - :active */

/* Practical application */
.nav-link:link,
.nav-link:visited {
  color: #2c3e50;
  text-decoration: none;
}

.nav-link:hover {
  color: #3498db;
}

.nav-link:active {
  color: #2980b9;
}

Negation Pseudo-class ​

The :not() selector allows you to exclude specific elements:

css
/* All li except the last one have bottom border */
li:not(:last-child) {
  border-bottom: 1px solid #ddd;
}

/* All buttons except disabled ones can be hovered */
button:not(:disabled):hover {
  background-color: #2980b9;
}

/* Exclude specific class */
input:not(.no-border) {
  border: 1px solid #ddd;
}

/* Complex combination */
a:not([href^="http"]):not([href^="mailto"]) {
  /* Only select internal links */
  color: #2c3e50;
}

/* Multiple conditions */
input:not(:disabled):not(:read-only):focus {
  background-color: #f8f9fa;
}

Other Practical Pseudo-classes ​

:target - URL Fragment Target ​

When the URL fragment identifier matches an element's ID:

css
/* Highlight anchor target */
:target {
  background-color: #fff3cd;
  border-left: 4px solid #ffc107;
  padding-left: 16px;
  animation: highlight 2s ease;
}

@keyframes highlight {
  from {
    background-color: #fff3cd;
  }
  to {
    background-color: transparent;
  }
}

/* Modal */
.modal:target {
  display: block;
  opacity: 1;
  visibility: visible;
}

:root - Root Element ​

Selects the root element of the document, typically used to define CSS variables:

css
:root {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --font-size-base: 16px;
  --spacing-unit: 8px;
}

.button {
  background-color: var(--primary-color);
  padding: calc(var(--spacing-unit) * 2);
}

Practical Case Studies ​

Smart Form Validation ​

css
.form-field {
  position: relative;
  margin-bottom: 20px;
}

/* Initial state */
.form-field input {
  border: 2px solid #ddd;
  padding: 12px;
  transition: all 0.3s;
}

/* Focus state */
.form-field input:focus {
  border-color: #3498db;
  outline: none;
}

/* Valid input */
.form-field input:valid:not(:placeholder-shown) {
  border-color: #27ae60;
  padding-right: 40px;
}

.form-field input:valid:not(:placeholder-shown) + .status-icon::after {
  content: "✓";
  color: #27ae60;
}

/* Invalid input */
.form-field input:invalid:not(:focus):not(:placeholder-shown) {
  border-color: #e74c3c;
}

.form-field input:invalid:not(:focus):not(:placeholder-shown) ~ .error {
  display: block;
}

/* Required field indicator */
.form-field input:required ~ label::after {
  content: " *";
  color: #e74c3c;
}

Responsive Card Grid ​

css
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 20px;
}

/* First card in each row */
.card:nth-child(4n + 1) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

/* Hover effect */
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}

/* Other cards reduce opacity except hovered one */
.card-grid:hover .card:not(:hover) {
  opacity: 0.7;
}

Pure CSS Accordion ​

css
.accordion-item input[type="checkbox"] {
  display: none;
}

.accordion-header {
  cursor: pointer;
  padding: 15px;
  background-color: #f8f9fa;
  border-bottom: 1px solid #ddd;
}

.accordion-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

input[type="checkbox"]:checked ~ .accordion-content {
  max-height: 500px;
}

input[type="checkbox"]:checked ~ .accordion-header {
  background-color: #3498db;
  color: white;
}

input[type="checkbox"]:checked ~ .accordion-header::after {
  transform: rotate(180deg);
}

Summary ​

Pseudo-classes are one of the most powerful tools in CSS. Mastering pseudo-classes allows you to create rich interactive effects without relying on JavaScript, making user interfaces more dynamic and friendly.

Key Points:

  • User Behavior Pseudo-classes: Respond to user actions, create interactive experiences
    • :hover - Mouse hover state
    • :active - Active state
    • :focus - Focus state
    • :focus-within - Internal focus state
  • Structural Pseudo-classes: Select based on element position in DOM tree
    • :first-child, :last-child - First and last child elements
    • :nth-child(), :nth-of-type() - Flexible position selection
    • :only-child, :empty - Special state elements
  • Form Pseudo-classes: Specifically handle various states of form elements
    • :disabled, :enabled - Disabled/enabled state
    • :checked - Checked state
    • :valid, :invalid - Validation state
    • :placeholder-shown - Placeholder display state
  • Negation Pseudo-class: :not() excludes specific elements
  • Practical Applications: Smart form validation, responsive cards, pure CSS interactive components