Modernizing My Portfolio: A Deep Dive into Responsive Design with CSS Custom Properties
On This Page
The Problem with Fixed Widths
When I first launched my portfolio site, I made a classic mistake that many developers make: I used fixed pixel widths everywhere. The main content was locked to 720px, projects and blog pages maxed out at 960px, and the experience felt… constrained.
On a 4K monitor, my content looked like a tiny column in the middle of an ocean of whitespace. On mobile, it was fine—but the transition between desktop and mobile wasn’t smooth. The header would jump in height, the navigation would suddenly shrink, and it just didn’t feel polished.
I needed to modernize the entire site with true responsive design, not just “mobile-friendly” design.
What I Wanted to Achieve
My goals were clear:
- Eliminate all fixed pixel widths - No more hardcoded
width: 720px - Implement fluid scaling - Content should grow and shrink smoothly with the viewport
- Create a proper breakpoint system - Mobile (320-767px), Tablet (768-1023px), Desktop (1024-1439px), Large Desktop (1440px+)
- Add a mobile hamburger menu - The horizontal nav bar needed to collapse on mobile
- Maintain design consistency - Header height and spacing should feel consistent across all screen sizes
This wasn’t just about making things “responsive”—it was about creating a fluid, continuous experience that felt native at every screen size.
The Solution: CSS Custom Properties + Modern Layout Techniques
1. Building a Responsive Design System
Instead of scattering fixed values throughout my CSS, I created a centralized design system using CSS custom properties:
:root {
/* Responsive container widths */
--container-narrow: min(720px, 100% - 2rem);
--container-medium: min(960px, 100% - 2rem);
--container-wide: min(1200px, 100% - 2rem);
--container-extra-wide: min(1400px, 100% - 2rem);
/* Fluid spacing using clamp() */
--space-xs: clamp(0.5rem, 2vw, 0.75rem);
--space-sm: clamp(0.75rem, 3vw, 1rem);
--space-md: clamp(1rem, 4vw, 1.5rem);
--space-lg: clamp(1.5rem, 5vw, 2.5rem);
--space-xl: clamp(2rem, 6vw, 3.5rem);
/* Fluid typography */
--font-size-base: clamp(1rem, 1.5vw, 1.25rem);
--font-size-sm: clamp(0.875rem, 1.2vw, 1rem);
--font-size-lg: clamp(1.125rem, 1.8vw, 1.5rem);
}
Why this works:
min()ensures containers never exceed their max width but can shrink responsivelyclamp()creates fluid values that scale smoothly between a minimum and maximum- CSS variables make it easy to maintain consistency across the entire site
2. Fluid Typography with clamp()
Fixed font sizes were killing the mobile experience. A 3rem heading looks great on desktop but overwhelming on a 320px phone. The solution? Fluid typography:
h1 {
font-size: clamp(2rem, 5vw, 3.052rem);
}
h2 {
font-size: clamp(1.75rem, 4vw, 2.441rem);
}
Now headings scale smoothly based on viewport width. On mobile, h1 is 2rem (32px). On desktop, it grows to 3.052rem (~49px). But instead of jumping at a breakpoint, it scales continuously with 5vw (5% of viewport width).
The magic of clamp(): clamp(min, preferred, max)
- Mobile gets the minimum value
- Desktop gets the maximum value
- Everything in between scales fluidly based on viewport width
3. CSS Grid for Responsive Layouts
I replaced all my manual flexbox grids with CSS Grid’s auto-fit and minmax():
ul {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
gap: var(--space-lg);
}
What this does:
repeat(auto-fit, ...)creates as many columns as fit in the containerminmax(min(300px, 100%), 1fr)ensures columns are at least 300px (or 100% on narrow screens)- Automatically collapses to single column on mobile without any breakpoints
- Scales smoothly from 1 column → 2 columns → 3+ columns based on available space
No more manual breakpoint management for grid layouts!
4. Building a Mobile Hamburger Menu
The horizontal navigation bar worked great on desktop but was cramped on mobile. I needed a hamburger menu that felt native and smooth.
The Structure:
<button class="hamburger" aria-label="Toggle menu" aria-expanded="false">
<span></span>
<span></span>
<span></span>
</button>
<div class="internal-links">
<!-- Nav links here -->
</div>
The CSS Animation:
/* Hamburger morphs into X when open */
.hamburger[aria-expanded="true"] span:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px);
}
.hamburger[aria-expanded="true"] span:nth-child(2) {
opacity: 0;
}
.hamburger[aria-expanded="true"] span:nth-child(3) {
transform: rotate(-45deg) translate(7px, -6px);
}
/* Menu slides down from top */
.internal-links {
position: fixed;
top: 60px;
left: 0;
right: 0;
transform: translateY(-100%);
transition: all 0.3s ease;
}
.internal-links.open {
transform: translateY(0);
}
The JavaScript:
const hamburger = document.querySelector('.hamburger');
const nav = document.querySelector('.internal-links');
hamburger?.addEventListener('click', () => {
const isOpen = hamburger.getAttribute('aria-expanded') === 'true';
hamburger.setAttribute('aria-expanded', String(!isOpen));
nav?.classList.toggle('open');
});
// Close when clicking outside
document.addEventListener('click', (e) => {
if (!hamburger?.contains(e.target) && !nav?.contains(e.target)) {
hamburger?.setAttribute('aria-expanded', 'false');
nav?.classList.remove('open');
}
});
UX Details I Focused On:
- Touch-friendly: 44x44px button meets WCAG minimum touch target size
- Accessible: Proper ARIA labels and keyboard support
- Smooth: 300ms transitions feel natural, not jarring
- Smart auto-close: Closes when clicking outside or on a nav link
- Visual feedback: Three-line icon morphs into X when menu is open
5. Comprehensive Breakpoint System
Instead of one arbitrary breakpoint, I created a comprehensive system:
/* Mobile: 320px - 767px */
@media (max-width: 767px) {
main {
padding: var(--space-md) var(--space-sm);
}
}
/* Tablet: 768px - 1023px */
@media (min-width: 768px) and (max-width: 1023px) {
main {
padding: var(--space-lg) var(--space-md);
}
}
/* Desktop: 1024px - 1439px */
@media (min-width: 1024px) and (max-width: 1439px) {
main {
padding: var(--space-xl) var(--space-lg);
}
}
/* Large Desktop: 1440px+ */
@media (min-width: 1440px) {
main {
padding: var(--space-xl) var(--space-lg);
}
}
Each breakpoint adjusts spacing appropriately without changing the fundamental layout.
The Technical Challenges I Faced
Challenge 1: Inconsistent Header Heights
When switching from desktop to mobile, the header would jump from ~80px to ~50px. This created a jarring experience and broke the visual rhythm.
Solution: Set consistent minimum heights:
header {
min-height: 60px;
}
nav {
min-height: 60px;
}
Now the header maintains 60px height across all screen sizes, with content vertically centered.
Challenge 2: Home and About Pages Stuck at 720px
I had updated the projects, blog, and wiki pages to use wider containers, but the home and about pages were still locked to 720px. Why?
The Problem:
- Global CSS set
main { width: var(--container-narrow); }(720px) - Other pages had page-specific overrides
- Home and about pages had no overrides, so they inherited the narrow width
Solution:
- Changed global default to
--container-wide(1200px) - Added page-specific override to home page for
--container-extra-wide(1400px) - Updated BlogPost layout (used by about page) to use
--container-medium(960px)
Now the width hierarchy is:
- Home: 1400px max
- Projects/Blog/Wiki: 960px max
- About/Blog posts: 960px max
- Project details: 720px max (for readability)
Challenge 3: CSS Grid Browser Compatibility
While CSS Grid has great browser support now, auto-fit with minmax() can behave unexpectedly on older browsers. I tested on:
- Chrome 120+
- Firefox 121+
- Safari 17+
- Mobile Safari (iOS 17+)
- Chrome Mobile (Android)
Everything worked beautifully. For older browser support, I could add a @supports fallback to flexbox, but given my target audience (tech professionals), I decided modern CSS was the right choice.
The Results
Before vs After
Before:
- Fixed 720px width for most content
- One arbitrary breakpoint at 720px
- Horizontal nav on all screen sizes (cramped on mobile)
- Header height jumped between desktop/mobile
- Typography fixed at
font-size: 20px
After:
- Fluid widths from 320px to 1400px
- Comprehensive breakpoint system (mobile, tablet, desktop, large desktop)
- Hamburger menu on mobile, horizontal nav on desktop
- Consistent 60px header height
- Fluid typography scaling with
clamp()
Performance Impact
Before optimization:
- No significant performance issues, but lots of unused CSS
After optimization:
- Slightly larger CSS file (+~150 lines)
- But cleaner, more maintainable code
- No JavaScript performance impact (hamburger menu uses vanilla JS, ~20 lines)
- Lighthouse scores unchanged (still 100 across the board)
Accessibility Wins
- WCAG AA compliance: 44x44px touch targets on hamburger button
- Proper ARIA labels:
aria-labelandaria-expandedfor screen readers - Keyboard navigation: All menu items remain keyboard accessible
- Focus management: Proper focus indicators maintained
- Semantic HTML: Using
<nav>and proper heading hierarchy
What I Learned
1. CSS Custom Properties Are a Game-Changer
Before this project, I was using Sass variables. But CSS custom properties are runtime values, which means:
- They can be updated via JavaScript if needed
- They cascade and inherit properly
- DevTools can inspect and modify them
- No build step required
For a design system, they’re perfect.
2. clamp() Is Magic for Responsive Design
I used to write breakpoints like this:
h1 { font-size: 2rem; }
@media (min-width: 768px) {
h1 { font-size: 2.5rem; }
}
@media (min-width: 1024px) {
h1 { font-size: 3rem; }
}
Now I write:
h1 { font-size: clamp(2rem, 5vw, 3rem); }
Three lines of CSS replaced with one, and it scales smoothly instead of jumping.
3. CSS Grid > Flexbox for Card Layouts
Flexbox is great for one-dimensional layouts. But for card grids, CSS Grid with auto-fit and minmax() is superior:
- Automatically responsive without breakpoints
- Better spacing control
- Simpler code
- More predictable behavior
4. Mobile-First Is Still the Right Approach
Even though my site is primarily for desktop users (portfolio/technical content), starting with mobile constraints made the desktop experience better:
- Forced me to prioritize content
- Made me think about touch targets
- Resulted in cleaner, more focused layouts
5. Vanilla JavaScript Is Often Enough
I didn’t need React, Vue, or even Alpine.js for the hamburger menu. 20 lines of vanilla JavaScript handled:
- Toggle functionality
- Click-outside-to-close
- Close-on-navigation
- Accessibility (ARIA attributes)
Modern JavaScript is incredibly capable for UI interactions.
Working with Claude Code
This entire modernization was done with Claude Code as my pair programming partner. Here’s what impressed me:
What Worked Well:
- Systematic approach: Claude identified all 8 files with fixed widths using grep
- Comprehensive updates: Updated global CSS, all page-specific styles, and layouts in one go
- Best practices: Suggested modern CSS techniques (clamp, CSS Grid, custom properties)
- Accessibility: Reminded me about WCAG touch target sizes and ARIA labels
- Git workflow: Helped create detailed conventional commits with proper scope
What I Learned About AI-Assisted Development:
- Context matters: Claude remembered all the files we’d modified and maintained consistency
- Iterative refinement: When I noticed the header height issue, Claude quickly fixed it
- Explanation alongside code: Every change came with clear reasoning
- Production-ready: The code didn’t need cleanup—it was already well-structured
The Workflow:
- I described what I wanted (responsive design, hamburger menu)
- Claude audited the codebase and found all fixed widths
- I reviewed the plan and approved
- Claude implemented changes systematically
- I tested in browser and requested refinements
- Claude committed with detailed conventional commit messages
This is the future of development: AI handles the systematic, repetitive work while I focus on design decisions and user experience.
Next Steps
With the responsive foundation in place, I’m planning to:
- Add dark mode - Use CSS custom properties to make theme switching easy
- Implement page transitions - Astro View Transitions API for smooth navigation
- Optimize images - Use Astro’s built-in image optimization
- Add analytics - Privacy-focused analytics to track what content resonates
- Create more content - Now that the platform is solid, focus on projects and blog posts
Conclusion
Modernizing a portfolio site might seem like a small project, but it’s an exercise in craftsmanship. Every detail matters:
- The smoothness of the hamburger animation
- The consistency of the header height
- The fluid scaling of typography
- The responsive behavior of card grids
These details add up to a professional, polished experience that reflects your standards as a developer.
If you’re still using fixed pixel widths in 2025, it’s time to upgrade. Modern CSS gives you the tools to create truly fluid, responsive designs that feel native at every screen size.
Key Takeaways:
- Use CSS custom properties for a centralized design system
- Replace fixed values with
clamp()for fluid scaling - Use CSS Grid with
auto-fitandminmax()for responsive layouts - Implement proper mobile navigation (hamburger menus done right)
- Test across real devices, not just browser DevTools
- Maintain accessibility throughout (WCAG AA minimum)
The web is responsive by default—our job is to stop breaking it with fixed widths.
Tech Stack: Astro, TypeScript, CSS Custom Properties, CSS Grid, Vanilla JavaScript Repository: github.com/hmbldv/portfolio Live Site: Coming soon (Vercel deployment in progress)
Want to see the code? Check out the commits on GitHub for the full implementation.