Description and Basic Syntax
CSS (Cascading Style Sheets) is a stylesheet language that controls the presentation layer of web documents. It transforms raw HTML structure into visually appealing, responsive, and interactive user interfaces.
Core Principles:
Separation of Concerns: Structure (HTML) vs. Presentation (CSS)
Cascade: Multiple style rules can apply to the same element
Inheritance: Child elements inherit certain properties from parents
Specificity: Determines which styles win when conflicts occur
CSS terminology
Anatomy of a CSS rule
/* Selector { Property: Value; } */
h1 { color: blue; }
/* Complete rule with multiple declarations */
.card {
background-color: white; /* Declaration 1 */
border-radius: 8px; /* Declaration 2 */
box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* Declaration 3 */
}Selectors: Patterns that target HTML elements
Properties: Aspects of elements you want to style (color, size, position)
Values: Settings applied to properties
Declaration: A property-value pair
Rule/Ruleset: Selector(s) + declaration block
How CSS is applied
Three ways to add CSS
<!-- 1. Inline CSS (highest specificity, avoid when possible) -->
<p style="color: red; font-size: 16px;">Inline styled text</p>
<!-- 2. Internal CSS (in <head>) -->
<style>
p { color: blue; }
</style>
<!-- 3. External CSS (recommended) -->
<link rel="stylesheet" href="styles.css">The Cascade and Inheritance
CSS applies styles based on three factors:
Source Order: Later rules override earlier ones
Specificity: More specific selectors win
Importance:
!importantoverrides everything (use sparingly)
/* Cascade example - last rule wins */
p { color: blue; }
p { color: red; } /* This wins */
/* Inheritance example */
body {
font-family: Arial, sans-serif; /* Inherited by all text */
color: #333; /* Inherited */
border: 1px solid black; /* NOT inherited */
}
/* Force inheritance */
.child {
color: inherit; /* Explicitly inherit parent's color */
}When two rules target the same element with the same property, the last one in your code wins—this is why it's called "cascading." Understanding this helps you debug why certain styles aren't appearing as expected.
CSS Selectors
Basic Selectors
/* Universal selector */
* {
margin: 0;
padding: 0;
}
/* Type/Element selector */
p { line-height: 1.6; }
/* Class selector */
.highlight { background-color: yellow; }
/* ID selector */
#header { position: fixed; }
/* Attribute selectors */
input[type="text"] { border: 1px solid #ccc; }
a[href^="https"] { color: green; } /* Starts with */
a[href$=".pdf"] { color: red; } /* Ends with */
a[href*="google"] { color: blue; } /* Contains */Selectors are patterns that tell CSS which HTML elements to target—from very broad (the * universal selector hits everything) to very specific (IDs target a single unique element). Attribute selectors let you style elements based on their HTML attributes, like styling all links that go to PDFs differently.
Pseudo-Classes and Pseudo-Elements
/* Pseudo-classes (state) */
a:hover { color: red; }
a:active { color: darkred; }
a:visited { color: purple; }
input:focus { outline: 2px solid blue; }
li:first-child { font-weight: bold; }
li:last-child { margin-bottom: 0; }
li:nth-child(2n) { background: #f0f0f0; } /* Even items */
li:nth-child(odd) { background: white; } /* Odd items */
input:disabled { opacity: 0.5; }
input:checked + label { color: green; }
div:empty { display: none; }
/* Pseudo-elements (create elements) */
p::before { content: "→ "; }
p::after { content: " ←"; }
p::first-line { font-weight: bold; }
p::first-letter { font-size: 2em; }
input::placeholder { color: #999; }
::selection { background: yellow; }Pseudo-classes target elements in specific states or positions, like :hover for when someone moves their mouse over something, or :nth-child(2n) for every even item in a list. These let you create interactive effects and pattern-based styling without adding extra classes to your HTML.
Pseudo-elements let you style specific parts of elements (like ::first-line) or even inject content (::before and ::after) without adding extra HTML. They're powerful for decorative elements and typography effects that would otherwise require cluttering your markup.
Advanced Selectors
/* Descendant combinator (space) */
article p { margin-bottom: 1em; }
/* Child combinator (>) */
ul > li { list-style: none; }
/* Adjacent sibling (+) */
h1 + p { font-size: 1.2em; } /* First p after h1 */
/* General sibling (~) */
h1 ~ p { color: gray; } /* All p siblings after h1 */
/* Multiple selectors (,) */
h1, h2, h3 { font-family: 'Georgia', serif; }
/* Combining selectors */
div.container > ul.menu li:hover a {
color: blue;
}
/* :not() pseudo-class */
input:not([type="submit"]) {
width: 100%;
}
/* :has() - Parent selector (modern browsers) */
div:has(> img) {
padding: 20px; /* Divs containing images */
}Combinators describe relationships between elements—a space means "anywhere inside," > means "direct child only," + means "immediately after," and ~ means "any sibling after."
Specificity calculation
Specificity determines which styles apply when multiple rules target the same element.
Specificity Hierarchy (0-0-0-0)
/* Inline styles: 1-0-0-0 (1000 points) */
/* IDs: 0-1-0-0 (100 points) */
/* Classes/pseudo: 0-0-1-0 (10 points) */
/* Elements: 0-0-0-1 (1 point) */
/* Examples with calculated specificity: */
p /* 0-0-0-1 = 1 */
.class /* 0-0-1-0 = 10 */
#id /* 0-1-0-0 = 100 */
p.class /* 0-0-1-1 = 11 */
#id .class p /* 0-1-1-1 = 111 */
#id #anotherId /* 0-2-0-0 = 200 */
style="..." /* 1-0-0-0 = 1000 */
/* !important overrides everything (avoid!) */
p { color: red !important; }CSS uses a point system to determine which rule wins when multiple rules target the same element—inline styles beat everything, IDs are stronger than classes, classes are stronger than element selectors. This hierarchy prevents chaos when you have conflicting styles, though it can be frustrating until you understand it.

The Box Model
Every element is a rectangular box with content, padding, border, and margin:

Box Model properties
.box {
/* Content dimensions */
width: 300px;
height: 200px;
/* Padding (inside border) */
padding: 20px; /* All sides */
padding: 10px 20px; /* Vertical | Horizontal */
padding: 10px 20px 30px; /* Top | Horizontal | Bottom */
padding: 10px 20px 30px 40px; /* Top | Right | Bottom | Left */
/* Border */
border: 2px solid #333;
border-radius: 8px;
/* Margin (outside border) */
margin: 20px auto; /* Center horizontally */
/* Box sizing */
box-sizing: border-box; /* Include padding/border in width */
}
/* Global box-sizing reset (recommended) */
*, *::before, *::after {
box-sizing: border-box;
}Every element has an onion-like structure: content in the center, padding around that, a border around the padding, and margin pushing other elements away on the outside. The shorthand syntax lets you set values for all four sides at once, using patterns like padding: 10px 20px (vertical, then horizontal).
Understanding box-sizing
/* Default (content-box) */
.content-box {
box-sizing: content-box;
width: 300px;
padding: 20px;
border: 10px solid;
/* Total width = 300 + (20*2) + (10*2) = 360px */
}
/* Border-box (recommended) */
.border-box {
box-sizing: border-box;
width: 300px;
padding: 20px;
border: 10px solid;
/* Total width = 300px (padding/border included) */
}By default, when you set width to 300px and add 20px padding, your element actually becomes 340px wide—confusing! Setting box-sizing: border-box makes width include padding and border, so 300px stays 300px, making layouts much more predictable.
Display property
Controls how elements behave in the document flow:
/* Common display values */
.element {
display: block; /* Full width, new line */
display: inline; /* Inline with text */
display: inline-block; /* Inline but respects width/height */
display: none; /* Hidden, removed from flow */
display: flex; /* Flexbox container */
display: grid; /* Grid container */
display: inline-flex; /* Inline flexbox */
display: inline-grid; /* Inline grid */
}The display property fundamentally changes how an element behaves: block elements take up the full width and start new lines (like paragraphs), inline elements flow with text (like links), and flex/grid unlock powerful layout systems. Choosing the right display value is the foundation of any layout.
Modern layout systems
Flexbox (one-dimensional layout)
/* Flex container */
.flex-container {
display: flex;
flex-direction: row; /* row | column | row-reverse | column-reverse */
justify-content: center; /* Main axis: flex-start | center | space-between | space-around | space-evenly */
align-items: center; /* Cross axis: stretch | center | flex-start | flex-end | baseline */
flex-wrap: wrap; /* nowrap | wrap | wrap-reverse */
gap: 20px; /* Space between items */
}
/* Flex items */
.flex-item {
flex: 1; /* Grow to fill space */
flex-basis: 200px; /* Initial size */
flex-grow: 1; /* Growth factor */
flex-shrink: 0; /* Shrink factor */
align-self: flex-start; /* Override align-items */
order: 2; /* Display order */
}
/* Common flexbox patterns */
/* Center everything */
.center {
display: flex;
justify-content: center;
align-items: center;
}
/* Navbar */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}
/* Card layout */
.card-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
}Flex container: Flexbox is perfect for one-dimensional layouts (either a row or column) and gives you superpowers for alignment: justify-content controls spacing along your main direction, align-items controls the perpendicular direction, and gap adds consistent spacing between items without margin math.
Flex items: Items inside a flex container can grow (flex-grow: 1) to fill available space, shrink when needed, or stay a specific size. The shorthand flex: 1 tells an item "take up equal space with your siblings," which is incredibly useful for responsive layouts.
Common flexbox patterns: These are real-world solutions you'll use constantly: centering anything with just two properties, creating navigation bars with space between items, and making responsive card grids that automatically wrap. Memorizing these patterns will speed up your development significantly.
CSS Grid (two-dimensional layout)
/* Grid container */
.grid-container {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* Three columns */
grid-template-columns: repeat(3, 1fr); /* Three equal columns */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); /* Responsive */
grid-template-rows: 100px auto 50px; /* Three rows */
gap: 20px; /* Row and column gap */
grid-template-areas: /* Named areas */
"header header header"
"sidebar main main"
"footer footer footer";
}
/* Grid items */
.grid-item {
grid-column: 1 / 3; /* Span columns 1-2 */
grid-row: 2 / 4; /* Span rows 2-3 */
grid-area: header; /* Use named area */
place-self: center; /* Center in cell */
}
/* Responsive grid example */
.responsive-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}Grid container: Grid is the most powerful CSS layout system, letting you define both rows and columns at once. The fr unit means "fraction of available space," so 1fr 2fr 1fr creates three columns where the middle one is twice as wide as the others, and repeat(auto-fit, minmax(200px, 1fr)) creates a responsive grid that automatically adjusts column count.
Grid items: Grid items can span multiple cells using row/column numbers, or you can name areas in your grid template and assign items to them by name (much more readable). This level of control makes complex layouts straightforward instead of hack-y.
Responsive grid example: The auto-fill pattern with minmax is magic: it automatically creates as many columns as will fit (minimum 250px each), and those columns grow to fill space. This gives you responsive layouts without writing media queries.
Positioning
/* Position values */
.element {
position: static; /* Default - normal flow */
position: relative; /* Relative to normal position */
position: absolute; /* Relative to positioned parent */
position: fixed; /* Relative to viewport */
position: sticky; /* Hybrid relative/fixed */
}
/* Positioning properties */
.positioned {
position: absolute;
top: 20px;
right: 20px;
bottom: auto;
left: auto;
z-index: 10; /* Stacking order */
}
/* Common patterns */
/* Center absolutely positioned element */
.center-absolute {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* Sticky header */
.sticky-header {
position: sticky;
top: 0;
z-index: 100;
background: white;
}
/* Overlay */
.overlay {
position: fixed;
inset: 0; /* top: 0; right: 0; bottom: 0; left: 0; */
background: rgba(0, 0, 0, 0.5);
}Position values: Positioning removes elements from normal document flow: relative lets you nudge an element from where it would normally be, absolute positions relative to the nearest positioned ancestor, fixed sticks to the viewport (like sticky headers), and sticky is a hybrid that acts relative until you scroll past it, then becomes fixed.
Common patterns: These solve frequent design challenges: centering with absolute positioning uses a trick where you move the element's top-left corner to the center, then shift it back by half its size with transform. The inset: 0 shorthand is cleaner than writing all four directions separately for overlays.
CSS custom properties (variables)
/* Define variables */
:root {
/* Colors */
--primary-color: #007bff;
--secondary-color: #6c757d;
--text-color: #333;
--bg-color: #f8f9fa;
/* Spacing */
--spacing-unit: 8px;
--spacing-small: calc(var(--spacing-unit) * 2);
--spacing-medium: calc(var(--spacing-unit) * 3);
--spacing-large: calc(var(--spacing-unit) * 4);
/* Typography */
--font-family: 'Inter', -apple-system, sans-serif;
--font-size-base: 16px;
--line-height: 1.5;
}
/* Use variables */
.button {
background: var(--primary-color);
padding: var(--spacing-small) var(--spacing-medium);
font-family: var(--font-family);
color: var(--text-color, black); /* Fallback value */
}
/* Change variables dynamically */
.dark-theme {
--text-color: #f0f0f0;
--bg-color: #1a1a1a;
}
/* Scoped variables */
.card {
--card-padding: 20px;
padding: var(--card-padding);
}Define variables: CSS variables (custom properties) let you store values like colors and spacing in one place, then reuse them throughout your stylesheet. They're defined with --name syntax and used with var(--name), making it easy to maintain consistent design systems and create theme switching.
Use variables and dynamic changes: Variables can reference other variables with calc(), creating mathematical relationships in your design. The real power is that variables can be scoped to elements or changed dynamically, so you can create dark themes just by redefining color variables at the root level.
Responsive Design
Media Queries
/* Mobile-first approach (recommended) */
/* Base styles for mobile */
.container {
width: 100%;
padding: 15px;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
max-width: 750px;
margin: 0 auto;
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.container {
max-width: 960px;
}
}
/* Large desktop */
@media (min-width: 1200px) {
.container {
max-width: 1140px;
}
}
/* Other media query features */
@media (orientation: landscape) { }
@media (prefers-color-scheme: dark) { }
@media (prefers-reduced-motion: reduce) { }
@media print { }
/* Container queries (modern browsers) */
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
}
}Mobile-first approach: Modern responsive design starts with mobile styles as the base, then uses min-width media queries to progressively enhance for larger screens. This approach ensures your site works on small screens first (where most traffic comes from), then adds complexity for devices that can handle it.
Other media query features: Media queries can respond to more than just screen width: orientation detects if the device is rotated, prefers-color-scheme respects system dark mode settings, and prefers-reduced-motion helps users who find animations uncomfortable. Container queries are newer and let elements respond to their container's size instead of the viewport.
Responsive Units
/* Relative units */
.text {
font-size: 1rem; /* Relative to root font-size */
padding: 1em; /* Relative to current font-size */
width: 50%; /* Percentage of parent */
height: 100vh; /* Viewport height */
max-width: 90vw; /* Viewport width */
margin: 5vmin; /* Smaller of vw or vh */
font-size: clamp(1rem, 2.5vw, 2rem); /* Responsive clamping */
}
/* Modern responsive typography */
:root {
font-size: clamp(14px, 2vw, 18px);
}
h1 {
font-size: clamp(1.5rem, 4vw, 3rem);
}Viewport units (vh, vw) size things relative to the browser window, rem sizes relative to the root font size (making everything scalable), and clamp() sets a responsive value with minimum and maximum bounds. The clamp() function is particularly powerful for fluid typography that grows with screen size but never gets too small or too large.
Transforms and Transitions
/* Transforms */
.transform {
transform: translate(50px, 100px);
transform: translateX(-50%);
transform: rotate(45deg);
transform: scale(1.5);
transform: skew(10deg, 20deg);
transform: rotate(45deg) scale(1.2) translateX(100px); /* Multiple */
transform-origin: center; /* Transform anchor point */
}
/* Transitions */
.transition {
transition: all 0.3s ease;
transition: opacity 0.3s, transform 0.3s;
transition-timing-function: ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier();
}
/* Common transition pattern */
.button {
background: blue;
transform: translateY(0);
transition: all 0.3s ease;
}
.button:hover {
background: darkblue;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}Transforms: Transforms move, rotate, scale, or skew elements without affecting document flow, making them perfect for animations. You can chain multiple transforms together in one property, and transform-origin lets you control the pivot point (useful for rotating things around corners instead of centers).
Common transition pattern: Transitions animate changes between states smoothly—instead of a button instantly changing color when hovered, it fades over 0.3s. This pattern of setting transition on the base state and different values on :hover is the foundation of interactive UI feedback that feels polished.
Animations
/* Define keyframes */
@keyframes slideIn {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Apply animation */
.animated {
animation: slideIn 0.5s ease forwards;
animation-name: slideIn;
animation-duration: 0.5s;
animation-timing-function: ease;
animation-delay: 0.2s;
animation-iteration-count: 1; /* or infinite */
animation-direction: normal; /* or reverse, alternate */
animation-fill-mode: forwards; /* or backwards, both */
}
/* Multiple animations */
.multi-animated {
animation:
slideIn 0.5s ease,
fadeIn 0.3s ease 0.2s;
}Define keyframes and apply: While transitions animate between two states, animations give you full control over multiple steps in the sequence using keyframes. You define what happens at different points (from/to or percentages), then apply the animation to elements with control over duration, timing, delays, and whether it repeats or plays in reverse.
Modern CSS features
CSS Logical Properties
/* Physical properties (old way) */
.old {
margin-left: 20px;
padding-right: 10px;
}
/* Logical properties (internationalization-friendly) */
.modern {
margin-inline-start: 20px; /* left in LTR, right in RTL */
padding-inline-end: 10px; /* right in LTR, left in RTL */
border-block-start: 1px solid; /* top */
border-block-end: 1px solid; /* bottom */
inline-size: 100%; /* width */
block-size: 50px; /* height */
}Logical properties use "start/end" instead of "left/right," making your CSS automatically work for languages that read right-to-left (like Arabic or Hebrew). This internationalization-friendly approach means you write code once that works globally, rather than maintaining separate stylesheets for different text directions.
Aspect ratio
/* Maintain aspect ratio */
.video-container {
aspect-ratio: 16 / 9;
width: 100%;
background: black;
}
.square {
aspect-ratio: 1; /* 1:1 ratio */
}The aspect-ratio property maintains proportional width-to-height relationships as elements resize, which used to require padding hacks. Now you can simply say "keep this video container at 16:9 no matter the screen size" with one line of code.
CSS functions
/* Calculations */
.calculated {
width: calc(100% - 40px);
height: calc(100vh - var(--header-height));
padding: calc(var(--spacing) * 2);
}
/* Min, Max, Clamp */
.responsive {
width: min(90%, 1200px);
height: max(300px, 50vh);
font-size: clamp(14px, 2vw, 20px);
}
/* CSS Filters */
.filtered {
filter: blur(5px);
filter: brightness(1.2);
filter: contrast(1.5);
filter: grayscale(100%);
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
}CSS functions perform calculations and comparisons: calc() does math with mixed units, min()/max() pick the smaller/larger value, and clamp() sets a value with boundaries. These make your CSS truly responsive and adaptive without JavaScript.
CSS Architecture Patterns
BEM (Block Element Modifier)
/* Block */
.card { }
/* Element */
.card__header { }
.card__body { }
.card__footer { }
/* Modifier */
.card--featured { }
.card__header--large { }BEM (Block Element Modifier) is a naming convention that makes relationships clear: .card is the component, .card__header is a part of that component, and .card--featured is a variation. This prevents naming conflicts and makes it obvious which styles affect which elements in large projects.
Utility-First (similar to Tailwind)
/* Utility classes */
.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.p-4 { padding: 1rem; }
.mt-2 { margin-top: 0.5rem; }
.text-center { text-align: center; }
.bg-primary { background: var(--primary-color); }Examples of CSS patterns for modern web apps
Loading Spinner
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(0,0,0,0.1);
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}Card Component
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 1.5rem;
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}Modal Overlay
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}Last updated
Was this helpful?