Part 1: Foundations
The core rules of CSS. Understand how styles are applied, how elements are targeted, and how space works on the web.
What CSS Is & How to Add It
CSS (Cascading Style Sheets) is a separate language whose only job is to describe how HTML should look. Same HTML, different CSS, completely different site.
The anatomy of a rule
A CSS rule has three parts: a selector (what to style), a property (what to change) and a value (the new setting):
h1 {
color: purple; /* property: value; */
font-size: 32px;
}Read it as: "find every h1, make its text purple and 32 pixels tall". There are three ways to attach CSS, but professionals almost always use the third:
- Inline:
style="..."on a tag — avoid, it doesn't scale. - Internal: a
<style>block in the<head>— fine for one page. - External: a separate
.cssfile linked with<link rel="stylesheet" href="styles.css">— the professional standard.
The same HTML, before and after CSS
- This is plain HTML with no CSS: black text, default fonts, no layout. Functional but bland.
- Now we link a stylesheet. Colours, sizes, spacing and a styled button appear — same HTML, new look.
Separation of concernsKeeping structure (HTML) and style (CSS) in separate files means you can redesign a site's entire appearance without touching a single line of content. That's why external stylesheets win.
Selectors & the Cascade
A selector decides which elements a rule applies to. Master selectors and you can target exactly what you want, anywhere on the page.
The three you'll use constantly
- Type selector —
p— every paragraph. - Class selector —
.btn— every element withclass="btn". Reusable, your workhorse. - ID selector —
#header— the one element withid="header". Unique.
Watch each selector pick its targets
p— the type selector grabs every paragraph at once..card— the class selector grabs every element sharing that class. Reusable across the page.#cta— the ID selector targets exactly one unique element.
What "cascading" means
When two rules target the same element, CSS resolves the conflict with specificity: an ID beats a class, a class beats a type. If specificity ties, the rule written last wins. This ordering is the "cascade" in the name.
Prefer classes over IDs for stylingIDs are very "heavy" in specificity, which makes them hard to override later. Style with classes; reserve IDs for JavaScript hooks and page anchors.
The Box Model
This is the single most important concept in CSS layout. Every element on a page — text, image, button, div — is a rectangular box with four layers. Understand them and spacing stops being a mystery.
The four layers of every box
- Content — the actual text or image, the innermost box.
- Padding — transparent space inside the border, pushing the border away from the content.
- Border — the line drawn around the padding. Can be thick, dashed, coloured.
- Margin — transparent space outside the border that pushes other elements away.
Padding vs margin trips up every beginner. The rule: padding is space inside an element (it grows the coloured area); margin is space between elements (it stays transparent). Need breathing room inside a button? Padding. Need a gap between two cards? Margin.
Pro default: box-sizing: border-boxBy default, adding padding makes an element wider. Almost every real project sets * { box-sizing: border-box; } so that width includes padding and border — layouts become far more predictable.
Part 2: Design & Layout
Learn how to make things beautiful and structure them perfectly on any screen size using modern layout tools.
Colors, Units & Typography
Good-looking sites come down to disciplined colour, spacing and type. Here are the tools.
Ways to write a colour
- Named:
red,rebeccapurple— quick, limited. - Hex:
#b534ff— the most common in professional code. rgb(181, 52, 255)andrgba(...)— theaadds transparency (0–1).hsl(270, 100%, 60%)— hue/saturation/lightness, easiest to tweak by hand.
Units: px vs rem vs %
px is a fixed pixel. rem is relative to the page's base font size — great for scalable, accessible typography. % is relative to the parent — great for fluid widths. A common professional recipe: sizes in rem, layout widths in % or fr.
Styling a card, one property at a time
- Start with a plain grey box.
border-radius: 16px;softens the corners.background: linear-gradient(...)adds colour and depth.padding: 40px;gives the content room to breathe.box-shadow+transformlift it off the page. Now it looks designed.
Flexbox: Aligning Things at Last
Before Flexbox, centering a box or spacing a navbar was genuinely painful. Flexbox makes arranging items in a row or column effortless — it's the layout tool you'll reach for every single day.
Add display: flex to a container and its direct children become flex items you can line up and distribute. Two properties do most of the work: justify-content (along the main axis) and align-items (across it).
Flex Children: Grow, Shrink & Basis
While the container controls alignment, the children control their own sizing using the flex shorthand property (which combines flex-grow, flex-shrink, and flex-basis). For example, flex: 1 means "grow to fill any available remaining space evenly". Setting flex-wrap: wrap on the container allows items to wrap onto the next line when they run out of room.
justify-content & align-items in action
display: flexlines the three items up in a row (the default: packed to the start).justify-content: centerpushes them to the centre of the row.justify-content: space-betweenspreads them out with equal gaps — perfect for a navbar.align-items: centercentres them on the cross-axis too. Vertical centering, finally solved.
The famous "center anything" trickdisplay:flex; justify-content:center; align-items:center; on a container centres its child both horizontally and vertically. Three lines that used to take hacks and tears.
Grid & Responsive Design
Flexbox is one-dimensional (a row or a column). CSS Grid is two-dimensional — rows and columns together — ideal for whole-page layouts and card galleries. Then media queries make it all adapt to phones.
Grid in one line
display: grid plus grid-template-columns defines your columns. 1fr means "one fraction of the free space", so repeat(3, 1fr) makes three equal columns.
Visual Layout with grid-template-areas
One of Grid's most powerful features is grid-template-areas, which lets you "draw" your layout using strings. You can define areas like "header header" "sidebar main" "footer footer" and then assign elements using grid-area: sidebar;. This makes rearranging entire page structures for mobile as simple as redefining the strings in a media query.
A responsive grid reacting to screen size
- On a wide screen we ask for
repeat(3, 1fr)— three equal columns side by side. - A media query detects a narrow phone and drops the grid to a single column so nothing is cramped.
- On a mid-size tablet, two columns is the sweet spot. Same HTML, layout adapts to the device.
Responsive = one site, every screenA media query — @media (max-width: 600px) { ... } — applies rules only below a certain width. This is how a single codebase serves phones, tablets and desktops. Always test your site narrow!
Responsive Auto-Grid (The Holy Grail)
Instead of relying entirely on media queries, Grid offers a "holy grail" pattern using auto-fit and minmax(). It automatically creates as many columns as will fit, dropping to fewer columns as the screen shrinks — zero media queries required.
.grid {
display: grid;
gap: 20px;
/* Columns must be at least 250px, but can stretch to 1fr if space allows */
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}Positioning, Motion & Hover Effects
Layout tools place elements in the flow. But sometimes you need to lift an element out of the flow — a badge on a corner, a sticky header — and add the smooth motion that makes a site feel polished and alive.
The position property
static— the default, normal document flow.relative— nudged from its normal spot, and becomes an anchor for absolute children.absolute— removed from flow and placed relative to the nearest positioned ancestor (usingtop/right/bottom/left).fixed— pinned to the screen, ignores scrolling (great for headers).sticky— normal until it hits a scroll edge, then it sticks.
Transitions bring it to life
A transition tells the browser to animate a change smoothly instead of snapping. Pair it with :hover and transform for the interactive feel every good button has:
.card { position: relative; transition: transform 0.3s ease; }
.card:hover { transform: translateY(-8px); }
.badge {
position: absolute;
top: -10px; right: -10px;
}Absolute positioning + a hover lift
- The badge starts in the flow — sitting below the price like normal text.
- Set the card to
position: relativeand the badge toposition: absolute→ it flies to the top-right corner, overlapping the edge. - Add a
transitionand hover the card: it lifts smoothly with a glowing shadow. That's the polish users feel.
The golden rule of absoluteAn absolute element positions itself against its nearest ancestor that has position set (anything but static). Forget to add position: relative to the parent and your badge will fly to the corner of the whole page instead. This one rule causes half of all positioning bugs.
Part 3: Advanced Concepts
Go beyond the basics. Learn complex targeting, creating motion, building scalable design systems, and robust architecture.
Advanced Selectors & Specificity
Basic selectors target elements by tag, class or id. But professionals reach for a richer toolkit — pseudo-classes, pseudo-elements and combinators — that lets you style elements based on their state and position without adding a single extra class.
Pseudo-classes: styling by state or position
A pseudo-class (written with one colon :) targets an element in a particular state or position — without you needing to add a class in the HTML:
:hover,:focus,:active— interaction states.:first-child,:last-child,:nth-child(even)— position in a list.:not(.active)— everything except a match.
Pseudo-elements (written with two colons ::) let you style or inject a piece of an element that isn't in the HTML at all — ::before and ::after add generated content, ::first-line styles just the first line.
Selectors targeting the same list, different ways
- Home
- About
- Services
- Contact
li— the plain type selector matches every item at once.li:first-child— only the first item. No extra class needed.li:nth-child(even)— the 2nd and 4th. Perfect for striped table rows.li:hover— only the item under the cursor lights up. State-based styling.li::before { content: "★" }— a pseudo-element injects a star before each item that was never in your HTML.
Specificity: who wins a conflict?
When two rules target the same element, the browser picks the winner by specificity. The mental model: count (IDs, classes, tags). An #id beats any number of .classes, which beat any number of tags. If specificity ties, the rule written last wins.
Keep specificity low and flatBeginners "fix" a stubborn style by piling on !important or long #header .nav ul li a chains — which makes the next override even harder, an escalating war. Prefer a single, well-named class. Low, even specificity is what keeps a large stylesheet maintainable.
CSS Variables & Design Systems
Imagine your brand colour appears in 200 places across your stylesheet, and the client asks to change it. Find-and-replace 200 times? No. You define it once as a CSS variable, and change one line. This is how every modern site — and every dark-mode toggle — is built.
Custom properties
You declare a variable (officially a "custom property") with a --name, usually on :root so the whole page can see it, and use it anywhere with var(--name):
:root {
--brand: #b534ff;
--gap: 16px;
}
.btn { background: var(--brand); }
.badge { color: var(--brand); padding: var(--gap); }Change one variable, recolour everything at once
- We define one variable,
--brand. A button and a badge both usevar(--brand). - A third element — a link — points at the same variable. None of them hard-code the colour.
- Now change
--brandto cyan. Watch: every element updates at once, instantly, because they all read from one source. - That single line is your entire theme. Swap the variables and the whole site restyles — which is exactly how a dark-mode toggle works.
The heart of a design systemProfessionals define a small set of variables — brand colours, spacing steps, font sizes, border radius — at the top of the stylesheet, then build everything from them. It keeps a large site perfectly consistent and makes a total re-skin a five-minute job instead of a five-day one.
CSS Animations & Keyframes
Transitions animate a change between two states (like a hover). But sometimes you want continuous, multi-step motion that runs on its own — a loading spinner, a pulsing badge, a bouncing arrow. That's what keyframe animations are for, and they need no JavaScript at all.
Define the frames, then play them
It's two parts. First you define an animation with @keyframes, describing what the element looks like at points along the timeline (0% to 100%, or from/to). Then you attach it to an element with the animation property:
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-30px); }
}
.ball {
animation: bounce 1s ease-in-out infinite;
}Read the animation shorthand: run bounce, over 1 second, with ease-in-out timing, repeating infinitely. You can also set a delay, a direction (alternate), and how many times to run.
Reading a keyframe timeline
- At
0%the ball sits at the bottom — the start of the timeline. - At
50%it's at the top of its arc. The browser smoothly interpolates every frame in between. - At
100%it's back at the bottom — one full cycle complete. - With
infinite, it just keeps looping — a self-running animation, zero JavaScript.
Animate the cheap propertiesFor smooth 60fps motion, animate transform and opacity — the browser can hardware-accelerate them. Animating width, top or margin forces expensive layout recalculation and can stutter. And respect prefers-reduced-motion for users who get motion sickness.
CSS Architecture & BEM
Writing CSS is easy; maintaining it is hard. As your project grows, naming clashes and specificity wars become a nightmare. BEM (Block, Element, Modifier) is a naming convention that solves this by giving your CSS structure and predictability.
The BEM structure
BEM stands for Block, Element, Modifier. It keeps specificity strictly flat and makes your HTML self-documenting.
- Block — The standalone entity that is meaningful on its own (e.g.,
.card,.btn,.navbar). - Element — A part of a block that has no standalone meaning and is semantically tied to its block (e.g.,
.card__title,.navbar__item). Separated by two underscores. - Modifier — A flag on a block or element that changes its appearance or behavior (e.g.,
.card--featured,.btn--large). Separated by two hyphens.
<!-- HTML -->
<div class="card card--dark">
<img class="card__image" src="...">
<h3 class="card__title">Card Title</h3>
<button class="btn btn--primary">Buy</button>
</div>
/* CSS */
.card { border-radius: 8px; }
.card--dark { background: #111; color: white; }
.card__title { font-size: 1.5rem; }
Why not just use .card h3?Because .card h3 increases specificity. If you later want to override the h3 somewhere else, you'll have to fight it. With .card__title, specificity stays flat, making overrides trivial and keeping bugs out of large codebases.
Part 4: Modern Mastery
Explore the cutting edge of CSS. Next-generation features, high-performance animations, accessibility, and utility-first styling.
Modern CSS: Nesting, :has() & Container Queries
CSS is evolving faster than ever. For years, developers relied on preprocessors like Sass for basic features, but today, browsers support powerful capabilities natively.
Native CSS Nesting
You can now nest selectors directly inside each other, keeping related styles neatly organized without needing Sass:
.navbar {
background: #333;
& .nav-item { color: white; }
&:hover { background: #444; }
}The :has() Parent Selector
The :has() pseudo-class is the "parent selector" developers begged for for decades. It lets you style an element based on its descendants.
/* Style the card ONLY if it contains an image */
.card:has(img) {
padding: 0;
overflow: hidden;
}
/* Style the label if the checkbox next to it is checked */
label:has(+ input[type="checkbox"]:checked) {
color: green;
}Container Queries
Media queries adapt to the viewport size. Container queries (@container) adapt an element based on the size of its parent container. This is revolutionary for reusable components like cards that look different depending on whether they are in a wide sidebar or a narrow grid column.
Cascade Layers (@layer)
For large projects, managing specificity can become chaotic. Cascade Layers give you explicit control over which styles override others, regardless of specificity.
@layer reset, base, components, utilities;
@layer utilities {
.text-red { color: red; } /* Always wins because it's defined last in the order */
}
@layer components {
.card .title#main-title { color: blue; } /* Highly specific, but loses to utilities! */
}The future is already hereFeatures like :has(), container queries, native nesting, and cascade layers are now supported in all major modern browsers. You can start building leaner, smarter style sheets today.
CSS Accessibility (a11y) & Preferences
Great CSS doesn't just look good; it adapts to the user's needs. Browsers provide media queries that let your styles respond to system-level preferences.
Respecting User Preferences
You can use special media queries to check how the user has configured their operating system:
@media (prefers-color-scheme: dark)— Trigger dark mode styles if the user's OS is in dark mode. This is often paired with CSS variables to flip themes automatically.@media (prefers-reduced-motion: reduce)— Turn off or slow down decorative animations for users who experience motion sickness or vestibular disorders.@media (prefers-contrast: more)— Increase borders and colour contrasts to aid legibility.
Focus States
Never remove outlines from focused elements (outline: none;) unless you replace them with your own distinct focus styling (like a customized box-shadow). Users navigating with a keyboard rely entirely on the focus ring to know where they are on the page. The :focus-visible pseudo-class applies focus styles only when navigating via keyboard, keeping things clean for mouse users while staying accessible.
button:focus-visible {
outline: 3px solid #00e5ff;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}Advanced Typography & Fluid Sizing
Text isn't just about picking a font. Advanced typography ensures your text scales perfectly on any device, remains highly readable, and uses modern features like Variable Fonts and fluid sizing.
Fluid Typography with clamp()
Instead of writing multiple media queries to change font sizes for mobile, tablet, and desktop, you can use the CSS clamp() function. It takes three values: a minimum size, an ideal size (usually using viewport units like vw), and a maximum size.
h1 {
/* Min: 2rem, Ideal: 5vw, Max: 4rem */
font-size: clamp(2rem, 5vw, 4rem);
}This single line ensures your heading is never smaller than 2rem, never larger than 4rem, and fluidly scales with the screen size in between.
Variable Fonts
Traditionally, if you wanted light, regular, and bold weights, you had to download three separate font files. Variable fonts pack every weight, width, and slant into a single highly optimized file. You control them using font-variation-settings or standard properties like font-weight with exact numbers (e.g., 450).
Line Height and MeasureFor optimal readability, limit line lengths (the "measure") to 60-75 characters using max-width: 65ch;. Set your line-height to around 1.5 to 1.7 for body text to give the lines breathing room.
3D Transforms, Filters & Glassmorphism
Modern CSS can manipulate elements in true 3D space, apply Photoshop-style filters, and create the stunning frosted glass effects seen in high-end UI design.
Entering the 3D Space
By default, transform works in 2D (X and Y axes). To unlock 3D, you add perspective to the parent container, and then use rotateX, rotateY, or translateZ on the child.
.scene {
perspective: 1000px; /* Creates depth */
}
.card {
transform: rotateY(45deg); /* Flips it in 3D */
transform-style: preserve-3d;
}Filters & Backdrop Filters
The filter property manipulates the element itself (blur, brightness, contrast, drop-shadow). But backdrop-filter is where the magic happens: it blurs the elements behind it, creating the famous glassmorphism effect.
Glassmorphism in Action
- We start with floating colorful shapes in the background, but our card is completely invisible.
- First, we add a semi-transparent white
background: rgba(255, 255, 255, 0.1). It's visible, but flat. - The magic:
backdrop-filter: blur(10px). The shapes behind the card instantly blur, creating the frosted glass effect. - Finally, a subtle white
borderand a softbox-shadowgive it defined edges and depth.
Advanced Grid & Subgrid
Once you master basic Grid, you'll encounter a common problem: aligning nested grids. Subgrid is the ultimate solution that allows child elements to participate in their parent's grid, perfectly aligning complex UI components across the page.
The Subgrid Magic
Imagine a row of cards where each card has a header, an image, and a footer. Normally, if one header is taller than the others, the content below it won't align across cards. By using grid-template-rows: subgrid;, the internal contents of the cards lock into the master grid of the parent container.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-auto-rows: auto 1fr auto; /* Header, Content, Footer */
}
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid; /* Inherit the rows from .card-grid */
}Scroll-Driven Animations
Historically, tying animations to the user's scroll position required heavy JavaScript libraries like GSAP or ScrollMagic. Now, CSS can natively link animations directly to the scroll timeline.
animation-timeline
Instead of an animation running based on time (like 1s), you can set its timeline to the scroll progress of the page or a specific container. As the user scrolls down, the animation plays forward; as they scroll up, it reverses.
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(100px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal-on-scroll {
animation: fade-in-up linear;
animation-timeline: view(); /* Linked to element entering the viewport */
animation-range: entry 10% cover 30%;
}CSS Performance Optimization
A beautiful design is useless if it makes the browser lag. Rendering performance is a critical skill for senior developers, ensuring silky smooth 60fps experiences on everything from supercomputers to low-end mobile devices.
The Rendering Pipeline
When you change a CSS property, the browser goes through steps: Layout (calculating sizes/positions) → Paint (drawing pixels) → Composite (putting layers together). Layout is incredibly slow. Composite is lightning fast because it happens on the GPU.
- Avoid animating Layout properties:
width,height,margin,top,left. These cause "layout thrashing". - Only animate Composite properties:
transformandopacity.
The will-change Property
You can tell the browser in advance that an element is going to animate. The browser will promote the element to its own GPU layer, preventing it from repainting the rest of the page when it moves.
.heavy-element {
/* Tells the browser to prepare a GPU layer */
will-change: transform, opacity;
}Don't overuse will-changeEvery GPU layer uses RAM. If you put will-change on too many elements, the browser will run out of memory and the page will crash. Only use it on elements that are actively animating or about to animate.
Utility Classes & Frameworks (Tailwind CSS)
While writing pure CSS (Vanilla CSS) is essential for understanding how the web works, many modern teams build user interfaces using Utility-first Frameworks like Tailwind CSS. These frameworks provide thousands of tiny, single-purpose classes that you compose directly in your HTML.
The Traditional vs. Utility Approach
Traditionally, you invent a class name and write rules in a separate file. With a utility framework, you apply pre-defined classes (like flex, p-4 for padding, bg-blue-500 for background color) to build the UI without leaving your HTML file.
<!-- A fully styled, responsive card built entirely with utilities -->
<div class="max-w-sm rounded-xl overflow-hidden shadow-lg bg-white p-6 hover:scale-105 transition-transform">
<div class="font-bold text-xl mb-2 text-gray-800">The Utility Way</div>
<p class="text-gray-600 text-base">
No separate CSS file needed. Build designs faster by composing primitives.
</p>
</div>Why use utilities?
- No naming fatigue: You stop wasting time inventing names like
.sidebar-outer-wrapper. - Zero context switching: You build the UI entirely inside your HTML/JSX without jumping between files.
- Highly maintainable: Changing the HTML safely removes the style. You never end up with dead CSS code.
Learn CSS first!Tailwind is incredibly powerful, but it relies on a deep understanding of CSS properties. items-center won't make sense if you don't understand Flexbox and align-items. Master vanilla CSS before adopting a framework.
You can style the web now 🎨
Selectors, the box model, colour, Flexbox, Grid and responsive design — you have everything to make a page look truly professional. Next, make it interactive with JavaScript.