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:

    1. Eliminate all fixed pixel widths - No more hardcoded width: 720px
    2. Implement fluid scaling - Content should grow and shrink smoothly with the viewport
    3. Create a proper breakpoint system - Mobile (320-767px), Tablet (768-1023px), Desktop (1024-1439px), Large Desktop (1440px+)
    4. Add a mobile hamburger menu - The horizontal nav bar needed to collapse on mobile
    5. 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 responsively
    • clamp() 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 container
    • minmax(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:

    1. Changed global default to --container-wide (1200px)
    2. Added page-specific override to home page for --container-extra-wide (1400px)
    3. 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-label and aria-expanded for 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:

    1. I described what I wanted (responsive design, hamburger menu)
    2. Claude audited the codebase and found all fixed widths
    3. I reviewed the plan and approved
    4. Claude implemented changes systematically
    5. I tested in browser and requested refinements
    6. 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:

    1. Add dark mode - Use CSS custom properties to make theme switching easy
    2. Implement page transitions - Astro View Transitions API for smooth navigation
    3. Optimize images - Use Astro’s built-in image optimization
    4. Add analytics - Privacy-focused analytics to track what content resonates
    5. 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-fit and minmax() 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.