Part 2: CSS Syntax and Selectors - The Foundation of Styling

Published on:

Welcome back to the CSS learning series! In this article, we’re diving into the foundation of CSS: syntax and selectors. These are the building blocks that allow you to target and style HTML elements. Understanding selectors deeply will save you hours of debugging and make your CSS more maintainable.

Understanding CSS Syntax

CSS follows a simple, consistent syntax. Let’s break it down:

selector {
  property: value;
  property: value;
}

Here’s a real example:

h1 {
  color: blue;
  font-size: 32px;
  margin-bottom: 20px;
}

Components:

  • Selector: h1 - targets all <h1> elements
  • Declaration block: Everything inside the curly braces {}
  • Property: color, font-size, margin-bottom - what you’re styling
  • Value: blue, 32px, 20px - how you’re styling it
  • Semicolon: Each declaration ends with ; (required)

Multiple Declarations

You can add as many properties as you need within a single rule:

.button {
  background-color: #007bff;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

Basic Selectors

Selectors are patterns that match HTML elements. Let’s start with the most common ones you’ll use daily.

1. Element Selector (Type Selector)

Targets all elements of a specific type:

p {
  color: #333;
  line-height: 1.6;
}

h2 {
  font-size: 24px;
  margin-top: 30px;
}

When to use: Styling default styles for all elements of a type. Perfect for resetting margins, setting default fonts, or establishing base typography.

Common use case: Reset/normalize styles, typography defaults, or base styles that apply to all elements of a type.

2. Class Selector

Targets elements with a specific class attribute. Prefixed with a dot (.):

.card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 20px;
}

HTML:

<div class="card">Card content</div>
<div class="card">Another card</div>

When to use: This is your go-to selector for reusable styles. Classes are the most flexible and maintainable way to style elements. Use classes for components, utilities, and reusable patterns.

Common use case: Component styling, utility classes, theme classes. This is what you’ll use 80% of the time in modern CSS.

3. ID Selector

Targets a single element with a specific ID. Prefixed with a hash (#):

#header {
  background-color: #2c3e50;
  padding: 20px;
}

#navigation {
  display: flex;
  justify-content: space-between;
}

HTML:

<header id="header">Header content</header>
<nav id="navigation">Navigation</nav>

When to use: IDs should be used sparingly. They’re perfect for unique page elements like header, footer, main content areas, or JavaScript hooks.

⚠️ Important: IDs are unique—only one element per page should have a specific ID. Avoid using IDs for styling if you can use a class instead. IDs have high specificity (more on this later), which can make your CSS harder to override.

Common use case: Unique page sections, JavaScript selectors, anchor links.

4. Universal Selector

Targets all elements. Prefixed with an asterisk (*):

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

When to use: CSS resets or normalizing styles. The box-sizing: border-box reset is extremely common in modern CSS.

Common use case: CSS resets, global styles like box-sizing.

Combining Selectors

You can combine selectors to create more specific rules:

Multiple Selectors (Grouping)

Apply the same styles to multiple selectors:

h1,
h2,
h3 {
  font-family: "Arial", sans-serif;
  color: #2c3e50;
  margin-bottom: 15px;
}

This is equivalent to writing three separate rules, but more concise.

Element with Class

Target elements that have both a specific tag and class:

button.primary {
  background-color: #007bff;
  color: white;
}

button.secondary {
  background-color: #6c757d;
  color: white;
}

HTML:

<button class="primary">Primary Button</button>
<button class="secondary">Secondary Button</button>

Common use case: When you want to style the same element type differently based on a modifier class.

Combinators

Combinators allow you to select elements based on their relationship in the HTML tree.

Descendant Combinator (Space)

Selects all descendants (children, grandchildren, etc.):

.navbar a {
  color: #333;
  text-decoration: none;
}

This targets all <a> elements inside .navbar, at any nesting level.

Common use case: Styling nested elements within components. This is used extensively in component-based styling.

Child Combinator (>)

Selects only direct children:

.menu > li {
  padding: 10px;
  border-bottom: 1px solid #eee;
}

HTML:

<ul class="menu">
  <li>Direct child</li>
  <!-- ✅ Selected -->
  <li>
    <ul>
      <li>Nested child</li>
      <!-- ❌ Not selected -->
    </ul>
  </li>
</ul>

Common use case: When you need to style only direct children, not nested elements. Useful for navigation menus, list layouts.

Adjacent Sibling Combinator (+)

Selects the immediately following sibling:

h2 + p {
  margin-top: 0;
  font-weight: bold;
}

HTML:

<h2>Heading</h2>
<p>This paragraph is selected</p>
<!-- ✅ Selected -->
<p>This paragraph is not</p>
<!-- ❌ Not selected -->

Common use case: Styling the first element that follows another element. Great for typography where you want the first paragraph after a heading to have different styling.

General Sibling Combinator (~)

Selects all following siblings:

h2 ~ p {
  color: #666;
}

HTML:

<h2>Heading</h2>
<p>Selected</p>
<!-- ✅ Selected -->
<p>Also selected</p>
<!-- ✅ Selected -->
<div>Not selected</div>
<p>Still selected</p>
<!-- ✅ Selected -->

Common use case: Styling all siblings that come after a specific element. Useful for content sections where you want consistent styling after a heading.

Attribute Selectors

Select elements based on their attributes:

Basic Attribute Selector

input[type="text"] {
  border: 1px solid #ccc;
  padding: 8px;
}

a[href^="https"] {
  color: green;
}

Common attribute selectors:

  • [attribute] - element has the attribute
  • [attribute="value"] - exact match
  • [attribute^="value"] - starts with
  • [attribute$="value"] - ends with
  • [attribute*="value"] - contains

Common use case: Styling form inputs by type, targeting external links, or selecting elements based on data attributes.

Pseudo-classes

Pseudo-classes target elements in specific states or positions:

State-based Pseudo-classes

a:hover {
  color: #007bff;
  text-decoration: underline;
}

a:visited {
  color: #6c757d;
}

a:active {
  color: #0056b3;
}

button:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

input:checked {
  accent-color: #007bff;
}

Common use case: Interactive states are essential for UX. Always style :hover, :focus, and :active states for better user experience.

Structural Pseudo-classes

li:first-child {
  font-weight: bold;
}

li:last-child {
  border-bottom: none;
}

li:nth-child(2) {
  color: red;
}

li:nth-child(even) {
  background-color: #f8f9fa;
}

li:nth-child(odd) {
  background-color: white;
}

p:not(.excluded) {
  color: #333;
}

Common use case:

  • :first-child / :last-child - Remove borders/margins from first/last items in lists
  • :nth-child() - Zebra striping tables, styling every nth item in galleries
  • :not() - Apply styles to all elements except specific ones

Form Pseudo-classes

input:required {
  border-left: 3px solid red;
}

input:valid {
  border-color: green;
}

input:invalid {
  border-color: red;
}

Common use case: Form validation styling, providing visual feedback for form states.

Pseudo-elements

Pseudo-elements target specific parts of an element:

p::before {
  content: "→ ";
  color: #007bff;
}

p::after {
  content: " ←";
  color: #007bff;
}

p::first-line {
  font-weight: bold;
  font-size: 1.2em;
}

p::first-letter {
  font-size: 3em;
  float: left;
  line-height: 1;
  margin-right: 5px;
}

::selection {
  background-color: #007bff;
  color: white;
}

Common use case:

  • ::before / ::after - Decorative elements, icons, quotes
  • ::first-letter - Drop caps, typography effects
  • ::selection - Customizing text selection color

Specificity: The Cascade Rules

When multiple rules target the same element, CSS uses specificity to determine which styles apply. Understanding this is crucial for debugging CSS.

Specificity Calculation

Specificity is calculated using a point system:

  1. Inline styles: 1000 points
  2. IDs: 100 points each
  3. Classes, attributes, pseudo-classes: 10 points each
  4. Elements, pseudo-elements: 1 point each
/* Specificity: 1 point (element) */
p {
  color: black;
}

/* Specificity: 10 points (class) */
.text {
  color: blue;
}

/* Specificity: 11 points (class + element) */
p.text {
  color: green;
}

/* Specificity: 100 points (ID) */
#intro {
  color: red;
}

/* Specificity: 101 points (ID + element) */
#intro p {
  color: orange;
}

Example conflict:

<p id="intro" class="text">Hello World</p>

In this case, #intro p (101 points) would win, making the text orange.

Important Rules

  1. Inline styles always win (unless you use !important)
  2. More specific selectors win over less specific ones
  3. Later rules win when specificity is equal (cascade order)

The !important Declaration

.button {
  background-color: blue !important;
}

⚠️ Use sparingly: !important overrides specificity but makes CSS harder to maintain. Only use it when absolutely necessary (like overriding third-party CSS).

Best Practices from Experience

Here are some practical tips I’ve learned over years of writing CSS:

1. Prefer Classes Over IDs for Styling

/* ❌ Avoid */
#header {
  ...;
}
#footer {
  ...;
}

/* ✅ Prefer */
.header {
  ...;
}
.footer {
  ...;
}

IDs are less flexible and harder to override. Classes are reusable and maintainable.

2. Use Semantic Class Names

/* ❌ Avoid */
.red-box {
  ...;
}
.big-text {
  ...;
}

/* ✅ Prefer */
.error-message {
  ...;
}
.heading-large {
  ...;
}

Names should describe purpose, not appearance.

3. Keep Specificity Low

/* ❌ Avoid - too specific */
#header .nav ul li a {
  ...;
}

/* ✅ Prefer - simpler */
.nav-link {
  ...;
}

Lower specificity = easier to override and maintain.

4. Use BEM-like Naming

.card {
  ...;
}
.card__header {
  ...;
}
.card__body {
  ...;
}
.card--featured {
  ...;
}

This makes relationships clear and avoids specificity issues.

5. Leverage Pseudo-classes for Dynamic States

Always style interactive states:

.button {
  transition: background-color 0.2s;
}

.button:hover {
  background-color: darken(primary, 10%);
}

.button:focus {
  outline: 2px solid primary;
}

.button:active {
  transform: scale(0.98);
}

Practical Example: Building a Card Component

Let’s put it all together with a real-world example:

/* Base card component */
.card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 24px;
  margin-bottom: 20px;
}

/* Card header */
.card__header {
  font-size: 24px;
  font-weight: bold;
  margin-bottom: 12px;
  color: #2c3e50;
}

/* Card body */
.card__body {
  color: #666;
  line-height: 1.6;
}

/* Card footer */
.card__footer {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid #eee;
}

/* Featured variant */
.card--featured {
  border: 2px solid #007bff;
  box-shadow: 0 4px 12px rgba(0, 123, 255, 0.2);
}

/* Links inside card */
.card a {
  color: #007bff;
  text-decoration: none;
}

.card a:hover {
  text-decoration: underline;
}

/* First card in a list */
.card:first-child {
  margin-top: 0;
}

HTML:

<div class="card card--featured">
  <div class="card__header">Featured Article</div>
  <div class="card__body">
    <p>This is the card content with a <a href="#">link</a>.</p>
  </div>
  <div class="card__footer">
    <button>Read More</button>
  </div>
</div>

What’s Next?

You now have a solid foundation in CSS syntax and selectors! These concepts are the building blocks for everything else in CSS.

In the next article, we’ll dive deep into CSS Properties—learning how to use typography, colors, backgrounds, borders, and the box model to create beautiful, well-styled components.

Practice Exercise: Try creating a navigation menu using different selectors. Use classes, pseudo-classes for hover states, and combinators to style nested elements. This will help solidify your understanding!


Remember: The best way to learn CSS is by doing. Open your code editor, experiment with these selectors, and see what happens. Don’t be afraid to break things—that’s how you learn! 🚀