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.
// 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
textis the title itself, andnull, 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 whentextis set.prefixandsuffixeach take their owntext,margin,padding,backgroundStyleandtextStyle, 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,paddingandbackgroundStylewrap 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,textPaddingandtextBackgroundStyledo the same for the title text alone, inside that outer box: the darker panel behind the title above is that background.textStylecolors the text and defaults to acurrentColorfill, 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
positionputs 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.alignplaces the rowleft,center(the default) orright, andalignedToAxeschooses 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, whateveralignedToAxessays.verticalAlignsettles the boxes against each other when they differ in height, putting a shorter one at thetop, themiddle(the default) or thebottomof the row.verticalExpandchanges 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.verticalAlignthen decides where the added padding goes: under the text fortop, split formiddle, above it forbottom.
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.
// 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 }
];linkwraps the title in a link to that URL, which the host page styles like any other link: thecurrentColorfill 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 norole="button"even whenonTitleClickis supplied (see accessibility).linkDisabledkeeps the link's semantics but stops the click from navigating, which is what you want when anonTitleClickhandler is doing the work. The example sets it so that clicking the caption here does not leave the docs.- With no
linkat all, supplyingonTitleClickmakes the title a button instead: a tab stop named from the prefix, text and suffix, activated by Enter or Space.