Intermediate35 minCSSPolish

Styling Details: Selectors, Pseudo-elements, and Motion

Add finishing detail with practical selectors, pseudo-elements, transitions, transforms, and restrained animation.

Learning Objectives

By the end of this lesson, you'll be able to:

  • Use combinators and pseudo-classes to target elements precisely without extra class names
  • Add decorative detail with ::before and ::after pseudo-elements
  • Write smooth property transitions with appropriate duration and timing
  • Apply Apply transforms (translate, scale, rotate) for interactive visual effects
  • Judge when motion supports user experience and when it detracts from it
  • Implement the prefers-reduced-motion media query for accessibility

Why This Matters:

These are the finishing tools. They turn a layout that works into a page that feels considered, responsive, and professional — without overcomplicating your CSS.

Before You Start:

You should be familiar with:

Practical Selectors: Targeting Exactly What You Need

You already know element selectors (p), class selectors (.card), and ID selectors (#main). But CSS has a much wider vocabulary for targeting elements — and using it well means you can style precisely without adding extra classes to your HTML.

Visual table showing five selector categories — element, class, attribute, combinator, and pseudo-class — each with a CSS example and a diagram highlighting what it matches.
Each selector type gives you a different level of control. Classes are the everyday workhorse. Combinators and pseudo-classes add precision when you need it.

Combinators: relationships between elements

Combinators let you select elements based on their position in the DOM tree relative to another element:

/* Direct child only — not deeper descendants */
.card > h2 { font-size: 1.25rem; }

/* Adjacent sibling — the p immediately after an h2 */
h2 + p { margin-top: 0.5rem; }

/* General sibling — any p after an h2, at the same level */
h2 ~ p { color: #52606d; }

The child combinator (>) is especially useful for preventing styles from leaking into nested components. If you style .nav a, every link inside nav is affected — including links inside a dropdown. .nav > a targets only the direct children.

Attribute selectors: matching by data, not appearance

/* Match inputs by type */
input[type="email"] { border-color: #3d85c6; }

/* Match links that open in a new tab */
a[target="_blank"]::after { content: " ↗"; }

/* Match elements whose class starts with "icon-" */
[class^="icon-"] { display: inline-block; }

Attribute selectors are powerful for form styling and for targeting elements without adding extra classes. They score the same as classes in specificity.

Structural pseudo-classes: position in a list

/* First and last items */
li:first-child { font-weight: 700; }
li:last-child { border-bottom: none; }

/* Every other row (striped tables) */
tr:nth-child(even) { background: #f8f9fa; }

/* Only child — useful for conditional layouts */
.card:only-child { max-width: 600px; margin: 0 auto; }

Structural pseudo-classes let you style elements by their position without adding .first or .last classes manually. The browser handles the counting.

State pseudo-classes: interactivity

/* User interaction states */
a:hover { color: #8f2f23; }
a:focus-visible { outline: 2px solid #3d85c6; outline-offset: 2px; }
button:active { transform: scale(0.98); }

/* Form validation states */
input:required { border-left: 3px solid #e67c00; }
input:valid { border-left-color: #2d6a4f; }

State pseudo-classes respond to user behaviour. :focus-visible is especially important — it shows a focus ring for keyboard users but hides it for mouse clicks, giving you the best of both worlds for accessibility.

Pseudo-elements: Visual Detail Without Extra HTML

Pseudo-elements let you insert styled content before or after an element's real content, entirely through CSS. They are ideal for decorative details — underlines, icons, badges, dividers — that do not belong in the HTML because they are presentational, not structural.

Diagram showing how ::before and ::after pseudo-elements are generated inside a heading element, with a practical example of a decorative underline bar added via CSS.
::before and ::after are generated inside the element as its first and last children. The content property is required — without it, nothing renders.

The essential rule: content is required

Every ::before and ::after pseudo-element must have a content property. Even if the content is an empty string, the property must be there:

/* This works — empty string creates a styled box */
.heading::after {
  content: "";
  display: block;
  width: 60px;
  height: 3px;
  background: #8f2f23;
}

/* This does NOT work — no content means no element */
.heading::after {
  display: block;
  width: 60px;
  height: 3px;
  background: #8f2f23;
}

Practical pseudo-element patterns

Decorative underline

.section-title::after {
  content: "";
  display: block;
  width: 50px;
  height: 3px;
  background: #8f2f23;
  margin-top: 0.5rem;
}

External link indicator

a[target="_blank"]::after {
  content: " ↗";
  font-size: 0.8em;
  color: #7b8794;
}

Required field marker

label.required::after {
  content: " *";
  color: #8f2f23;
  font-weight: 700;
}

Accessibility note: content generated by pseudo-elements is read by some screen readers but not all. Do not put meaningful text — like instructions or labels — in pseudo-elements. Use them for decoration only.

Transitions: Smooth Property Changes

Without a transition, CSS property changes are instant. One frame the link is blue, the next frame it is red. Transitions add a duration to that change, letting the browser interpolate between the old and new values over time.

Diagram showing the four parts of a CSS transition — property, duration, timing function, and delay — with a timeline visualising a 0.3-second ease transition on hover.
A transition has four parts: which property to animate, how long it takes, what speed curve to use, and how long to wait before starting.

Setting transitions on the base state

Transitions belong on the element's default state, not on the trigger state. This ensures the transition runs in both directions — on hover and on hover-out:

/* ✅ Correct — transition on the base state */
.nav-link {
  color: #1f2933;
  transition: color 0.2s ease;
}
.nav-link:hover {
  color: #8f2f23;
}

/* ❌ Wrong — transition only on :hover means
   the return to default is instant */
.nav-link {
  color: #1f2933;
}
.nav-link:hover {
  color: #8f2f23;
  transition: color 0.2s ease;
}

Choosing what to transition

Be specific about which properties you transition. Using transition: all is tempting but it can cause unexpected animations on properties you did not intend to change — including expensive layout properties:

/* ❌ Transitions everything, including layout properties */
.card { transition: all 0.3s ease; }

/* ✅ Only the properties you want animated */
.card {
  transition: background-color 0.2s ease,
              box-shadow 0.2s ease;
}

Timing functions

The timing function controls the speed curve. For most UI work, you only need two or three:

  • ease — starts fast, ends slow. Good default for most hover effects.
  • ease-in-out — slow start and slow end. Good for elements entering and leaving visibility.
  • linear — constant speed. Useful for progress bars or loading indicators, but feels mechanical for UI.

Transforms: Visual Changes Without Layout Shifts

Transforms change how an element looks — its position, size, or rotation — without affecting the document flow. The element keeps its original space in the layout, and neighbouring elements do not shift.

This makes transforms ideal for interactive effects. A hover lift, a button press, or a card entrance can use transforms without causing the rest of the page to reflow.

Three panels showing the effect of translate, scale, and rotate transforms on the same card element, with dashed outlines showing the original position.
Transforms are purely visual. The dashed outline shows where the element actually sits in the document flow — surrounding content is unaffected.

translate: shift position

/* Hover lift — the classic card effect */
.card {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}

scale: grow or shrink

/* Button press feedback */
.button:active {
  transform: scale(0.97);
}

/* Subtle image zoom on hover */
.thumbnail:hover {
  transform: scale(1.03);
}

rotate: tilt

/* Playful hover accent on a small icon */
.icon-link:hover .icon {
  transform: rotate(8deg);
}

Combining transforms

You can apply multiple transforms in a single declaration. They are applied right to left:

/* Lift + slight scale on hover */
.card:hover {
  transform: translateY(-4px) scale(1.02);
}

Performance note: transform and opacity are the cheapest properties to animate because the browser can handle them on the GPU compositor layer. Animating width, height, or margin causes layout recalculation and is noticeably slower.

Restrained Motion: When Animation Helps and When It Hurts

Motion in CSS is a tool, not a feature list. A well-placed transition makes an interface feel responsive. An excessive animation makes it feel chaotic.

Side-by-side comparison of restrained button hover (subtle lift, 0.2s) versus excessive animation (spinning, scaling, 1.5s) with guidelines for good motion.
Restrained motion signals interactivity. Excessive motion distracts and can cause physical discomfort for some users.

Motion Decision Checklist

  • Does this motion serve a purpose? It should signal interactivity, provide feedback, or guide attention — not just look cool.
  • Is the duration appropriate? UI interactions should feel snappy: 0.15s to 0.4s. Longer than 0.5s feels sluggish.
  • Am I animating more than two properties? If so, consider whether all of them need to animate on the same trigger.
  • Would a user notice if I removed it? If yes, the motion is serving a purpose. If no, you can probably remove it.
  • If in doubt, leave it out. You can always add motion later. Removing it after users have grown used to it is harder.

prefers-reduced-motion: Respecting User Preferences

Some users experience motion sickness, vertigo, or discomfort from screen animations. Operating systems let users signal this preference, and CSS lets you respond to it with a media query:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

This is a near-instant duration rather than none, which ensures JavaScript-based animation callbacks still fire. Place this at the end of your stylesheet.

You can also be more surgical — keeping subtle colour transitions while removing movement:

@media (prefers-reduced-motion: reduce) {
  .card {
    transition: background-color 0.2s ease;
    /* Remove the lift transform but keep the colour shift */
  }
  .card:hover {
    transform: none;
  }
}

This is not optional. Respecting prefers-reduced-motion is a baseline accessibility requirement, not a nice-to-have. WCAG 2.1 (guideline 2.3) addresses motion that can cause seizures or discomfort.

⏸️ Pause & Check: Do You Understand?

Before moving forward, can you answer these?

  1. What is the difference between a pseudo-class (:hover) and a pseudo-element (::before)?
  2. Why must you always include a content property on ::before and ::after, even if it is empty?
  3. You write transition: all 0.3s ease on a card. Why might "all" cause unexpected results?
  4. A button has a hover lift using translateY(-4px). Does the element below it shift up to fill the gap?
Check Your Answers
  1. A pseudo-class selects an existing element in a particular state (hovered, focused, first-child). A pseudo-element creates a virtual sub-element that you can style — it does not exist in the HTML source.
  2. The browser only generates the pseudo-element when the content property is present. Without it — even if you set width, height, and background — the pseudo-element does not render at all.
  3. Transitioning "all" means every property that changes will animate, including layout properties like width and height that may cause jank, or properties you did not intend to animate. Listing specific properties gives you control.
  4. No. Transforms are purely visual — the element keeps its original space in the document flow. Neighbouring elements are unaffected.

How confident are you with this concept?

😕 Still confused | 🤔 Getting there | 😊 Got it! | 🎉 Could explain it to a friend!

Add Polish to a Page

Apply pseudo-elements, transitions, transforms, and a reduced-motion fallback to an existing project page.

Add a decorative pseudo-element

Pick a heading in your project — the Black Swan Bistro site works well. Add a ::after pseudo-element that creates a short decorative underline bar beneath the heading.

  1. Set content: "" (empty string)
  2. Set display: block
  3. Give it a width, height, background colour, and a small margin-top
  4. Verify in DevTools that the pseudo-element appears inside the heading element in the DOM tree
💡 Need a hint?
A typical decorative bar might be 40–80px wide, 3px tall, with a brand colour.
If the bar does not appear, check that you have content: "" — this is the most common mistake.

Add a hover transition to a link or button

Find a navigation link or button in your project. Add a smooth colour transition on hover:

  1. Set the transition property on the default state (not on :hover)
  2. Specify the property name, duration, and timing function
  3. Add the colour change on the :hover pseudo-class
  4. Test it in the browser — it should feel smooth, not instant
💡 Need a hint?
Start with transition: color 0.2s ease and adjust from there.
Always set the transition on the base selector, not on :hover. The base state needs to know how to transition back.

Create a hover lift with transform

Add a subtle lift effect to a card or link component:

  1. Set transition: transform 0.2s ease on the default state
  2. On :hover, add transform: translateY(-3px)
  3. Optionally add a box-shadow transition to pair with the lift
  4. Check that surrounding content does not shift — the transform should be purely visual
💡 Need a hint?
Keep the lift small — 2px to 4px is enough. Larger lifts look cartoonish.
If you want shadow and transform to transition together, list both: transition: transform 0.2s ease, box-shadow 0.2s ease.

Respect prefers-reduced-motion

Add a media query that disables or reduces your transitions and transforms for users who prefer reduced motion:

  1. Add @media (prefers-reduced-motion: reduce) { } at the end of your stylesheet
  2. Inside, set transition: none and transform: none on the animated elements
  3. Test by enabling "Reduce motion" in your operating system preferences or via DevTools (Rendering tab → Emulate CSS media feature)
💡 Need a hint?
In macOS: System Settings → Accessibility → Display → Reduce motion.
In Chrome DevTools: open the Rendering tab (three-dot menu → More tools → Rendering), scroll to "Emulate CSS media feature prefers-reduced-motion".

You have finished when:

  • ☐ A heading has a decorative ::after underline bar
  • ☐ A link or button transitions colour smoothly on hover
  • ☐ A card or interactive element has a hover lift using translateY
  • ☐ A prefers-reduced-motion media query disables or reduces motion

💪 Independent Challenge

Now try this on your own without hints!

Your Task:

Open a project page you have been building (the Black Swan Bistro site is ideal). Add a polish pass that applies at least four of the techniques from this lesson: a pseudo-element, a precise selector (combinator or attribute), a transition, and a transform. Each addition should serve a clear purpose — not just demonstrate a feature.
Requirements:
  • At least one pseudo-element (::before or ::after) adding a decorative detail
  • At least one combinator or attribute selector replacing a situation where you might have added a new class
  • At least one transition on a hover or focus interaction
  • At least one transform (translate, scale, or rotate) paired with a transition
  • A prefers-reduced-motion media query that handles all your motion additions
Stretch Goals (Optional):
  • Use :nth-child to create alternating styles (striped rows, offset cards)
  • Add a focus-visible outline style to all interactive elements for keyboard navigation
  • Combine ::before content with an attribute selector to auto-label external links

Success Criteria:

CriteriaYou've succeeded if...
Purposeful detailEvery visual addition (pseudo-element, transition, transform) serves a clear UI purpose, not just decoration for its own sake
Selector precisionAt least one selector uses a combinator or attribute selector to avoid adding a new class to the HTML
Restrained motionTransitions are 0.15s–0.4s, transforms are subtle, and no animation distracts from content
Accessibilityprefers-reduced-motion media query is present and handles all animated properties

Recap

This lesson covered the CSS tools that turn a working layout into a polished page. Combinators and pseudo-classes let you target elements precisely without cluttering your HTML with extra classes. Pseudo-elements add decorative detail through CSS alone — a heading underline, an external-link indicator, a required-field marker — without extra DOM elements.

Transitions smooth out property changes over time. Set them on the base state, be explicit about which properties to transition, and keep durations between 0.15s and 0.4s for UI interactions. Transforms — translate, scale, rotate — change how an element looks without affecting its space in the document flow, making them ideal for hover lifts, button presses, and subtle reveals.

The hardest skill is restraint. Good motion supports the user's attention. Excessive motion fights it. And prefers-reduced-motion is not a feature — it is a responsibility.

Your Pages Can Feel Considered Now

Key Takeaways:

  • Selectors let you target exactly the elements you need — combinators, attribute selectors, and pseudo-classes give precision without adding extra classes to HTML.
  • Pseudo-elements (::before, ::after) add visual detail through CSS alone. They always require a content property.
  • Transitions animate property changes over time. Set them on the base state, not the trigger state.
  • Transforms (translate, scale, rotate) are purely visual — they do not affect document flow or surrounding elements.
  • Restrained motion supports user experience. Excessive motion undermines it. When in doubt, leave it out.
  • prefers-reduced-motion is not optional. Users who need reduced motion depend on your stylesheet respecting that preference.

Learning Objectives Review:

Look back at what you set out to learn. Can you now:

  • ✅ Use combinators and pseudo-classes to target elements precisely Check!
  • ✅ Add decorative detail with ::before and ::after pseudo-elements Got it!
  • ✅ Write smooth transitions with appropriate duration and timing Can explain it!
  • ✅ Apply transforms for hover lifts, scale effects, and subtle rotation Could teach this!
  • ✅ Judge when motion helps and when it hurts usability Check!
  • ✅ Implement prefers-reduced-motion to respect user accessibility needs Got it!

If you can confidently answer "yes" to most of these, you're ready to move on!

Think & Reflect:

Selector Precision

  • Have you used a selector in this lesson that you had not tried before? Which one?
  • Can you think of a place in your project where an attribute selector or :nth-child would simplify your CSS?

Visual Polish

  • What is one decorative detail you could add to your project with a pseudo-element?
  • How do you decide whether a transition duration is too short or too long?

Motion and Accessibility

  • Before this lesson, had you heard of prefers-reduced-motion? How does it change your approach to adding animation?
  • What is the simplest way to test reduced motion behaviour without changing your OS settings?

🎯 Looking Ahead:

With selectors, pseudo-elements, and motion in your toolkit, you are ready to build complete, polished web pages. The next steps in the pathway bring these pieces together in project work.

Recommended Next Steps

Continue Learning

Ready to move forward? Continue with the next tutorial in this series:

BSB Part 4B: Polish and Refine

Additional Resources

Deepen your understanding with these helpful resources:

Progress tracking is disabled. Enable it in to track your completed tutorials.