GrBadgeWrap
Adds a status or count badge on top of an element.
Overview
When to take it
- the counter belongs to the control — unread items on a mail icon, goods in a basket, tasks on a tab;
- the fact matters, not the number —
dotshows a dot with no digit; - the number may be large —
maxfolds it into “99+”; - zero has to be shown —
showZero; by default the counter disappears rather than drawing “0”.
When to take something else
| Need | Take |
|---|---|
| The label sits in the flow, not on top of a control | GrBadge |
| A presence status on an avatar | GrAvatar |
| The counter is part of a tab | GrTabs with badge |
| The counter is part of a navigation section | GrBottomNav with badge |
Variants every tone and view, rendered by the real component
tone
primaryneutralsuccesswarningdangerinfoslateazure
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrBadgeWrap :value="3"><GrButton variant="outline">Inbox</GrButton></GrBadgeWrap>Examples 4 each with a live demo and its code
The header of an app: how many, how urgent
The tone carries the urgency: red is left for alerts that need action today, the long queue of messages is the calm primary.
maxfolds 128 into «99+», andaria-labelkeeps the real number for the screen reader. A fact without a number is adot, and on a round avatar it sits on the circle by itself.Acme Billing 128 unread messages3 alerts need actionALBilling details are missingNew messages and mentions, two corners of one tile
A workspace with mentions usually has new messages too, and the rail shows both: the dot at the top says «something happened», the number at the bottom —
placement="bottom-right"— says how many times you were called. Open a workspace and its marks move into the channel list, where the count stands in the line and is aGrBadge.Acme BillingA counter that pops only for news
animatemarks what the buyer does not know yet: the counter appearing or growing. Removing an item is their own action, and the number falls quietly; at zero the counter leaves the icon altogether. The calmneutraltone suits a cart — nothing in it is an alarm.Northwind Office1 items in the cartOffice chair, oak€349 · ships in 3 daysIn the cart: €349Zero is the answer
Reports saved without signal wait in the outbox. By default a zero removes the counter, but here «nothing waits to be sent» is the very thing an inspector checks before leaving the site:
show-zerokeeps it, and the tone turns from warning to success. Sync drains the queue without a pop; a new report brings it back.Bremen yardReport #2041 Outbox 2 reports waiting to be sent2 waiting · last sync 09:40
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
placement | "top-right" | "top-left" | "bottom-right" | "bottom-left" | undefined | "top-right" | The corner the label hangs in. `top-right` by default. The offset from the corner is set by the `--gr-badge-wrap-offset-x/-y` variables. |
tone |
| "danger" | The colour of the label from the tone scale: the fill is the tone role, the text is its `-fg`. `danger` by default. |
ariaLabel | string | undefined | undefined | The label of the counter for a screen reader. If it is not set, it comes from the locale. |
value | string | number | undefined | undefined | The value of the counter: a number or a short string ("NEW"). Not set — there is no counter; zero is hidden until `showZero` is on. |
dot | boolean | undefined | false | A dot instead of a number: pure decoration unless `ariaLabel` is set. |
max | number | undefined | undefined | The threshold: values above it are drawn as "{max}+". |
showZero | boolean | undefined | false | Whether to show a zero value. By default zero is hidden. |
animate | boolean | undefined | false | Whether to mark the appearance and the growth of the **counter** with an animation. A dot has nothing to animate. |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | The element the label is attached to. |
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 { GrBadgeWrapProps, GrBadgeWrapTone, GrBadgeWrapPlacement } from '@feugene/granularity/components/GrBadgeWrap'Filedist/types/components/GrBadgeWrap/GrBadgeWrap.vue.d.ts
interface GrBadgeWrapProps {
value?: string | number;
dot?: boolean;
max?: number;
showZero?: boolean;
tone?: GrBadgeWrapTone;
placement?: GrBadgeWrapPlacement;
ariaLabel?: string;
animate?: boolean;
}Filedist/types/components/GrBadgeWrap/config.d.ts
const grBadgeWrapConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrBadgeWrap">;Filedist/types/components/GrBadgeWrap/grBadgeWrapStyles.d.ts
type GrBadgeWrapTone = GrTone;type GrBadgeWrapPlacement = typeof GR_BADGE_WRAP_PLACEMENTS[number];function formatBadgeValue(value: string | number, max?: number): string;Filedist/types/components/GrBadgeWrap/safelist.d.ts
const grBadgeWrapSafelist: string[];Usage from the library prose
The counter
<GrBadgeWrap :value="3">
<GrButton size="sm" variant="outline">Inbox</GrButton>
</GrBadgeWrap>The number is drawn decoratively, with a visually hidden label beside it —
otherwise a screen reader would read “Inbox, 3” without a unit, or would not read
the counter at all. The text comes from the locale (gr.badgeWrap.count), and
ariaLabel overrides it.
Zero is not shown by default: an empty circle on an icon reads as a defect.
showZero brings it back when zero is a meaningful state (“0 drafts”).
Trimming large numbers
<GrBadgeWrap :value="120" :max="99">
<GrButton size="sm" variant="outline">Inbox</GrButton>
</GrBadgeWrap>What is seen is “99+”, and what is announced is the real number: “120 unread”. “99 plus” tells the user nothing.
The function is available separately: import { formatBadgeValue } from '@feugene/granularity'.
The dot
<GrBadgeWrap dot aria-label="There are unread items">
<GrAvatar :size="40">AD</GrAvatar>
</GrBadgeWrap>The dot carries no number and stays decorative until it is given an ariaLabel —
then the fact of the event is announced with a word. It suits the case where
“needs attention” matters and “how many exactly” does not.
A child is mandatory. The badge is positioned absolutely relative to the root, and a
root without content has zero size: the dot would be drawn around an empty spot, take up
nothing in the layout and overlap its neighbours. If a status marker is needed on its own,
in the flow — that is the dot of GrBadge.
Animating the counter
<GrBadgeWrap :value="unread" animate>
<GrButton size="sm" variant="outline">Inbox</GrButton>
</GrBadgeWrap>animate marks with a “pop” what the user does not know about yet: the counter
appearing and the number growing. A decrease is the trace of the user’s own
action, and highlighting it means flashing the badge on every letter read.
| Transition | What happens |
|---|---|
| there was no counter → it appeared | a “pop” |
| the number grew | a “pop” |
| the number went down | silently |
| the counter disappeared | silently |
| the first render of the page | silently |
A string value (value="NEW") has no order, so any change of it counts as an
event.
A batch of changes gives one “pop”: while the animation plays, a new value does not restart it. Five letters that arrive within a second are marked with a single movement rather than with flickering.
animate is off by default. Whether the animation is appropriate is known by the
application: a counter that changes once an hour and a counter that changes
constantly are different cases, and the library picks the quiet one for the
consumer.
The dot (dot) takes no part in the animation: it has no value, and “growth” is
not defined for it.
The movement obeys prefers-reduced-motion through the global clamp of the
package — the details are in motion.md. The duration and the
curve come from --gr-duration-base and --gr-ease-out, that is, they are set by
the theme along with all the rest of the movement.
Tone and placement
| Prop | Values | Default |
|---|---|---|
tone | the whole GR_TONES scale (neutral, primary, success, warning, danger, info, slate, azure) | danger |
placement | top-right | top-left | bottom-right | bottom-left | top-right |
The text colour comes from the paired token --gr-<tone>-fg rather than from a
white literal: the badge survives a change of theme.
The offset from the corner is set with variables — by the theme or inline:
<GrBadgeWrap :value="3" style="--gr-badge-wrap-offset-x: -0.75rem" />| Variable | Default (counter / dot) |
|---|---|
--gr-badge-wrap-offset-x | -0.5rem / 0 |
--gr-badge-wrap-offset-y | -0.5rem / 0 |
By default the dot does not reach beyond the frame: the corner of the frame is empty on a
rounded button and on a round avatar, and a dot centred on it would hang next to the control.
With a zero offset it sits on the arc of the rounding or on the edge of the circle — like the
status dot of GrAvatar. The counter is larger and covers the rounding anyway.
Why not GrBadge inside
The wrapper draws its own minimal badge deliberately: otherwise every counter would drag a full-size component along with it, with its own scale of sizes and radii, for the sake of a circle 20 pixels across.
Props in this example