Skip to main content

Responsive Data Visualization React Mobile

Charts on mobile need different treatment than desktop: smaller screens demand larger fonts, fewer data points, and touch-friendly interactions (no hover, tap to select). This article teaches you to build responsive charts that look great at any screen size, from watches (320px) to desktops (2560px+). You'll learn container queries, media breakpoints, touch detection, and when to hide or simplify chart elements on small screens.

The Challenge: Charts Across All Screens​

A line chart with 100 points and tiny axis labels is unreadable on a 375px phone but perfect on a 1920px monitor. Responsive charting isn't just scaling; it's redesigning the chart for each context. Desktop users hover to explore; mobile users tap. Desktop screens show legends; mobile screens need collapsible overlays. Responsive charts require thinking about the full experience.

In 2026, mobile web traffic represents 62% of overall web usage globally. Charts that don't work on mobile lose half your audience. Building responsive-first is essential.

Using ResponsiveContainer (Recharts)​

Recharts provides ResponsiveContainer, which wraps a chart and scales it to fill its parent:

import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer
} from "recharts";

export function ResponsiveLineChart({ data }) {
return (
<div style={{ width: "100%", height: "400px" }}>
<ResponsiveContainer>
<LineChart data={data} margin={{ top: 5, right: 30, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="revenue" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>
</div>
);
}

The parent div constrains size; ResponsiveContainer fills it. The chart scales to fit. This works for all Recharts chart types.

Custom Responsive SVG Charts with viewBox​

For custom SVG charts (not using Recharts), use viewBox to scale content to the viewport:

import React, { useState, useEffect } from "react";
import { scaleLinear, scaleBand } from "d3-scale";

export function ResponsiveBarChart({ data }) {
const [dimensions, setDimensions] = useState({
width: 600,
height: 400
});

useEffect(() => {
const handleResize = () => {
const container = document.getElementById("chart-container");
if (container) {
setDimensions({
width: container.clientWidth,
height: Math.max(300, container.clientWidth * 0.6) // maintain aspect ratio
});
}
};

window.addEventListener("resize", handleResize);
handleResize();

return () => window.removeEventListener("resize", handleResize);
}, []);

const margin = { top: 20, right: 20, bottom: 40, left: 50 };
const innerWidth = Math.max(100, dimensions.width - margin.left - margin.right);
const innerHeight = Math.max(100, dimensions.height - margin.top - margin.bottom);

const xScale = scaleBand()
.domain(data.map((d) => d.label))
.range([0, innerWidth])
.padding(0.1);

const yScale = scaleLinear()
.domain([0, Math.max(...data.map((d) => d.value))])
.range([innerHeight, 0]);

return (
<div
id="chart-container"
style={{ width: "100%", height: "400px" }}
>
<svg
viewBox={`0 0 ${dimensions.width} ${dimensions.height}`}
preserveAspectRatio="xMidYMid meet"
style={{ width: "100%", height: "100%" }}
>
<g transform={`translate(${margin.left}, ${margin.top})`}>
{data.map((d, i) => (
<rect
key={i}
x={xScale(d.label)}
y={yScale(d.value)}
width={xScale.bandwidth()}
height={innerHeight - yScale(d.value)}
fill="steelblue"
/>
))}
</g>

{/* Axes */}
<g transform={`translate(${margin.left}, ${dimensions.height - margin.bottom})`}>
{xScale.domain().map((label, i) => (
<g key={i} transform={`translate(${xScale(label) + xScale.bandwidth() / 2}, 0)`}>
<line y1="0" y2="5" stroke="black" />
<text
y="20"
textAnchor="middle"
fontSize={innerWidth < 300 ? "10" : "12"}
>
{label}
</text>
</g>
))}
</g>
</svg>
</div>
);
}

The viewBox attribute tells the SVG to scale its internal coordinate system. preserveAspectRatio="xMidYMid meet" centers and maintains aspect ratio. The chart recomputes scales on resize, ensuring it always fills the container.

Media Queries for Conditional Rendering​

Different screen sizes call for different visualizations:

import React, { useState, useEffect } from "react";

export function AdaptiveChart({ data }) {
const [screenSize, setScreenSize] = useState("desktop");

useEffect(() => {
const updateScreenSize = () => {
if (window.innerWidth < 480) setScreenSize("mobile");
else if (window.innerWidth < 768) setScreenSize("tablet");
else setScreenSize("desktop");
};

window.addEventListener("resize", updateScreenSize);
updateScreenSize();

return () => window.removeEventListener("resize", updateScreenSize);
}, []);

if (screenSize === "mobile") {
// Simplified chart for mobile: fewer data points, larger fonts
return (
<div>
<h3>Top Performers</h3>
{data.slice(0, 3).map((d, i) => (
<div
key={i}
style={{
padding: "10px",
borderBottom: "1px solid #ddd",
fontSize: "16px"
}}
>
<strong>{d.label}</strong>: {d.value}
</div>
))}
</div>
);
}

if (screenSize === "tablet") {
// Medium detail: bar chart with larger bars and labels
return (
<div style={{ fontSize: "14px" }}>
{/* Bar chart with adjusted sizing */}
</div>
);
}

// Desktop: full chart with all details
return (
<div>
{/* Complex chart with legend, multiple series, etc. */}
</div>
);
}

This renders different UI based on screen size. Mobile gets a simple list; tablet a smaller chart; desktop the full visualization. This is a mobile-first approach: start with simplicity, add complexity as space allows.

Touch Interaction: Tap Instead of Hover​

Mobile has no hover; users tap. Update your interaction model:

import React, { useState } from "react";

export function TouchFriendlyChart({ data }) {
const [selectedIndex, setSelectedIndex] = useState(null);

const handleBarPress = (index) => {
// On mobile, toggle selection on tap
setSelectedIndex(selectedIndex === index ? null : index);
};

const maxValue = Math.max(...data.map((d) => d.value));

return (
<div style={{ padding: "20px" }}>
<div style={{ display: "flex", gap: "10px", alignItems: "flex-end" }}>
{data.map((d, i) => (
<div
key={i}
onClick={() => handleBarPress(i)}
style={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
cursor: "pointer",
minWidth: "0"
}}
>
<div
style={{
width: "100%",
height: `${(d.value / maxValue) * 150}px`,
backgroundColor:
selectedIndex === i ? "orange" : "steelblue",
borderRadius: "4px",
transition: "background-color 0.2s"
}}
/>
<label
style={{
marginTop: "8px",
fontSize: "12px",
wordBreak: "break-word",
textAlign: "center"
}}
>
{d.label}
</label>

{/* Show details below bar on tap */}
{selectedIndex === i && (
<div
style={{
marginTop: "8px",
padding: "8px",
backgroundColor: "#f0f0f0",
borderRadius: "4px",
fontSize: "14px",
fontWeight: "bold",
whiteSpace: "nowrap"
}}
>
{d.value}
</div>
)}
</div>
))}
</div>
</div>
);
}

Tapping a bar toggles selection and shows details. This is better than hover for mobile: users see what they tapped, and details persist for reading.

Simplifying Charts for Small Screens​

On mobile, hide or simplify complex elements:

const Mobile = ({ data, isMobile }) => {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="month"
tick={{ fontSize: isMobile ? 10 : 12 }}
angle={isMobile ? -45 : 0}
height={isMobile ? 60 : 30}
/>
<YAxis
tick={{ fontSize: isMobile ? 10 : 12 }}
width={isMobile ? 40 : 60}
/>

{/* Hide legend on mobile to save space */}
{!isMobile && <Legend />}

{/* Simpler tooltip on mobile */}
<Tooltip
contentStyle={{
fontSize: isMobile ? "12px" : "14px",
padding: isMobile ? "4px 8px" : "8px 12px"
}}
/>

{/* Show fewer lines on mobile */}
<Line dataKey="sales" stroke="#8884d8" />
{!isMobile && <Line dataKey="profit" stroke="#82ca9d" />}
</LineChart>
</ResponsiveContainer>
);
};

Adjust font sizes, hide redundant elements (legends, second series), and change angles for better readability on small screens.

Touch-Friendly Hit Targets​

Make interactive elements large enough to tap accurately (minimum 44x44px):

export function MobileOptimizedChart({ data }) {
// Each bar is at least 44px wide
const minBarWidth = 44;
const containerWidth = Math.max(data.length * minBarWidth, 300);

return (
<div
style={{
overflowX: "auto",
width: "100%"
}}
>
<div style={{ width: `${containerWidth}px`, padding: "20px" }}>
{/* Chart with adequate touch targets */}
</div>
</div>
);
}

If bars are too small, allow horizontal scrolling instead of cramping them into the viewport.

Testing Responsiveness​

Use Chrome DevTools or similar:

  1. Open DevTools (F12)
  2. Toggle Device Toolbar (Ctrl+Shift+M)
  3. Test at multiple breakpoints: 375px (iPhone SE), 768px (iPad), 1440px (desktop)

Measure:

  • Readability: Can you read all labels?
  • Interactivity: Can you tap/click accurately?
  • Performance: Does the chart render smoothly on throttled mobile networks?

Key Takeaways​

  • Responsive charts adapt to screen size, not just scale proportionally.
  • Use ResponsiveContainer (Recharts) or viewBox (custom SVG) for fluid layouts.
  • Mobile charts need tap interaction, larger fonts, and simplified elements.
  • Test at multiple breakpoints: mobile (375px), tablet (768px), desktop (1440px+).
  • Implement media queries or size detection to conditionally render different visualizations per context.

Frequently Asked Questions​

How do I detect if a user is on mobile?​

Use media queries in CSS or check window.innerWidth in JavaScript. For React, check at mount and on resize:

const [isMobile, setIsMobile] = useState(window.innerWidth < 768);

useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);

Should I show fewer data points on mobile?​

Yes, sample or aggregate data. Mobile screens can't show 100 bars clearly; sample every Nth point or group by day instead of hour.

How do I optimize chart performance on low-end mobile devices?​

Use Canvas instead of SVG for large datasets; reduce animation complexity; disable features like tooltips on hover (not available on touch anyway); defer non-critical rendering with requestIdleCallback.

Can I use CSS media queries with SVG charts?​

Partially. You can wrap the SVG in a media-query-responsive div, but SVG internals (text size, spacing) don't respond to media queries. Use JavaScript to adjust scales and re-render.

What about landscape vs portrait orientation?​

Listen to the orientationchange event or re-measure on resize. Most charts handle both if you use responsive scaling. Test both orientations.

Further Reading​