JAVASCRIPT • LEVEL 2 • LESSON 5 OF 9
0% Complete
← Back to Learning Path

📱 Lesson 5: Responsive Grids with JavaScript

Adapt grid layouts based on screen size using JavaScript.

Why JavaScript for Responsive Grids?

CSS media queries work great, but JavaScript gives you more control: detect exact screen width, respond to orientation changes, calculate optimal layouts, and combine multiple conditions.

💡 When to Use JS:

Use CSS media queries for simple breakpoints. Use JavaScript when you need complex logic, calculations, or need to coordinate layout with other features.

Basic Responsive Grid

Change columns based on window width:

function updateGrid() {
  const container = document.getElementById('grid');
  const width = window.innerWidth;
  
  if (width < 640) {
    // Mobile: 1 column
    container.style.gridTemplateColumns = '1fr';
  } else if (width < 1024) {
    // Tablet: 2 columns
    container.style.gridTemplateColumns = 'repeat(2, 1fr)';
  } else {
    // Desktop: 3 columns
    container.style.gridTemplateColumns = 'repeat(3, 1fr)';
  }
}

// Call on load and resize
window.addEventListener('load', updateGrid);
window.addEventListener('resize', updateGrid);

Using matchMedia (Modern Approach)

The matchMedia API is cleaner and more efficient:

const mobile = window.matchMedia('(max-width: 640px)');
const tablet = window.matchMedia('(min-width: 641px) and (max-width: 1024px)');
const desktop = window.matchMedia('(min-width: 1025px)');

function updateGrid() {
  const container = document.getElementById('grid');
  
  if (mobile.matches) {
    container.style.gridTemplateColumns = '1fr';
    container.style.gap = '0.5rem';
  } else if (tablet.matches) {
    container.style.gridTemplateColumns = 'repeat(2, 1fr)';
    container.style.gap = '1rem';
  } else if (desktop.matches) {
    container.style.gridTemplateColumns = 'repeat(4, 1fr)';
    container.style.gap = '2rem';
  }
}

// Listen for changes
mobile.addListener(updateGrid);
tablet.addListener(updateGrid);
desktop.addListener(updateGrid);

// Initial call
updateGrid();

Debouncing Resize Events

Resize fires many times per second. Debounce it for performance:

let resizeTimer;

function debouncedUpdate() {
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(() => {
    updateGrid();
  }, 250); // Wait 250ms after user stops resizing
}

window.addEventListener('resize', debouncedUpdate);

⚡ Performance Tip:

Always debounce or throttle resize handlers! Running updateGrid() 60 times per second during resize can cause jank.

Orientation Detection

Respond to portrait vs landscape:

function updateGrid() {
  const container = document.getElementById('grid');
  const isPortrait = window.innerHeight > window.innerWidth;
  
  if (isPortrait) {
    container.style.gridTemplateColumns = 'repeat(2, 1fr)';
  } else {
    container.style.gridTemplateColumns = 'repeat(4, 1fr)';
  }
}

window.addEventListener('orientationchange', updateGrid);

Container-Based Responsive

Adapt to container width, not window width:

function updateGrid() {
  const container = document.getElementById('grid');
  const containerWidth = container.offsetWidth;
  
  if (containerWidth < 400) {
    container.style.gridTemplateColumns = '1fr';
  } else if (containerWidth < 800) {
    container.style.gridTemplateColumns = 'repeat(2, 1fr)';
  } else {
    container.style.gridTemplateColumns = 'repeat(3, 1fr)';
  }
}

This is powerful for components in sidebars or split layouts!

React Responsive Grid Hook

Create a reusable hook for responsive grids:

function useResponsiveGrid() {
  const [columns, setColumns] = useState(3);
  
  useEffect(() => {
    const updateColumns = () => {
      const width = window.innerWidth;
      if (width < 640) setColumns(1);
      else if (width < 1024) setColumns(2);
      else setColumns(3);
    };
    
    updateColumns();
    window.addEventListener('resize', updateColumns);
    return () => window.removeEventListener('resize', updateColumns);
  }, []);
  
  return columns;
}

// Usage in component
function Gallery({ images }) {
  const columns = useResponsiveGrid();
  
  return (
    
{images.map(img => )}
); }

Breakpoint Configuration

Define breakpoints in a config object:

const breakpoints = {
  sm: 640,
  md: 768,
  lg: 1024,
  xl: 1280
};

const gridConfig = {
  mobile: { cols: 1, gap: '0.5rem' },
  tablet: { cols: 2, gap: '1rem' },
  desktop: { cols: 4, gap: '2rem' }
};

function updateGrid() {
  const width = window.innerWidth;
  const container = document.getElementById('grid');
  let config;
  
  if (width < breakpoints.md) {
    config = gridConfig.mobile;
  } else if (width < breakpoints.lg) {
    config = gridConfig.tablet;
  } else {
    config = gridConfig.desktop;
  }
  
  container.style.gridTemplateColumns = `repeat(${config.cols}, 1fr)`;
  container.style.gap = config.gap;
}

✅ Best Practice:

Match your JavaScript breakpoints to your CSS breakpoints (if you're using both). Consistency prevents layout jumps!

Responsive with Data Attributes

Store responsive settings in data attributes:

// HTML: 
function updateGrid() { const container = document.getElementById('grid'); const width = window.innerWidth; let cols; if (width < 768) { cols = container.dataset.mobile; } else if (width < 1024) { cols = container.dataset.tablet; } else { cols = container.dataset.desktop; } container.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; }

Combining with User Preferences

Respect both screen size AND user choice:

let userPreference = localStorage.getItem('grid-density') || 'auto';

function updateGrid() {
  const container = document.getElementById('grid');
  
  if (userPreference === 'auto') {
    // Use responsive logic
    const width = window.innerWidth;
    if (width < 768) {
      container.style.gridTemplateColumns = '1fr';
    } else {
      container.style.gridTemplateColumns = 'repeat(3, 1fr)';
    }
  } else {
    // Use user's fixed preference
    container.style.gridTemplateColumns = `repeat(${userPreference}, 1fr)`;
  }
}

🎯 Practice Challenge!

Create a grid that shows 1 column on mobile, 2 on tablet, and 4 on desktop. Add debouncing to the resize handler.

📝 Quick Check (3 Questions)

1. Why should you debounce resize events?

2. What's the advantage of matchMedia over innerWidth?

3. When should you use container width instead of window width?

← Previous Lesson