Skip to content

Chart title

The title is a row of up to three text boxes, a prefix, the text itself and a suffix, drawn above or below the plot. Its config covers where that row sits (position, align, alignedToAxes), how the boxes are spaced and filled (margin, padding, backgroundStyle, textStyle), what happens when the text is too wide for the chart, and whether the title is a link.

ts
// A title using all three of its boxes: a prefix badge, the title text and a
// suffix note, boxed together by the title's own background and aligned to
// the left edge of the plot. verticalExpand grows the shorter boxes to the
// tallest one, so the badge and the note line up with the text.
import type { MochartInputConfig } from '@mochart/core';

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: {
    text: 'Warehouse Throughput',
    align: 'left',
    alignedToAxes: true,
    verticalAlign: 'middle',
    verticalExpand: true,
    margin: { top: 0, right: 0, bottom: 10, left: 0 },
    padding: { top: 3, right: 4, bottom: 3, left: 4 },
    backgroundStyle: {
      strokeColor: 'currentColor',
      strokeOpacity: 0.2,
      strokeWidth: 1,
      fillColor: 'currentColor',
      fillOpacity: 0.03
    },
    textMargin: { top: 0, right: 8, bottom: 0, left: 8 },
    textPadding: { top: 3, right: 6, bottom: 3, left: 6 },
    textBackgroundStyle: { fillColor: 'currentColor', fillOpacity: 0.05 },
    textStyle: { fillColor: 'currentColor', fillOpacity: 0.9 },
    prefix: {
      text: 'FY26',
      padding: { top: 0, right: 6, bottom: 0, left: 6 },
      backgroundStyle: { fillColor: '#3e63dd', fillOpacity: 0.9 },
      textStyle: { fillColor: '#ffffff' }
    },
    suffix: {
      text: 'pallets per week',
      padding: { top: 0, right: 2, bottom: 0, left: 2 },
      textStyle: { fillColor: 'currentColor', fillOpacity: 0.55 }
    },
    truncation: { enabled: true, text: '…' }
  },
  categoryAxis: { property: 'week', type: 'string', scale: 'ordinal' },
  valueAxes: [{ id: 'VA0', title: { text: 'Pallets' }, min: 0 }],
  seriesDefaults: { renderer: 'bar' },
  series: [
    { property: 'inbound', title: 'Inbound' },
    { property: 'outbound', title: 'Outbound' }
  ],
  seriesGroups: [{ id: 'flow' }]
};

export const data = [
  { week: 'W1', inbound: 320, outbound: 295 },
  { week: 'W2', inbound: 348, outbound: 310 },
  { week: 'W3', inbound: 336, outbound: 342 },
  { week: 'W4', inbound: 372, outbound: 351 },
  { week: 'W5', inbound: 359, outbound: 368 },
  { week: 'W6', inbound: 391, outbound: 377 }
];

The three boxes

  • text is the title itself, and null, the default, means no title: the row is not drawn and takes no height from the plot. The prefix and the suffix are drawn only when text is set.
  • prefix and suffix each take their own text, margin, padding, backgroundStyle and textStyle, which is what makes the badge and the grey note in the example above. They keep the width their text needs and sit either side of the title text on one line.
  • The title's own margin, padding and backgroundStyle wrap all three boxes together, in the way layout and spacing describes: the background fills the box inside the margin, and the padding lies between that background and the text boxes. Both default to 5px on the bottom side only, which is the gap under a top-positioned title.
  • textMargin, textPadding and textBackgroundStyle do the same for the title text alone, inside that outer box: the darker panel behind the title above is that background.
  • textStyle colors the text and defaults to a currentColor fill, so the title follows the host page (see theming). No config property sets the font: the text inherits the page's font and size, which you can override in CSS on .mochart-title-text, and the chart measures whatever it renders.

Placing the title

  • position puts the row above the plot (top, the default) or below it (bottom). It shares that row with the legend, and the title always comes first: with both at the bottom, the title sits directly under the plot and the legend below it.
  • align places the row left, center (the default) or right, and alignedToAxes chooses what it is aligned within: the plot area between the axes (true, the default) or the full chart width (false). A title too wide for the space between the axes is aligned to the chart width instead, whatever alignedToAxes says.
  • verticalAlign settles the boxes against each other when they differ in height, putting a shorter one at the top, the middle (the default) or the bottom of the row.
  • verticalExpand changes that from a position into a stretch: with it on, each shorter box grows its padding until its padded box matches the tallest one, so the backgrounds share a height. That is why the badge in the example above is as tall as the title text. verticalAlign then decides where the added padding goes: under the text for top, split for middle, above it for bottom.

When the title does not fit

truncation.enabled is on by default: a title wider than the row it is aligned within is cut and truncation.text () is appended. Only the text section is cut. The prefix and the suffix keep their full width, and if those two alone fill the row the text is dropped rather than squeezed. Turning truncation off means nothing is cut, so a long title runs past the edge of the chart.

Cutting the text changes only what is drawn. The chart's accessible name and the name a clickable title is announced by both use the full text you set.

A pointer can still get at the full text: truncation.tooltipEnabled (on by default) gives the truncated title an svg <title> holding the full text, which browsers show as their native tooltip while a mouse or pen rests on it. Touch has no hover, so nothing shows there.

A caption under the chart

The same row makes a source note when it is moved below the plot, aligned to the chart rather than the axes, and left without a background.

ts
// The title moved under the plot and used as a source note: no background,
// aligned to the chart bounds rather than to the axes, and linked. The link
// is disabled here so the docs page does not navigate away when it is
// clicked; an onTitleClick handler still runs.
import type { MochartInputConfig } from '@mochart/core';

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: {
    text: 'Source: weekly operations report',
    position: 'bottom',
    align: 'left',
    alignedToAxes: false,
    link: 'https://example.com/reports/throughput',
    linkDisabled: true,
    margin: { top: 8, right: 0, bottom: 0, left: 0 },
    padding: { top: 0, right: 0, bottom: 0, left: 0 },
    textStyle: { fillColor: 'currentColor', fillOpacity: 0.6 }
  },
  legend: { position: 'bottom' },
  categoryAxis: { property: 'week', type: 'string', scale: 'ordinal' },
  valueAxes: [{ id: 'VA0', title: { text: 'Pallets' }, min: 0 }],
  seriesDefaults: { renderer: 'line' },
  series: [
    { property: 'inbound', title: 'Inbound' },
    { property: 'outbound', title: 'Outbound' }
  ]
};

export const data = [
  { week: 'W1', inbound: 320, outbound: 295 },
  { week: 'W2', inbound: 348, outbound: 310 },
  { week: 'W3', inbound: 336, outbound: 342 },
  { week: 'W4', inbound: 372, outbound: 351 },
  { week: 'W5', inbound: 359, outbound: 368 },
  { week: 'W6', inbound: 391, outbound: 377 }
];
  • link wraps the title in a link to that URL, which the host page styles like any other link: the currentColor fill above is what picks up this site's link color. The link is keyboard-reachable on its own, which is why a linked title gets no role="button" even when onTitleClick is supplied (see accessibility).
  • linkDisabled keeps the link's semantics but stops the click from navigating, which is what you want when an onTitleClick handler is doing the work. The example sets it so that clicking the caption here does not leave the docs.
  • With no link at all, supplying onTitleClick makes the title a button instead: a tab stop named from the prefix, text and suffix, activated by Enter or Space.

Released under the MIT License.