Skip to content

Formatting Presets

Formatting presets help Pivot reports show numbers, money, percentages, and dates in a readable way. Conditional formatting presets then help users spot high revenue, low margin, or other important analytical values.

Open the full demo and framework source.

Formatting changes display, not input eligibility or arithmetic. Percentage calculations store ratios (0.5 means 50%); a percentage format does not divide the value again. Configure null/invalid/count semantics through Aggregation policies and preserve those operations when saving reports.

Import the helpers from @revolist/pivot and create reusable formatters for the value types in your report:

import {
createPivotValueFormatter,
formatPivotValue,
pivotConditionalFormattingPresets,
createPivotConditionalCellProperties,
PivotPlugin,
type PivotConfig,
} from '@revolist/pivot';
const revenueFormatter = createPivotValueFormatter({
preset: 'currency',
currency: 'USD',
locale: 'en-US',
options: { maximumFractionDigits: 0 },
});
const marginFormatter = createPivotValueFormatter({
preset: 'percent',
locale: 'en-US',
options: { minimumFractionDigits: 1, maximumFractionDigits: 1 },
});

createPivotValueFormatter() returns a function. Use it when the same formatting rule is reused by many cells. Use formatPivotValue() when you only need to format one value:

const formattedLabel = formatPivotValue(null, {
preset: 'date',
nullDisplay: 'No close date',
});
Interactive example

Formatting presets

Null and invalid display fallback without changing aggregate data

The example loads when visible.

Pivot formatters are display helpers. They do not change the original source data or the aggregated number that Pivot calculates. Keep your source values as real numbers and dates, then format them at render time.

const columnTypes = {
pivotCurrency: {
cellTemplate: (_h, props) => revenueFormatter(props.value),
cellProperties: () => ({ class: { 'align-right': true } }),
},
pivotPercent: {
cellTemplate: (_h, props) => marginFormatter(props.value),
cellProperties: () => ({ class: { 'align-right': true } }),
},
};

Attach those column types to the Pivot dimensions that produce analytical value cells:

const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'closeDate', name: 'Close date', columnType: 'pivotDate' },
{ prop: 'segment', name: 'Segment' },
{ prop: 'revenue', name: 'Revenue', columnType: 'pivotCurrency' },
{ prop: 'marginRate', name: 'Margin', columnType: 'pivotPercent' },
],
rows: ['region', 'closeDate'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue' },
{ prop: 'marginRate', aggregator: 'avg', label: 'Avg margin' },
],
};

The formatter presets use the browser Intl formatters. Choose the preset that matches the value:

  • number: plain numeric values such as counts, units, or scores.
  • currency: money values. Always provide currency, for example "USD" or "EUR".
  • percent: ratio values such as 0.315, displayed as 31.5%.
  • date: date-only values.
  • datetime: date and time values.
const integerFormatter = createPivotValueFormatter({
preset: 'number',
locale: 'en-US',
options: { maximumFractionDigits: 0 },
});
const eurFormatter = createPivotValueFormatter({
preset: 'currency',
currency: 'EUR',
locale: 'de-DE',
});
const dateFormatter = createPivotValueFormatter({
preset: 'date',
locale: 'en-US',
options: { month: 'short', day: '2-digit', year: 'numeric' },
});

Use pivotConditionalFormattingPresets to describe matching rules, and createPivotConditionalCellProperties() to turn those rules into a RevoGrid cellProperties function.

const conditionalCellProperties = createPivotConditionalCellProperties(
[
pivotConditionalFormattingPresets.gt(300000, {
field: 'revenue',
style: {
backgroundColor: 'rgba(22, 163, 74, 0.16)',
color: '#166534',
fontWeight: '600',
},
}),
pivotConditionalFormattingPresets.lt(0.28, {
field: 'marginRate',
style: {
backgroundColor: 'rgba(220, 38, 38, 0.14)',
color: '#991b1b',
fontWeight: '600',
},
}),
pivotConditionalFormattingPresets.between(0.28, 0.34, {
field: 'marginRate',
style: {
backgroundColor: 'rgba(234, 179, 8, 0.14)',
color: '#854d0e',
},
}),
],
{ match: 'all' },
);

Available presets:

  • gt(value, props): greater than a number.
  • lt(value, props): less than a number.
  • between(min, max, props): between two numbers, including both bounds.
  • equal(value, props): equal to a value.
  • textContains(value, props): contains text.

The optional field narrows a rule to a generated value field. For example, field: 'revenue' applies to generated revenue cells even when Pivot creates column paths such as Software|revenue.

By default, createPivotConditionalCellProperties() only formats Pivot analytical cells. These are generated cells from the values area, not row header fields such as region or closeDate.

This default prevents a rule like gt(300000) from accidentally styling row dimensions that happen to contain comparable values. If you intentionally want rules to apply outside analytical value cells, enable includeNonAnalytical:

const allCellProperties = createPivotConditionalCellProperties(rules, {
includeNonAnalytical: true,
});

Most Pivot reports should keep the default and attach conditional formatting to value dimensions:

const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{
prop: 'revenue',
name: 'Revenue',
columnType: 'pivotCurrency',
cellProperties: conditionalCellProperties,
},
{
prop: 'marginRate',
name: 'Margin',
columnType: 'pivotPercent',
cellProperties: conditionalCellProperties,
},
],
rows: ['region'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum' },
{ prop: 'marginRate', aggregator: 'avg' },
],
};

This report groups opportunities by region and close date, splits generated value columns by segment, formats revenue as currency, formats margin as a percent, and highlights high revenue or weak margin.

const rows = [
{ region: 'North America', closeDate: '2026-01-16', segment: 'Software', revenue: 184000, marginRate: 0.37 },
{ region: 'North America', closeDate: '2026-01-22', segment: 'Services', revenue: 126000, marginRate: 0.31 },
{ region: 'EMEA', closeDate: '2026-02-06', segment: 'Services', revenue: 118000, marginRate: 0.28 },
];
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'closeDate', name: 'Close date', columnType: 'pivotDate' },
{
prop: 'revenue',
name: 'Revenue',
columnType: 'pivotCurrency',
cellProperties: conditionalCellProperties,
},
{
prop: 'marginRate',
name: 'Margin',
columnType: 'pivotPercent',
cellProperties: conditionalCellProperties,
},
],
rows: ['region', 'closeDate'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue' },
{ prop: 'marginRate', aggregator: 'avg', label: 'Avg margin' },
],
totals: {
subtotals: true,
grandTotal: true,
},
groupLabels: {
null: formatPivotValue(null, { preset: 'date', nullDisplay: 'No close date' }),
},
};
const grid = document.querySelector('revo-grid');
grid.columnTypes = columnTypes;
grid.plugins = [PivotPlugin];
grid.pivot = pivot;
grid.source = rows;
  • Formatting the source data before Pivot aggregates it. Keep values numeric so sum, avg, and comparisons work correctly.
  • Using preset: 'percent' with whole percentages. 31.5% should usually be stored as 0.315, not 31.5.
  • Forgetting currency when using the currency preset.
  • Expecting conditional formatting to affect row fields by default. It targets analytical value cells unless includeNonAnalytical is enabled.
  • Omitting field on a conditional rule when the report has several value fields. Without field, the rule can match every analytical value cell.
  • Expecting cellTemplate formatting to change conditional comparisons. Conditional rules evaluate the raw cell value, not the formatted display string.

Continue with Sorting And Filtering or Configuration Reference.