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.
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);
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 (
<div style={{
display: 'grid',
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: '1rem'
}}>
{images.map(img => <img key={img.id} src={img.url} />)}
</div>
);
}
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;
}
Responsive with Data Attributes
Store responsive settings in data attributes:
// HTML: <div id="grid" data-mobile="1" data-tablet="2" data-desktop="4">
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)`;
}
}