GrDelta
Shows a signed value with its sign, tone and arrow inline in a sentence.
Overview
When to take it
- a signed number stands inside a sentence — in the description of an operation, in a table cell, in the caption under a chart;
- the sign decides the colour — growth, decline and “unchanged” are seen before the quantity itself is read;
- growth can be bad —
polarity="negative-good"for cost, response time and churn; - the quantity may be missing — “no data” and “zero” are drawn differently.
When to take something else
| Need | Take |
|---|---|
| A large metric as a tile, with a label and a trend | GrStatistic |
| A number as a status or a label | GrBadge |
| A share of a whole as a bar or a ring | GrProgressBar / GrProgressCircle |
| A series of values over time | GrSparkline |
| A “property → value” pair | GrDescriptionList |
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrDelta :value="12.4" />Examples 4 each with a live demo and its code
A change inside a sentence
At the default size the delta has no font size of its own: inside a heading it is large, inside a sentence it is the size of the sentence, and the arrow grows with the number. Zero is «no change» and stays neutral.
Revenue in September +8.4%
Recurring revenue reached €48,920, +8.2% week over week. Trial conversion slipped by -1.2 pp, and seats per account stayed at 4.2 — 0%, which is neither good nor bad news.
No data is not zero
nullprintsempty-textwith no tone, arrow or affixes — «€—» would read as zero euros. The sign comes fromIntl, solocalemoves the separators and the sign still lands before the currency.- Germany+12.6%
- Poland0.0%
- Sweden-3.4%
- Portugalnew market
en-GB+€1,284.50de-DE+1.284,50 €fr-FR+1 284,50 €
When growth is bad news
polarity="negative-good"flips the tone for cost, latency and churn, but never the sign: a cost that fell is still −€0.42, and it is green.nonedrops the tone where the sign is not a verdict.Metric This month Change polarityRevenue €92,350 +6.3% positive-good· more is betterCost per order €3.18 -€0.42 negative-good· less is betterLatency, p95 412 ms +38 ms negative-good· less is betterMonthly churn 2.1 % +0.4 pp negative-good· less is betterCash balance shift €18,400 -€1,240 none· the sign is not a verdictSteps for a row of controls, hooks for the rest
Next to controls
size="sm"matches them instead of the text around.--gr-delta-suffix-colorand--gr-delta-arrow-sizemute a unit or enlarge the arrow, and the publicdeltaTonegives a badge the same verdict as the delta.Orders +4.6% Refunds +0.3 ppLatency -12msFaster than last week
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | The type size by the controls scale. `md` by default — the type size of the surrounding line; `xs`, `sm`, `lg` are needed when the quantity stands in a row with controls. |
emptyText | string | undefined | undefined | What to print a missing quantity with. |
locale | string | undefined | undefined | The locale of the formatting. Unset — the locale of the i18n adapter. |
precision | number | undefined | undefined | Digits after the decimal point. Unset — as the locale gives it. |
prefix | string | undefined | undefined | An affix before the number: a currency, a unit. |
suffix | string | undefined | undefined | An affix after the number: `%`, `ms`. |
polarity | GrDeltaPolarity | undefined | undefined | What counts as good. For revenue growth is a success, for cost and response time it is the opposite; without this the component would lie in half of the cases. |
showSign | boolean | undefined | undefined | Put a `+` on positive values. The minus is set by `Intl` itself. |
showArrow | boolean | undefined | undefined | An arrow of direction. Decorative: the direction is already in the sign. |
valuerequired | number | null | — | The quantity. `null` means there is none; that is not a zero. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrDeltaPolarity, GrDeltaTone, GrDeltaProps } from '@feugene/granularity/components/GrDelta'Filedist/types/components/GrDelta/config.d.ts
const grDeltaConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrDelta">;Filedist/types/components/shared/deltaTone.d.ts
type GrDeltaPolarity = 'positive-good'type GrDeltaTone = 'success' | 'danger' | 'neutral';function deltaTone(value: number, polarity?: GrDeltaPolarity): GrDeltaTone;function deltaDirection(value: number): 'up' | 'down' | 'flat';Filedist/types/components/GrDelta/GrDelta.vue.d.ts
interface GrDeltaProps {
value: number | null;
precision?: number;
prefix?: string;
suffix?: string;
polarity?: GrDeltaPolarity;
showSign?: boolean;
showArrow?: boolean;
emptyText?: string;
locale?: string;
size?: GrComponentSize;
}Filedist/types/components/GrDelta/defaults.d.ts
interface GrDeltaConfigurableProps {
size: GrComponentSize;
polarity: GrDeltaPolarity;
showSign: boolean;
showArrow: boolean;
emptyText: string;
}Filedist/types/components/GrDelta/safelist.d.ts
const grDeltaSafelist: string[];Usage from the library prose
Zero is neutral, null is not zero
Two states that are not expressed by two colours, and both have already led to bugs in applications:
- zero — “unchanged”. Neither a success nor an error: the tone is neutral with
any
polarity. A condition of the formvalue < 0 ? danger : successpaints a zero movement green, like an income; null— there is no quantity at all. It is printed as a dash (emptyText), with no tone, no arrow and no affixes:$—reads as “zero dollars”.
The choice of tone lives in a pure function of its own and is covered by tests
without mounting — it is exactly that switch that used to spread through an
application in copies.
The function is public: deltaTone(value, polarity) and deltaDirection(value)
are given away by the package. They are needed where the markup of a delta does not
fit but the rule is the same — the tone of GrStatistic and
GrBadge in the tiles of a report. Rewritten in an application, it
diverges from this one at the very first edit — which is what happened with the zero
painted green like an income.
Polarity inverts the tone but not the sign
<GrDelta :value="-15" suffix="%" polarity="negative-good" />Cost fell by 15 % — that is a success, and the quantity is green. But it is still
−15 %: the sign belongs to the number, not to the judgement. The arrow follows the
sign as well — downwards, because the quantity decreased.
polarity="none" removes the tone entirely: for a balance or an offset the sign says
nothing about quality.
The sign is set by Intl, not by string concatenation
showSign switches on signDisplay: 'exceptZero' in the formatting rather than
appending a '+' to a ready string. The difference is not stylistic: concatenation
has already broken GrStatistic — the string '+1,234' stops being a number, and
the digit groups are lost at the next step.
The locale comes from the i18n adapter unless it is set with a prop.
The sign stands before the currency
Together with prefix the sign moves into a node of its own before the affix:
+$0,0280, not $+0,0280. The sign belongs to the quantity as a whole rather than
to the number after the currency symbol, and the second order is accepted in no
typographic tradition.
The invariant above is not weakened in the process: the sign is still set by Intl,
the component merely takes it out of formatToParts — the string is not
concatenated and the digit groups are not reassembled. Without prefix there is
nowhere to take it out to, and the markup stays as it was.
There is deliberately no way to configure the position of the sign. A separate subtlety is RTL: there the sign is preceded by an invisible direction mark, which is what turns it around relative to the digits; it moves together with the sign, because it is the sign it governs.
The affixes themselves are drawn by GrValue — the primitive shared
with GrStatistic. Their styling and the --gr-value-* tokens come from there too:
a currency on the left is set like a number, a unit on the right is dimmed. A currency
on the right (+1 000 ₽) is obtained by changing the default of the suffix, and the
recipe is on the page of the primitive.
Tuning points of its own
The delta has four hooks of its own (--gr-delta-*, the full list is in
../tokens.md), and they did not appear for the sake of symmetry.
The delta does not dim the affix: “−15 %” is set in the type size and colour of the line
as a whole, otherwise the percent reads as a caption beside the number. That is an override
of the default of GrValue, and it is applied with an inline style — and inline beats any
CSS of the consumer. Had the component set the colour as a literal, there would be nothing
left to bring the dimming back with; the override therefore refers to
--gr-delta-suffix-color and --gr-delta-suffix-size, leaving the decision to whoever set
them:
<GrDelta :value="-15" suffix="%" style="--gr-delta-suffix-color: var(--gr-muted-fg)" />The arrow is tuned by --gr-delta-arrow-size and --gr-delta-arrow-gap. The size is a
fraction of the type size of the quantity rather than a step of the scale: at md the delta
has no type size of its own at all, and a pixel value would have left the arrow tiny inside
a heading. The spacing is logical, so in RTL it stands on the proper side.
The type size comes from the line
The default step (md) does not set font-size at all: the quantity is set in the
type size of the line it stands in. Inside a heading it is large, in a caption under a
chart it is small, in a table cell it is exactly like the rest of the text of the
cell — and all of that without a single prop.
<h2 class="text-[length:var(--gr-text-3xl)]">
Revenue for March <GrDelta :value="8.4" :precision="1" suffix="%" show-arrow />
</h2>The edges of the ladder stay explicit and take the control scale — xs 12 px, sm
13 px, lg 16 px. They are needed in the other case: when the quantity stands not
inside a sentence but in a row with controls, and has to match them rather than the
surrounding text.
The arrow is set in em and grows together with the number — it has no step of its
own. When overriding the type size from the outside, override the pair:
the component does not touch the line height, and a half-measure leaves the line with
someone else’s leading.
The arrow is decorative
showArrow draws the direction but marks the icon aria-hidden: the direction is
already announced by the sign, and there is no point duplicating it for a screen
reader. There is no arrow by default — in a line of text it more often adds noise than
helps.
Limits
The component does not compute the delta: it accepts a ready one. “Compared to what” — the period, the base value, the filters — is known by the application, and dragging that into a presentational component means dragging its data model in there too. Percentages, comparisons of periods and a sparkline are outside as well.
Props in this example