Responsive design handles most layout differences between desktop and mobile through CSS media queries, but sometimes you need JavaScript to know the device type — for example, to load a lighter image gallery on phones, disable heavy animations on tablets, or change the behaviour of a touch-based carousel. There are two main approaches: checking the viewport width at runtime using window.innerWidth or matchMedia(), and inspecting the navigator.userAgent string to identify the browser and OS. The matchMedia() approach is preferred because it mirrors your CSS breakpoints exactly and updates automatically when the user resizes the window or rotates the device. User-agent sniffing is fragile — UA strings change frequently and are easily spoofed — so rely on it only as a last resort for features that genuinely cannot be determined from the viewport. The ResizeObserver API lets you react to element-level size changes rather than just the whole window, which is useful inside web components or sidebars. For touch detection specifically, the standard check is navigator.maxTouchPoints > 0 rather than the deprecated navigator.msMaxTouchPoints. Combining these techniques gives you a reliable, future-proof toolkit for adaptive JavaScript behaviour. When paired with the techniques from localStorage, you can even persist the detected preference and skip the check on repeat visits. All examples below use vanilla JavaScript with no dependencies.
Problem: You need JavaScript to behave differently on mobile devices or at specific breakpoints, and you want a reliable detection method that does not break when browsers update their user-agent strings.
Solution: Add the following code to your theme’s JavaScript file:
// 1. Check viewport width (mirrors CSS breakpoints)
function isMobile() {
return window.innerWidth < 768;
}
function isTablet() {
return window.innerWidth >= 768 && window.innerWidth < 1024;
}
// 2. matchMedia — preferred approach, matches CSS media queries
const mobileQuery = window.matchMedia('(max-width: 767px)');
function handleBreakpointChange(e) {
if (e.matches) {
console.log('Mobile layout active');
// load lighter assets, disable heavy animations, etc.
} else {
console.log('Desktop layout active');
}
}
mobileQuery.addEventListener('change', handleBreakpointChange);
handleBreakpointChange(mobileQuery); // run on load
// 3. Touch device detection (does NOT rely on user-agent)
const isTouchDevice = navigator.maxTouchPoints > 0;
// 4. ResizeObserver — react to an element's size change
const sidebar = document.querySelector('.sidebar');
if (sidebar) {
const ro = new ResizeObserver(entries => {
for (const entry of entries) {
const width = entry.contentRect.width;
console.log('Sidebar width:', width);
}
});
ro.observe(sidebar);
}
NOTE: Avoid using screen.width for layout decisions — it returns the physical screen resolution, not the CSS viewport width, and gives misleading results on high-DPI displays. Also, never gate critical functionality (form submission, navigation) behind a device check; use progressive enhancement instead so the feature works for everyone and is merely enhanced on specific devices.