Chart.js Responsive Layout for Fixed-Height Dashboard Cards
Fixed-height Chart.js dashboard tiles need responsive:true, maintainAspectRatio:false, and an explicit CSS height on the canvas wrapper to prevent zero-height collapse and resize thrash.
25 Nov 2025, 06:40 UTC

The configuration that keeps dashboard tiles stable
When a chart must fill a fixed-height card in a responsive grid, set options.responsive = true and options.maintainAspectRatio = false, then give the canvas wrapper an explicit CSS height. Chart.js fits the canvas to the wrapper's content box instead of deriving height from width. This avoids the zero-height collapse and aspect-ratio feedback loops that happen when the canvas tries to size itself.
Worked example
Run this in the browser after the container exists in the DOM. The wrapper must have a height before you construct the chart.
<div class="chart-box">
<canvas id="sales"></canvas>
</div>
/* CSS */
.chart-box {
position: relative;
height: 260px;
}
// JavaScript (run after the DOM element exists)
const ctx = document.getElementById('sales');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: ['Q1', 'Q2', 'Q3'],
datasets: [{
data: [12, 19, 15],
borderWidth: 2,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
resizeDelay: 120,
animation: { duration: 0 },
scales: {
x: { grid: { display: false } },
y: { beginAtZero: true }
}
}
});
Why the wrapper height matters
With maintainAspectRatio: false, Chart.js no longer computes height from width and the configured aspectRatio. The canvas height comes from the wrapper's content box (the area inside any padding and border). If the wrapper has no height, a percentage height from an auto-height parent, or display: contents, the wrapper collapses to zero and the chart disappears.
Do not put width or height attributes on the <canvas> element, and avoid styling the canvas itself for layout. Chart.js manages the canvas backing store and CSS size; external sizing fights the resize observer and can produce blurry rendering or feedback loops.
Resize path versus data updates
Container resizes and data changes are separate code paths. When the wrapper resizes, Chart.js resizes the backing canvas for the current devicePixelRatio (the ratio between physical device pixels and CSS pixels) and re-renders layout. For data-only changes, call chart.update('none') to skip animation, or chart.update() when you want transitions. Do not construct a new Chart on every resize; reuse the instance and let the internal resize handler work.
resizeDelay coalesces bursts of resize events before layout work. A value such as 120 milliseconds is useful when the wrapper itself animates or when many tiles resize at once. Without a delay, a CSS height transition on the wrapper can cause the chart to thrash through repeated layout and render cycles.
Limits and common mistakes
- Missing wrapper height:
maintainAspectRatio: falseremoves automatic height. An explicit CSS height on the wrapper is required. - Conflicting aspect settings: Setting
aspectRatiowhilemaintainAspectRatioisfalsehas no effect on height; the wrapper controls it. - Canvas-level sizing: Width/height attributes or CSS on the canvas interfere with the responsive engine.
- Re-creation on resize: Building a new chart instance per resize event leaks memory and drops interaction state.
- Missing cleanup: In single-page apps and component frameworks, call
chart.destroy()on unmount to detach observers and listeners. - Time scale without adapter: For time axes, register a date adapter (for example, a date-fns or Luxon adapter) or the scale may not behave as expected.
- High pixel ratio cost: A high
devicePixelRatiomagnifies canvas memory and redraw cost. Large line datasets should be aggregated, decimated, or paginated. On dense dashboards with many simultaneous charts, consider reducing chart count or capping the pixel ratio. - Server-side rendering: Guard canvas access during SSR or render a placeholder;
document.getElementByIdand canvas contexts do not exist on the server.
How to verify the layout
Resize the browser window and log the wrapper and canvas dimensions in the browser console:
const box = document.querySelector('.chart-box');
const canvas = document.getElementById('sales');
console.log('wrapper', box.clientWidth, box.clientHeight);
console.log('canvas CSS', canvas.style.width, canvas.style.height);
console.log('canvas backing', canvas.width, canvas.height);
With maintainAspectRatio: false, the canvas CSS size should track the wrapper, not width divided by an aspect ratio. To confirm the difference, toggle maintainAspectRatio to true in the same wrapper: the rendered height will change with width instead of staying fixed.
To test resize coalescing, add a CSS height transition to .chart-box and compare resizeDelay: 0 against resizeDelay: 150. The delayed setting should produce fewer layout and render cycles during the animated resize.
Version assumptions
Option names and defaults are version-sensitive. The configuration above uses Chart.js 3/4 naming (resizeDelay, options.scales.x.grid.display). Chart.js 2 uses different scale and option structures. Check package.json for the installed major version and confirm resizeDelay, devicePixelRatio, and scale options in that version's typings before deploying.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.