CSS is not decoration — it is communication
Every visual decision a website makes — the colour of a button, the size of a heading, the space between lines, the way a card lifts when you hover it — is a CSS decision. CSS is how the web communicates trust, urgency, hierarchy, personality, and professionalism. Writing good CSS is not an artistic skill. It is an engineering skill.
CSS stands for Cascading Style Sheets. The name reveals everything about how it works. Style Sheets — files that describe how elements should look. Cascading — the system by which multiple rules are applied in priority order, with more specific rules overriding less specific ones. Understanding the cascade is the difference between CSS that works and CSS that confuses you for years.
CSS works through a simple mechanism: you write rules. A rule says: "find these elements (the selector), and apply these visual properties (the declarations) to them." That is all CSS is. The complexity comes from selectors being powerful, properties being numerous, and the cascade determining which rule wins when multiple rules target the same element.
Your projects will be judged by how they look before anyone reads the code. A recruiter opening your portfolio project has a visual first impression within 50ms. Google's PageSpeed Insights and Core Web Vitals — which directly affect search rankings — measure CSS rendering performance. Every FAANG company has dedicated CSS architecture at scale. Learning CSS deeply now means your projects look professional, which gets your GitHub clicked, which gets you interviews.
This essay covers the box model (the single most important CSS concept), selectors and specificity, the cascade, colours and typography, responsive design with media queries, transitions and pseudo-classes, and CSS variables. You will apply all of it to your portfolio project from essay 1.1 — watching it transform from a plain document into a professional page.
Phase 1 building block two
CSS from this essay is the foundation for essay 1.3 (Flexbox and Grid for layouts), which is itself the foundation for essay 2.1 (React components — which generate CSS-styled HTML). The concepts you learn here — box model, specificity, the cascade, CSS variables — are used identically in every framework, every component library, and every design system you will encounter for the rest of your career.
The portfolio you style this week will be expanded in every Phase 1 essay. By essay 1.7 (Local Storage), your portfolio will be complete, deployed, and live. CSS variables you define this week will persist through that entire build. The Job Board project (10.1) uses CSS Grid for its layout — built on what you learn here. Build the CSS habit correctly now.
Three analogies — simple to deep
CSS is deceptively simple to start and genuinely deep to master. These three mental models take you from understanding the basics to thinking about CSS the way senior engineers do.
Level 1 — The Tailor
You built a mannequin in essay 1.1 (the HTML skeleton). CSS is the tailor. The tailor looks at the mannequin and applies: fabric colour (background-color), fit (width, height, padding), spacing between items (margin), typography (font-family, font-size), and finishing details (border, border-radius, box-shadow). Just as a tailor works with measurements and proportions, CSS works with sizes, spacing, and visual relationships.
Level 2 — The Priority System
Imagine three people all giving instructions to the same employee: the CEO, the manager, and a colleague. When instructions conflict, the CEO always wins, then the manager, then the colleague. CSS works identically. The cascade has a priority order: inline styles > ID selectors > class selectors > tag selectors. When two rules conflict, the more specific one wins. This system is called specificity — and it is the number one source of CSS confusion for beginners who don't understand it.
Level 3 — Everything is a Box
This is how senior engineers think about CSS. Every single element on every webpage is a rectangular box. Text, images, buttons, divs, paragraphs — all boxes. Each box has four layers: the content (the actual text or image), padding (space between the content and the border), border (the edge of the element), and margin (space outside the border, separating this element from others). This is the CSS Box Model — and understanding it fully explains why spacing behaves the way it does, why elements overlap, and why percentage widths sometimes work unexpectedly.
CSS is like the settings panel for your HTML. HTML said "this is a heading." CSS says "make that heading dark blue, 36px big, bold, with 20px of space above it." You write the instruction once (in a .css file), and every heading on every page follows it automatically. Change one line of CSS and you change every heading everywhere.
Interactive Box Model + Live CSS Playground
The Box Model is the single most important CSS concept to internalise. Use the sliders to change each layer and watch the diagram and CSS update in real time.
Now write CSS and see it render live. Pick a preset or write your own:
Now try the specificity calculator — paste any CSS selector and see its exact score:
CSS from first principles — everything that matters
CSS has hundreds of properties. You do not need to memorise them all. You need to understand the core concepts deeply — box model, cascade, specificity, layout, responsive design — and then look up specific properties as you need them.
How CSS is Applied — The Three Methods
There are three ways to apply CSS to HTML, listed from worst to best practice:
1. Inline styles (worst) — CSS written directly in the HTML element's style attribute: <p style="color: red;">. Hard to maintain, mixes structure with presentation, impossible to reuse, highest specificity so it overrides everything.
2. Internal stylesheet (sometimes useful) — CSS written in a <style> tag inside <head>. Good for single-page demos. Not ideal for real projects because styles cannot be shared between HTML files.
3. External stylesheet (correct) — CSS in a separate .css file, linked from HTML with <link rel="stylesheet" href="style.css">. This is how real projects work. One CSS file can style all pages of a website. Change one file; everything updates.
Selectors — Finding the Elements You Want
Specificity — Why Your CSS Sometimes Gets Overridden
When two rules target the same element and set the same property, the browser must decide which one wins. The answer is specificity — a scoring system. Each selector type contributes to a score written as three numbers: IDs — Classes — Tags.
A tag selector p scores 0-0-1. A class selector .card scores 0-1-0. An ID selector #nav scores 1-0-0. Combined selectors add: #nav .link = 1 ID + 1 class = 1-1-0. Higher score always wins. If scores are equal, the rule that appears later in the stylesheet wins — this is the cascade.
!important overrides all specificity calculations. It is a shortcut that causes long-term pain. If you are reaching for !important, you have a specificity problem to fix, not override. The only legitimate use is overriding third-party library styles you cannot change. In your own code: never.
The Box Model — Master This or Struggle Forever
Every element is a box with four layers. The total rendered width of an element by default is: content width + left padding + right padding + left border + right border + left margin + right margin. This surprises beginners who set width: 200px and get something wider.
The fix: box-sizing: border-box. With this declaration, the width property includes padding and border — so width: 200px means the element is exactly 200px wide, regardless of padding and border. Always apply this globally:
/* The universal reset — every professional CSS file starts with this */ *, *::before, *::after { box-sizing: border-box; /* width includes padding and border */ margin: 0; /* remove browser default margins */ padding: 0; /* remove browser default padding */ } /* Base body styles */ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 16px; /* base size — all rem values are relative to this */ line-height: 1.6; /* comfortable reading line height */ color: #1a1a2e; /* near-black text — not pure black (too harsh) */ background-color: #ffffff; } /* Images responsive by default */ img { max-width: 100%; /* images never overflow their container */ display: block; /* removes the small gap below inline images */ }
CSS Variables — Write Once, Change Everywhere
CSS custom properties (variables) let you define a value once and reuse it everywhere. When you need to change your brand colour, you change it in one place instead of hunting through your entire stylesheet. This is how every professional design system works.
/* :root targets the html element — the highest scope */ /* Variables defined here are available everywhere in the document */ :root { /* Colours */ --colour-primary: #7c3aed; /* your main brand colour */ --colour-primary-dark: #5b21b6; --colour-text: #1a1a2e; --colour-text-muted: #6b7280; --colour-bg: #ffffff; --colour-surface: #f9fafb; --colour-border: #e5e7eb; /* Typography */ --font-size-sm: 0.875rem; /* 14px */ --font-size-base: 1rem; /* 16px */ --font-size-lg: 1.125rem; /* 18px */ --font-size-xl: 1.25rem; /* 20px */ --font-size-2xl: 1.5rem; /* 24px */ --font-size-4xl: 2.25rem; /* 36px */ /* Spacing scale */ --space-1: 0.25rem; /* 4px */ --space-2: 0.5rem; /* 8px */ --space-4: 1rem; /* 16px */ --space-8: 2rem; /* 32px */ --space-16: 4rem; /* 64px */ /* Border radius */ --radius-sm: 4px; --radius-md: 8px; --radius-lg: 16px; --radius-full: 9999px; /* pill shape */ /* Shadows */ --shadow-sm: 0 1px 3px rgba(0,0,0,0.1); --shadow-md: 0 4px 12px rgba(0,0,0,0.12); --shadow-lg: 0 8px 30px rgba(0,0,0,0.15); } /* Using variables: */ .button { background-color: var(--colour-primary); border-radius: var(--radius-md); padding: var(--space-2) var(--space-4); font-size: var(--font-size-base); box-shadow: var(--shadow-sm); } /* Dark mode — just swap variable values */ @media (prefers-color-scheme: dark) { :root { --colour-bg: #0f172a; --colour-text: #e2e8f0; --colour-surface: #1e293b; } /* Every element using variables now has dark mode — no other changes needed */ }
Responsive Design — Making Pages Work on Every Screen
Responsive design means your layout adapts to different screen sizes. The core tool is media queries — CSS rules that only apply when the browser window is a certain width. The modern approach is mobile-first: write CSS for mobile screens by default, then add media queries to enhance the layout for larger screens.
/* Mobile first — default styles apply to mobile (smallest screens) */ .container { max-width: 1200px; margin: 0 auto; /* centres the container */ padding: 0 1rem; /* side padding so content doesn't touch edges */ } .projects-grid { display: grid; grid-template-columns: 1fr; /* 1 column on mobile */ gap: 1.5rem; } /* Tablet: screens 640px and wider */ @media (min-width: 640px) { .projects-grid { grid-template-columns: repeat(2, 1fr); /* 2 columns */ } } /* Desktop: screens 1024px and wider */ @media (min-width: 1024px) { .projects-grid { grid-template-columns: repeat(3, 1fr); /* 3 columns */ } .container { padding: 0 2rem; } } /* Common breakpoints used in the industry: */ /* sm: 640px, md: 768px, lg: 1024px, xl: 1280px, 2xl: 1536px */ /* (These match Tailwind CSS's defaults — you'll use Tailwind in Phase 2+) */
Transitions and Hover States — The Polish That Matters
Transitions make state changes smooth instead of instant. A button that changes colour on hover instantly feels cheap. The same button that transitions over 200ms feels professional. This is a small detail that enormously affects perceived quality.
.btn { background-color: var(--colour-primary); color: white; padding: 0.75rem 1.5rem; border: none; border-radius: var(--radius-md); cursor: pointer; /* transition: property duration easing */ /* 'all' means every animatable property transitions */ /* 0.2s is fast enough to feel snappy, slow enough to be visible */ transition: all 0.2s ease; } .btn:hover { background-color: var(--colour-primary-dark); transform: translateY(-2px); /* subtle lift effect */ box-shadow: var(--shadow-md); } .btn:active { transform: translateY(0); /* press back down on click */ box-shadow: var(--shadow-sm); } /* Card hover: subtle lift */ .card { border: 1px solid var(--colour-border); border-radius: var(--radius-lg); padding: var(--space-8); transition: transform 0.2s, box-shadow 0.2s; } .card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
Style your portfolio — the complete CSS file
Create style.css in your portfolio folder and link it from index.html. Build this file section by section, understanding every property before you write it.
First, link the stylesheet. In your index.html <head>, add:
<!-- Add this inside <head>, after the meta tags --> <link rel="stylesheet" href="css/style.css"> <!-- Import Google Fonts (optional but recommended) --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
Now build the complete CSS file. Every section is explained:
/* ═══════════════════════════════════════ SECTION 1 — DESIGN TOKENS (variables) Change these to change your entire site ═══════════════════════════════════════ */ :root { --primary: #7c3aed; --primary-dark: #5b21b6; --text: #111827; --text-muted: #6b7280; --bg: #ffffff; --surface: #f3f4f6; --border: #e5e7eb; --font: 'Inter', -apple-system, sans-serif; --radius: 12px; --shadow: 0 4px 20px rgba(0,0,0,0.08); --max-width: 1100px; } /* ═══════════════════════════════════════ SECTION 2 — RESET ═══════════════════════════════════════ */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } img { max-width: 100%; display: block; } a { color: inherit; text-decoration: none; } /* ═══════════════════════════════════════ SECTION 3 — BASE TYPOGRAPHY ═══════════════════════════════════════ */ body { font-family: var(--font); font-size: 1rem; line-height: 1.7; color: var(--text); background: var(--bg); } h1, h2, h3, h4 { line-height: 1.2; font-weight: 700; color: var(--text); } h1 { font-size: clamp(2rem, 5vw, 3.5rem); } /* clamp(min, preferred, max) */ h2 { font-size: clamp(1.5rem, 3vw, 2.25rem); } h3 { font-size: 1.25rem; } p { color: var(--text-muted); max-width: 65ch; } /* ch = width of '0'. 65ch is ideal line length */ /* ═══════════════════════════════════════ SECTION 4 — LAYOUT UTILITIES ═══════════════════════════════════════ */ .container { max-width: var(--max-width); margin: 0 auto; padding: 0 1.5rem; } /* ═══════════════════════════════════════ SECTION 5 — NAVIGATION ═══════════════════════════════════════ */ header { position: sticky; /* sticks to top when scrolling */ top: 0; background: rgba(255,255,255,0.9); backdrop-filter: blur(10px); /* frosted glass effect */ border-bottom: 1px solid var(--border); z-index: 100; } nav { max-width: var(--max-width); margin: 0 auto; padding: 1rem 1.5rem; display: flex; /* Flexbox! Full coverage in essay 1.3 */ align-items: center; gap: 2rem; } nav a { font-size: 0.9rem; font-weight: 500; color: var(--text-muted); transition: color 0.2s; } nav a:hover { color: var(--primary); } /* ═══════════════════════════════════════ SECTION 6 — HERO (About Section) ═══════════════════════════════════════ */ #about { min-height: 90vh; display: flex; flex-direction: column; justify-content: center; padding: 6rem 1.5rem; max-width: var(--max-width); margin: 0 auto; } #about h1 span { color: var(--primary); } /* highlight name */ #about p { font-size: 1.2rem; margin-top: 1rem; } /* ═══════════════════════════════════════ SECTION 7 — PROJECTS ═══════════════════════════════════════ */ #projects { padding: 5rem 1.5rem; max-width: var(--max-width); margin: 0 auto; } #projects h2 { margin-bottom: 2.5rem; } article { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 2rem; margin-bottom: 1.5rem; transition: transform 0.2s, box-shadow 0.2s; } article:hover { transform: translateY(-4px); box-shadow: var(--shadow); } article h3 { margin-bottom: 0.75rem; } article p { margin-bottom: 1.25rem; } article a { display: inline-block; padding: 0.5rem 1rem; border: 1px solid var(--primary); border-radius: 6px; color: var(--primary); font-size: 0.875rem; font-weight: 500; margin-right: 0.75rem; transition: all 0.2s; } article a:hover { background: var(--primary); color: white; } /* ═══════════════════════════════════════ SECTION 8 — CONTACT FORM ═══════════════════════════════════════ */ #contact { padding: 5rem 1.5rem; max-width: 600px; margin: 0 auto; } label { display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: 0.4rem; } input, textarea, select { width: 100%; padding: 0.75rem 1rem; border: 1px solid var(--border); border-radius: 8px; font-family: var(--font); font-size: 1rem; background: var(--bg); transition: border-color 0.2s, box-shadow 0.2s; margin-bottom: 1.25rem; } input:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(124,58,237,0.15); /* focus ring */ } button[type="submit"] { width: 100%; padding: 0.875rem; background: var(--primary); color: white; border: none; border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.2s; } button[type="submit"]:hover { background: var(--primary-dark); transform: translateY(-1px); } /* ═══════════════════════════════════════ SECTION 9 — FOOTER ═══════════════════════════════════════ */ footer { text-align: center; padding: 3rem 1.5rem; border-top: 1px solid var(--border); color: var(--text-muted); font-size: 0.875rem; } /* ═══════════════════════════════════════ SECTION 10 — RESPONSIVE ═══════════════════════════════════════ */ @media (max-width: 640px) { nav { gap: 1rem; flex-wrap: wrap; } #about { padding: 4rem 1rem; } }
After adding this CSS, open your portfolio with Live Server. The transformation is dramatic — you now have a sticky navigation with frosted glass effect, a hero section centred on screen, styled project cards that lift on hover, a beautiful form with focus states, and a clean footer. This is a real portfolio page that can be shown to recruiters.
The CSS errors that trip up beginners for months
Without box-sizing: border-box, adding padding to an element makes it wider than the specified width. A button with width: 200px; padding: 20px will actually render as 240px wide. This confuses every beginner who has ever tried to make two elements fit side by side. Always apply the universal reset at the top of every CSS file you write. Make it a reflex.
/* ALWAYS at the top of your CSS file */ *, *::before, *::after { box-sizing: border-box; }
Using px for font sizes breaks accessibility. If a user has set their browser's default font size to 20px (because they have difficulty reading small text), px values ignore this — the text stays at whatever pixel size you coded. rem values are relative to the browser's base font size and respect user preferences. Use px for borders, shadows, and fixed-size elements. Use rem for all typography and spacing that should scale with user preferences. Standard: 1rem = 16px at default browser settings.
When a CSS rule isn't applying, beginners add !important. Then they add it again for something else. Then they use it on three different rules targeting the same element and wonder why nothing works. !important breaks the cascade entirely. The correct solution is to understand specificity and write selectors with the appropriate level of specificity. If your rule isn't applying, open DevTools, click the element, look at the computed styles panel, and find which rule is winning and why.
/* WRONG: fighting specificity with !important */ .button { background: blue !important; } /* CORRECT: understand why it's not applying and fix the selector */ .hero .button { background: blue; } /* more specific than just .button */
Most tutorials show desktop CSS first. But more than 60% of web traffic globally is on mobile devices. If you write desktop-first CSS and try to "undo" it for mobile, you spend more time overriding than building. The modern industry standard is mobile-first: write your default CSS for small screens (single column, large tap targets, simplified navigation), then use @media (min-width: ...) to enhance for larger screens. This produces cleaner, less redundant CSS.
Beginners write color: #7c3aed in forty different places. Then a designer says "make the brand colour a bit darker." Now they must find and change forty values. CSS variables (--primary: #7c3aed) mean you change it once. Professional codebases define all design tokens (colours, spacing, typography, border radii, shadows) as CSS variables in :root and never reference raw values anywhere else in the stylesheet. This is the exact approach used at every design-system-driven company.
CSS at Google, Meta, and Amazon — specifically
--md-sys-color-primary. Google engineers do not write raw CSS properties for colour or spacing — they reference tokens, ensuring visual consistency across every product and every platform. Understanding design tokens at this level puts you ahead of most candidates who have only worked with ad-hoc styling.font-weight: bold) are defined once as a class and reused, rather than repeated in every component. The concepts behind this approach — specificity, the cascade, reusable classes — are exactly what you are learning now.CSS questions that separate candidates at FAANG screens
"Explain the CSS Box Model. How does box-sizing: border-box change it?"
width: 200px; padding: 20px; border: 2px solid, the element actually renders 244px wide. With box-sizing: border-box, the width property includes padding and border — so the same element renders exactly 200px wide, with the content area shrinking to accommodate padding and border. Nearly every professional CSS codebase applies border-box globally via the universal selector reset."If two CSS rules target the same element, which one wins? How is this calculated?"
1-0-0 beats 0-99-99 because IDs are compared first. If scores are equal, the rule appearing later in the stylesheet wins — this is the cascade. Inline styles override all stylesheet rules. !important overrides everything including inline styles, but creates maintenance problems and should be avoided. The practical implication: prefer class selectors for most styling, use IDs sparingly, never use !important in your own code."How would you organise CSS for a large application with 50+ components?"
Answer out loud before revealing
"Cascading" refers to the algorithm that determines which CSS rule applies when multiple rules target the same element and property. The cascade has three factors considered in order: (1) Origin — browser defaults are weakest, author styles (your CSS) are medium, user styles (browser overrides set by users) are strong, !important author styles are strongest. (2) Specificity — calculated as a three-number score based on selector composition. Higher score wins. (3) Source order — if two rules have identical specificity, the one appearing later in the stylesheet wins. Understanding the cascade means you can predict exactly which rule will apply without trial and error.
Padding is the space between an element's content and its border — it is inside the element. The background colour fills the padding area. Clicking in the padding area fires click events on the element. Margin is the space outside the border — it pushes other elements away. Background does not extend into margins. The key practical rule: use padding to create breathing room inside a component (space between text and the card edge), use margin to create space between components (distance between two cards). Note: vertical margins between block elements collapse — if one element has margin-bottom: 20px and the next has margin-top: 30px, the space between them is 30px (the larger value), not 50px. This surprises many beginners.
A CSS variable (custom property) stores a value under a name defined with --variable-name in a :root block. It is referenced anywhere with var(--variable-name). The advantage over hardcoded values: when you need to change that value (say, your brand colour), you change it once in :root and it propagates everywhere automatically. With hardcoded values, you must find and change every instance — missing one causes inconsistency. CSS variables also enable theming: defining different variable values for dark mode or different brand themes with just a few overrides. They are also readable: var(--colour-primary) communicates intent better than #7c3aed.
Mobile-first means writing default CSS that works on small screens, then using @media (min-width: ...) queries to add complexity for larger screens. Desktop-first is the reverse — default CSS for large screens, then undoing or changing things for small screens. Mobile-first is recommended for three reasons: (1) Simplicity — mobile layouts are simpler (single column, no complex grids), so starting with the simpler case and adding complexity is easier than starting complex and removing it. (2) Performance — mobile devices are slower and on worse connections; CSS is parsed before rendering. Mobile-first ensures small devices get minimal CSS. (3) User reality — over 60% of global web traffic is mobile; it should be the primary design target, not an afterthought.
position: fixed removes an element from the document flow and fixes it at a specified position on the screen — it stays in place as you scroll and can overlap other content. position: sticky is a hybrid: the element stays in the normal document flow but "sticks" to a specified position (e.g., top: 0) once scrolling would make it go past that point. The navigation bar in the portfolio CSS uses sticky — it scrolls with the page until it would disappear off the top, then sticks there. Unlike fixed, a sticky element cannot overlap content before it triggers the stick point, which makes it easier to use without calculating offsets for the content below it.
Your two-week CSS checklist
What you must own from this essay
box-sizing: border-box makes width include padding and border. Apply it globally. Always.:root. Reference them everywhere. Changing your design system is then one edit, not five hundred.Vocabulary you now own
The two tools that solve every layout problem in modern CSS.