Skip to main content
Skip to main content

Mobile and responsive charts

Your chart fills its container — it does not magically know it is on a phone. Mobile support is three layers you wire together:

  1. Page — viewport meta tag and scroll containment
  2. Chart engine — compact axes and fonts below 600px width
  3. ChartUI — dense toolbar, overflow menu, safe areas
Loading chart…
On a narrow screen the toolbar compacts — the chart still needs a real height.

End-user guide to toolbar buttons on mobile: Top toolbar and mobile.

Step 1 — viewport meta tag

On notched iPhones, add viewport-fit=cover so safe-area insets work:

<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>

ChartUI reads env(safe-area-inset-*) on its outer shell. Without the meta tag, padding may be zero on iOS.

In Next.js, put this in pages/_app via next/head (not _document).

Step 2 — give the chart a height

The canvas follows the child div inside ChartUI:

<div style={{ width: "100%", height: "min(70vh, 560px)", minHeight: 320 }}>
<ChartUI chart={chart}>
<div ref={containerRef} style={{ width: "100%", height: "100%" }} />
</ChartUI>
</div>
RuleWhy
Non-zero height (vh, dvh, px)Zero height = invisible chart
min-height: 0 on flex parentsFlex children otherwise refuse to shrink
Avoid horizontal page scrolloverflow-x: clip on page or panel

The engine uses ResizeObserver and calls fit() when the box changes — you do not manually resize on orientation change if the container updates.

Step 3 — chart layout modes

Pass layout when creating the chart:

import { createChart, CHART_COMPACT_BREAKPOINT_PX } from "@efixdata/exeria-chart";

const chart = createChart({
container: el,
layout: {
mode: "auto",
breakpoints: {
compact: CHART_COMPACT_BREAKPOINT_PX, // default 600
},
},
});

What auto picks

Effective modeWhen
desktopWide screen + fine pointer (mouse)
compactWidth ≤ compact breakpoint (default 600px)
touchCoarse pointer on a wide screen (e.g. tablet)

Override manually if you need to:

chart.setLayoutMode("desktop"); // force desktop metrics
chart.setLayoutMode("auto"); // follow media queries again

In compact mode, fit() uses narrower value axes, shorter time ticks, and tighter legend spacing.

Listen for changes:

chart.subscribe("ENVIRONMENT_CHANGE", (env) => {
console.log(env.layoutMode, env.isCompact);
});

Global breakpoint (all charts): configureChartEnvironment({ compactBreakpoint: 640 }). Per-chart: layout.breakpoints.compact on createChart.

React hook from the UI package:

import { useChartEnvironment } from "@efixdata/exeria-chart-ui-react";

function MyToolbar() {
const { isCompact, layoutMode, isTouch } = useChartEnvironment();
// hide custom chrome when isCompact
}

Types and helpers: Chart environment reference.

Step 4 — ChartUI mobile props

import {
ChartUI,
applyChartUiEnvironmentOptions,
} from "@efixdata/exeria-chart-ui-react";

applyChartUiEnvironmentOptions({ compactBreakpoint: 600 });

<ChartUI
chart={chart}
mobileLayout="minimal"
compactBreakpoint={600}
theme={{ edgeInset: 8 }}
>
<div ref={containerRef} style={{ width: "100%", height: "100%" }} />
</ChartUI>
PropWhat it does
mobileLayout="default"Full compact toolbar row
mobileLayout="minimal"Indicators only in ⋯ overflow, not on main row
compactBreakpointSync UI with chart (default 600)
theme.edgeInsetExtra padding + safe areas

ChartUI calls chart.setLayoutMode("auto") on mount and re-runs fit() when the breakpoint or chart instance changes.

Compact toolbar (≤ breakpoint)

flowchart LR
Pencil["✏️ Drawing rail"]
Row["Type · Interval · ⛶ · ⋯"]
Overflow["⋯ → autoscale, scale, settings, share, currency"]

Pencil --- Row
Row --> Overflow
  • Pencil — toggles drawing-tools rail on the chart edge (no page reflow)
  • Type, interval, fullscreen, ⋯ — stay on the top row
  • ⋯ overflow — autoscale, price scale, settings, share (if on), currency
  • minimal — indicators appear only under ⋯

Fullscreen

Fullscreen targets the ChartUI container. Safe-area padding and min-height: 100dvh apply while active. Layout re-syncs after enter/exit (including visualViewport resize on mobile Safari).

Touch gestures on the chart surface

GestureResult
PanScroll along the time axis
PinchZoom (throttled per frame)
SwipeInertial scroll after release
Long pressContext menu (go to start/end, autoscale, crosshair)

Crosshair mode keeps the last position after you lift your finger (sticky crosshair). Tooltips on coarse pointers are suppressed in shared UI so labels do not get stuck.

Pinch and pan close an open long-press menu. Menu position uses viewport coordinates (clientX / clientY) so it stays under your finger when the page scrolls.

More interaction detail: Drawing and interaction, Navigation and viewport.

CSS tokens (optional)

If you build custom chrome aligned with ChartUI:

--ui-mobile-breakpoint: 600px;
--ui-toolbar-touch: 40px;

Use the same 600px in your media queries as compactBreakpoint.

What is supported vs what you still own

Works out of the box

  • Container resize + device pixel ratio
  • Compact layout in the chart core
  • Touch pan, pinch, swipe, long-press menu
  • Compact ChartUI toolbar + overflow + minimal layout
  • Safe-area-aware shell and fullscreen

You still implement

  • Viewport meta and page scroll behavior
  • Fetching data on slow networks and interval changes
  • Headers, tabs, and native WebView bridges outside ChartUI

Quick troubleshooting

ProblemCheck
Chart flat line on phoneContainer height — Step 2
Toolbar overlaps contentedgeInset and safe-area meta
Axes too wide on mobilelayout.mode: "auto" and compactBreakpoint
Pinch zoom feels stuckParent touch-action — avoid blocking on chart parent
Custom header + ChartUI clashuseChartEnvironment().isCompact to hide your chrome

Manual QA list: Mobile QA checklist.

What is next?