Build a Developer Portfolio That Gets You Hired: The Complete Capstone Guide
html
You know HTML semantics. You can build a Grid layout in your sleep. You understand specificity, stacking contexts, and why border-box saves lives. But here's the gap nobody talks about: hiring managers don't care what you know. They care what you can prove.
A GitHub repo full of tutorial code doesn't prove anything. A portfolio site that looks like every Bootstrap template from 2019 doesn't prove anything. What proves competence is a custom-built portfolio that demonstrates intentional decisions — and your ability to explain every single one of them in an interview.
This module is the bridge. We'll cover what hiring managers actually scan for in your code, the CSS-only challenges that still show up in every junior frontend interview, the questions you'll be asked, and exactly how to talk about your work without sounding like you memorized MDN.
By the end, you'll ship a portfolio that gets callbacks.
Part 1: What Hiring Managers Actually Look For
When a hiring manager opens your portfolio, they spend about 30 seconds deciding if you're worth a closer look. Here's what they're scanning for:
The 30-Second Code Quality Checklist
| Signal | What They Check | Red Flag |
|---|---|---|
| Semantics | View source. Is it <header> or <div class="header">? | Div soup everywhere |
| Accessibility | Tab through the page. Can they navigate without a mouse? | No focus rings, missing labels |
| Responsive | Resize the browser. Does it break at 375px? | Horizontal scroll on mobile |
| Performance | Lighthouse score. Is it under 70? | 2MB hero image, no lazy loading |
| CSS Architecture | Inspect a component. Is it BEM or chaos? | #main .wrapper div p span |
| Modern CSS | Are you using Grid, Flexbox, Custom Properties? | Floats for layout, !important spam |
The Secret: Hiring managers for junior roles aren't looking for perfection. They're looking for intentionality. A broken grid is forgivable if you can explain why you chose Grid over Flexbox. A missing alt tag is fatal if you don't know what an alt tag is.
The Portfolio Sections That Matter
You need five sections. No more, no less:
- Hero — Who you are and what you do in one sentence
- Projects — 3 case studies with live links, GitHub repos, and "what I learned" blurbs
- About — Your story, not your resume. Why frontend? Why now?
- Contact — A working form (even if it posts to Formspree)
- Resume — PDF download, but also an HTML version for accessibility
Part 2: Building the Portfolio Site
Here's the architecture. We're using everything from Modules 1-6.
File Structure
portfolio/
├── index.html
├── about.html
├── contact.html
├── projects/
│ ├── project-1.html
│ └── project-2.html
├── styles/
│ ├── main.scss
│ ├── 1-settings/
│ ├── 2-tools/
│ ├── 3-generic/
│ ├── 4-elements/
│ ├── 5-objects/
│ ├── 6-components/
│ │ ├── _hero.scss
│ │ ├── _project-card.scss
│ │ ├── _nav.scss
│ │ ├── _contact-form.scss
│ │ └── _footer.scss
│ └── 7-utilities/
├── images/
│ ├── profile.jpg
│ ├── projects/
│ │ ├── saas-landing.jpg
│ │ ├── blog-grid.jpg
│ │ └── dashboard.jpg
│ └── resume.pdf
└── js/
└── main.js # Mobile menu toggle only
The Hero Section
1<header class="hero"> 2 <div class="hero__content"> 3 <p class="hero__eyebrow">Frontend Developer</p> 4 <h1 class="hero__title"> 5 I build accessible, performant web experiences that work for everyone. 6 </h1> 7 <p class="hero__description"> 8 Recent graduate of a self-directed HTML & CSS mastery program. 9 Obsessed with semantics, CSS Grid, and Core Web Vitals. 10 </p> 11 <div class="hero__actions"> 12 <a href="#projects" class="button button--primary">View My Work</a> 13 <a href="/resume.pdf" class="button button--outline" download> 14 Download Resume 15 </a> 16 </div> 17 </div> 18 <div class="hero__image"> 19 <img 20 src="images/profile-400.webp" 21 srcset="images/profile-400.webp 400w, images/profile-800.webp 800w" 22 sizes="(max-width: 640px) 80vw, 400px" 23 alt="Alex Chen, frontend developer, smiling in a home office" 24 width="400" 25 height="400" 26 loading="eager" 27 fetchpriority="high"> 28 </div> 29</header>
1// _hero.scss 2.hero { 3 min-height: 100dvh; 4 display: grid; 5 place-items: center; 6 padding: 6rem 1.5rem 3rem; 7 gap: 3rem; 8 9 @media (min-width: 768px) { 10 grid-template-columns: 1fr 1fr; 11 padding: 0 3rem; 12 } 13 14 &__eyebrow { 15 font-size: var(--text-sm); 16 font-weight: 600; 17 text-transform: uppercase; 18 letter-spacing: 0.1em; 19 color: var(--color-primary); 20 margin-bottom: 1rem; 21 } 22 23 &__title { 24 font-size: clamp(2rem, 5vw, 3.5rem); 25 font-weight: 800; 26 line-height: 1.1; 27 letter-spacing: -0.02em; 28 max-width: 20ch; 29 margin-bottom: 1.5rem; 30 } 31 32 &__description { 33 font-size: clamp(1rem, 1.5vw, 1.25rem); 34 color: var(--color-text-muted); 35 max-width: 50ch; 36 margin-bottom: 2rem; 37 } 38 39 &__actions { 40 display: flex; 41 flex-wrap: wrap; 42 gap: 1rem; 43 } 44 45 &__image { 46 justify-self: center; 47 48 img { 49 width: min(300px, 80vw); 50 height: auto; 51 border-radius: 50%; 52 aspect-ratio: 1; 53 object-fit: cover; 54 border: 4px solid var(--color-surface); 55 box-shadow: 0 20px 60px var(--shadow-color); 56 } 57 } 58}
The Project Grid
1<section id="projects" class="projects"> 2 <div class="container"> 3 <h2 class="section-title">Featured Projects</h2> 4 5 <div class="projects__grid"> 6 7 <article class="project-card"> 8 <div class="project-card__image"> 9 <img 10 src="images/projects/saas-landing.webp" 11 alt="SaaS analytics dashboard landing page with dark mode support" 12 width="800" 13 height="500" 14 loading="lazy"> 15 </div> 16 <div class="project-card__content"> 17 <h3 class="project-card__title">SaaS Analytics Landing</h3> 18 <p class="project-card__description"> 19 A fully responsive landing page built with SCSS, Vite, and modern CSS features. 20 Achieved 96 Lighthouse performance score with AVIF images and critical CSS. 21 </p> 22 <ul class="project-card__tags"> 23 <li>SCSS</li> 24 <li>Vite</li> 25 <li>CSS Grid</li> 26 <li>AVIF</li> 27 </ul> 28 <div class="project-card__links"> 29 <a href="https://saas-demo.netlify.app" target="_blank" rel="noopener noreferrer"> 30 Live Site 31 </a> 32 <a href="https://github.com/yourname/saas-landing" target="_blank" rel="noopener noreferrer"> 33 GitHub 34 </a> 35 </div> 36 </div> 37 </article> 38 39 <!-- Repeat for 2 more projects --> 40 41 </div> 42 </div> 43</section>
1// _project-card.scss 2.projects__grid { 3 display: grid; 4 gap: 2rem; 5 6 // Responsive without media queries 7 grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); 8} 9 10.project-card { 11 background: var(--color-surface); 12 border: 1px solid var(--color-border); 13 border-radius: 16px; 14 overflow: hidden; 15 transition: transform 0.3s ease, box-shadow 0.3s ease; 16 17 &:hover { 18 transform: translateY(-4px); 19 box-shadow: 0 20px 40px var(--shadow-color); 20 } 21 22 &__image { 23 aspect-ratio: 16 / 10; 24 overflow: hidden; 25 background: var(--color-bg); 26 27 img { 28 width: 100%; 29 height: 100%; 30 object-fit: cover; 31 transition: transform 0.5s ease; 32 } 33 } 34 35 &:hover &__image img { 36 transform: scale(1.05); 37 } 38 39 &__content { 40 padding: 1.5rem; 41 } 42 43 &__title { 44 font-size: var(--text-lg); 45 font-weight: 700; 46 margin-bottom: 0.75rem; 47 } 48 49 &__description { 50 color: var(--color-text-muted); 51 font-size: var(--text-sm); 52 line-height: 1.6; 53 margin-bottom: 1rem; 54 } 55 56 &__tags { 57 display: flex; 58 flex-wrap: wrap; 59 gap: 0.5rem; 60 margin-bottom: 1.25rem; 61 list-style: none; 62 padding: 0; 63 64 li { 65 font-size: var(--text-xs); 66 font-weight: 500; 67 padding: 0.25rem 0.75rem; 68 background: var(--color-bg); 69 border-radius: 999px; 70 color: var(--color-primary); 71 } 72 } 73 74 &__links { 75 display: flex; 76 gap: 1.5rem; 77 78 a { 79 font-size: var(--text-sm); 80 font-weight: 600; 81 text-decoration: none; 82 83 &:hover { 84 text-decoration: underline; 85 } 86 } 87 } 88}
Part 3: CSS-Only Interview Challenges
These three problems appear in almost every junior frontend interview. You should be able to write the solution and explain why it works in under three minutes.
Challenge 1: Center a Div (Yes, Still)
The Question: "How many ways can you center a div horizontally and vertically?"
The 2026 Answer: Grid first, Flexbox second, absolute positioning for legacy.
1/* Method 1: Grid (Modern, 2 lines, best for page layouts) */ 2.center-grid { 3 display: grid; 4 place-items: center; /* Shorthand for justify + align */ 5 min-height: 100vh; 6} 7 8/* Method 2: Flexbox (Best for components) */ 9.center-flex { 10 display: flex; 11 justify-content: center; 12 align-items: center; 13 min-height: 100vh; 14} 15 16/* Method 3: Absolute positioning (When parent isn't flex/grid) */ 17.center-absolute { 18 position: absolute; 19 top: 50%; 20 left: 50%; 21 transform: translate(-50%, -50%); 22} 23 24/* Method 4: Margin auto (Only works if element has defined width/height) */ 25.center-margin { 26 width: 200px; 27 height: 200px; 28 margin: auto; 29}
What to say in the interview:
"I default to Grid with
place-items: centerfor page-level centering because it's the most concise and doesn't require knowing the child's dimensions. For component-level centering, like a button inside a card header, I use Flexbox. I avoid absolute positioning unless I'm positioning a tooltip or modal overlay, because it removes the element from document flow and can cause accessibility issues."
Challenge 2: Responsive Grid Without Media Queries
The Question: "Build a card grid that goes from 1 column on mobile to 3 columns on desktop without using @media."
1.card-grid { 2 display: grid; 3 /* Each column is at least 280px, max 1fr */ 4 /* Auto-fit creates as many columns as fit */ 5 grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); 6 gap: 1.5rem; 7}
What to say:
"I use
repeat(auto-fit, minmax(min(100%, 280px), 1fr)). Themin(100%, 280px)ensures that on very small screens, the column never exceeds the container width.auto-fitcreates as many 280px columns as fit in the available space. This eliminates the need for media queries entirely — the grid adapts fluidly."
Challenge 3: Pure CSS Hamburger Menu
The Question: "Build a mobile hamburger menu without JavaScript."
1<header class="site-header"> 2 <input type="checkbox" id="menu-toggle" class="menu-toggle" aria-label="Toggle navigation"> 3 <label for="menu-toggle" class="menu-button" aria-hidden="true"> 4 <span></span> 5 <span></span> 6 <span></span> 7 </label> 8 9 <nav class="main-nav"> 10 <ul> 11 <li><a href="#projects">Projects</a></li> 12 <li><a href="#about">About</a></li> 13 <li><a href="#contact">Contact</a></li> 14 </ul> 15 </nav> 16</header>
1.menu-toggle { 2 position: absolute; 3 opacity: 0; 4 width: 0; 5 height: 0; 6} 7 8.menu-button { 9 display: flex; 10 flex-direction: column; 11 gap: 5px; 12 cursor: pointer; 13 padding: 0.5rem; 14 z-index: 10; 15 16 span { 17 display: block; 18 width: 24px; 19 height: 2px; 20 background: var(--color-text); 21 transition: transform 0.3s, opacity 0.3s; 22 } 23 24 // Animate to X when checked 25 .menu-toggle:checked ~ & { 26 span:nth-child(1) { transform: translateY(7px) rotate(45deg); } 27 span:nth-child(2) { opacity: 0; } 28 span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); } 29 } 30} 31 32.main-nav { 33 position: fixed; 34 top: 0; 35 right: 0; 36 width: min(300px, 80vw); 37 height: 100dvh; 38 background: var(--color-surface); 39 transform: translateX(100%); 40 transition: transform 0.3s ease; 41 padding: 5rem 2rem; 42 box-shadow: -10px 0 30px var(--shadow-color); 43 44 ul { 45 list-style: none; 46 display: flex; 47 flex-direction: column; 48 gap: 1.5rem; 49 } 50 51 a { 52 font-size: 1.25rem; 53 font-weight: 600; 54 } 55} 56 57// The magic: sibling selector 58.menu-toggle:checked ~ .main-nav { 59 transform: translateX(0); 60} 61 62// Desktop: hide toggle, show nav inline 63@media (min-width: 768px) { 64 .menu-button { display: none; } 65 66 .main-nav { 67 position: static; 68 width: auto; 69 height: auto; 70 transform: none; 71 padding: 0; 72 box-shadow: none; 73 background: transparent; 74 75 ul { 76 flex-direction: row; 77 gap: 2rem; 78 } 79 80 a { 81 font-size: var(--text-base); 82 font-weight: 500; 83 } 84 } 85}
What to say:
"I use the checkbox hack with a hidden
<input type="checkbox">and the sibling selector~. When the checkbox is checked, the nav slides in. The label acts as the visual hamburger button. On desktop, I hide the toggle withdisplay: noneand reset the nav to static positioning. For production, I'd use the Popover API with anchor positioning, but this pure CSS version works everywhere and demonstrates selector logic."
Part 4: Common CSS Interview Questions — With Answers That Get You Hired
Don't just know the answer. Know why the answer matters.
Q1: "Explain the CSS cascade. How does the browser decide which rule wins?"
Bad answer: "Specificity. IDs beat classes, classes beat elements."
Good answer:
"The cascade evaluates four things in order: origin and importance, specificity, source order, and inheritance. First,
!importantin a user stylesheet beats author styles, which beat browser defaults. Second, specificity is calculated as a tuple:(ID selectors, class selectors, element selectors). A single ID beats any number of classes. Third, if specificity is tied, the last declared rule wins. Finally, some properties likecolorandfont-familyinherit from parent to child. I manage this by keeping specificity flat — mostly classes — and using@layerin complex projects to explicitly control the cascade."
Q2: "How do you calculate specificity?"
Bad answer: "IDs are 100, classes are 10, elements are 1."
Good answer:
"It's not base-10 addition — it's a tuple comparison:
(a, b, c)whereais ID count,bis class count including pseudo-classes and attribute selectors, andcis element count. The browser compares left to right.(1, 0, 0)beats(0, 99, 0). I avoid specificity wars by using BEM naming, which keeps most of my selectors at(0, 1, 0)or(0, 2, 0), and I use@layerwhen I need to override third-party styles without reaching for!important."
Q3: "What is a Block Formatting Context (BFC)?"
Bad answer: "Something to do with floats."
Good answer:
"A Block Formatting Context is an isolated rendering region where block boxes are laid out independently of the rest of the page. It's created by things like
overflow: hidden,display: flow-root,position: absolute, andfloat. The practical use cases are: containing floats so the parent wraps them, preventing margin collapse between siblings, and stopping inline content from wrapping around floats. I usedisplay: flow-rootas the modern, intentional way to create a BFC without side effects — unlikeoverflow: hidden, which clips content."
Q4: "Explain stacking contexts. When does z-index fail?"
Bad answer: "Higher z-index is on top."
Good answer:
"A stacking context is a three-dimensional layer system.
z-indexonly works within the same stacking context, and a new context is created bypositionwith a non-autoz-index,opacityless than 1,transform,filter,isolation: isolate, and several other properties. The most common bug is settingz-index: 9999on a child whose parent creates a new stacking context withz-index: 1— the child is trapped inside that layer and can never appear above an element outside the parent's context. I debug this in Chrome DevTools Layers panel and fix it by either removing the parent'sz-indexor restructuring the DOM."
Q5: "When would you use Grid over Flexbox?"
Bad answer: "Grid is for 2D, Flexbox is for 1D."
Good answer:
"I use Grid when I need to control both rows and columns simultaneously — page layouts, card grids, dashboards. I use Flexbox when I'm distributing items along a single axis — navigation bars, card footers, form rows with labels and inputs. In practice, they work together: Grid for the page skeleton, Flexbox for the components inside each grid cell. Grid is also better for gap handling in two dimensions, while Flexbox is better when content size should drive the layout, like a row of tags that wraps naturally."
Q6: "How do you handle browser support for modern CSS features?"
Bad answer: "I don't use features that aren't supported everywhere."
Good answer:
"I use progressive enhancement and feature detection. For layout, I start with a solid baseline that works everywhere — like a single-column mobile layout — then enhance with Grid and Flexbox inside
@supportsqueries if needed. For features like container queries or:has(), I check caniuse.com and let the baseline experience work without them — they're enhancements, not requirements. In build tools, I use PostCSS withpostcss-preset-envto transpile modern syntax for older browsers. I also test in actual browsers, not just Chrome, and I use@supports (display: grid)when I need to provide a fallback layout."
Part 5: How to Talk About Your Process
In interviews, you'll be asked: "Walk me through this project." Most juniors list technologies. Hireable developers explain decisions.
The STAR Method for Portfolio Projects
| Letter | What to Cover |
|---|---|
| Situation | "I needed to build a responsive blog that worked on a $50 Android phone and passed WCAG 2.2." |
| Task | "I was responsible for the entire frontend: HTML structure, CSS architecture, performance, and accessibility." |
| Action | "I chose mobile-first CSS Grid for the layout because it eliminated the need for complex media query overrides. I used clamp() for fluid typography instead of breakpoint-based font sizes. For images, I generated AVIF and WebP fallbacks with Sharp and used srcset to serve appropriate sizes." |
| Result | "The site scores 96 on Lighthouse Performance, 100 on Accessibility, and loads in 1.2 seconds on 3G. The CSS is organized with ITCSS and BEM, so another developer could jump in and understand the structure immediately." |
Decision Narratives to Memorize
On Grid vs. Flexbox:
"I started with Grid for the overall page architecture because I needed precise control over the sidebar and main content areas. Inside the cards, I used Flexbox because the content height was variable and I wanted the footer to always stick to the bottom using
margin-top: auto."
On Dark Mode:
"I implemented dark mode using CSS Custom Properties and the
:has()selector with a hidden checkbox toggle. This meant zero JavaScript for the theme switch, which eliminated flash-of-unstyled-content issues and kept the bundle size down. The same variables also respectprefers-color-schemefor users who haven't toggled manually."
On Performance:
"My biggest win was image optimization. I converted all assets to AVIF with WebP fallbacks, used
loading="lazy"below the fold, and set explicitwidthandheighton every image to prevent layout shift. I also inlined critical CSS for the hero section and deferred the rest, which brought First Contentful Paint under 800ms."
Part 6: The Capstone Project Checklist
Your portfolio is your final exam. It needs to prove you can do the job.
Design Requirements
- Custom design — No Bootstrap, Tailwind, or template. Build from scratch.
- Hero section — Clear value proposition, professional photo or illustration
- 3+ project case studies — Each with live link, GitHub repo, tech stack tags, and a "what I learned" paragraph
- About section — Your story, not a resume dump
- Contact form — Functional (Formspree, Netlify Forms, or similar)
- Resume download — PDF + accessible HTML version
Technical Requirements
- Semantic HTML5 —
<header>,<nav>,<main>,<article>,<footer>, proper heading hierarchy - CSS Grid + Flexbox — Grid for page architecture, Flexbox for components
- Mobile-first responsive — Works flawlessly from 320px to 2560px
- Dark mode — Toggle +
prefers-color-schemesupport - BEM naming — Clean, flat specificity
- ITCSS architecture — Organized, scalable file structure
- Modern CSS — Custom Properties,
clamp(), Container Queries where appropriate - Zero layout shifts — All images have dimensions, fonts use
font-display: swap
Performance Requirements
- Lighthouse 90+ on Performance, Accessibility, Best Practices, and SEO
- Optimized images — AVIF/WebP with
<picture>fallbacks, lazy loading - Minified assets — Vite build or equivalent
- Core Web Vitals — LCP < 2.5s, CLS < 0.1, INP < 200ms
Accessibility Requirements
- Keyboard navigable — Tab through entire site without a mouse
- Focus indicators — Visible on all interactive elements
- Skip link — First focusable element on every page
- Color contrast — WCAG AA minimum (4.5:1 for text)
- ARIA labels — On navigation, current page states, and icon-only buttons
- Alt text — Descriptive, contextual alt text on every image
Deployment Requirements
- Live URL — Netlify, Vercel, or GitHub Pages
- Custom domain (optional but impressive) —
yourname.devvia Namecheap or Porkbun - HTTPS enabled — Automatic on Netlify/Vercel
- GitHub repo — Clean commit history, meaningful messages, README with setup instructions
Key Takeaways
| Concept | Interview Angle |
|---|---|
| BEM / ITCSS | "I organize CSS to prevent specificity wars and make onboarding easy." |
| Grid vs. Flexbox | "Grid for 2D architecture, Flexbox for 1D distribution. I use both." |
| Stacking Contexts | "z-index only works within a context. I debug with DevTools Layers." |
| BFC | "I use display: flow-root to contain floats without clipping." |
| Cascade | "I keep specificity flat and use @layer for explicit control." |
| Performance | "I optimize LCP with preloaded hero images, CLS with aspect ratios, and INP with GPU-accelerated animations." |
| Accessibility | "I test with keyboard-only navigation and screen readers. It's not a checklist item — it's the foundation." |
Quick Reference Cheat Sheet
1// Center a div (Grid) 2.parent { display: grid; place-items: center; } 3 4// Responsive grid (no media queries) 5.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); } 6 7// Hamburger (CSS only) 8#toggle:checked ~ .nav { transform: translateX(0); } 9 10// Specificity: (ID, Class, Element) 11// #nav .link = (1,1,0) 12// .card h2 = (0,1,1) 13 14// Stacking context fix 15// Remove z-index from parent, or raise parent's z-index 16 17// BFC 18.container { display: flow-root; } // Contains floats, no side effects
This is the final module. Everything before this was practice. Your portfolio is the proof. Ship it, share it, and start applying. The best time to build it was three months ago. The second best time is now.