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)