Badge
Small coloured pill for a count or a short marker, announced as a live status region. It is the unread counter next to a room, the "Paid" marker on a plan and the version number on a file — one short string on the accent colour, sized by its content.
Use this when / not when
- Use for a count that changes — unread items, pending invites — where
role="status"andaria-live="polite"mean a screen reader hears the new number without the user going to look. - Use for a short fixed marker: a version, "New", "Paid".
- Not for something the user removes or selects — use
TagorSelectedItem, which have a close control and a real button underneath. - Not for a word or two of coloured text with no pill around it — use
Textwith acolor.
There is no status palette. No success / warning / error variant exists: the badge is
the portal's accent colour, the muted grey of isMutedBadge, or whatever you pass as
backgroundColor. Semantic colours are yours to supply.
Import
import { Badge } from "@onlyoffice/apps-ui-kit/components/badge";
Also exported from the root barrel @onlyoffice/apps-ui-kit.
Needs ThemeProvider above it in the tree for the dark theme: the dark background and the
muted grey are declared under the dark class the provider puts on <body>. Without a provider
the badge still renders, in its light colours.
Stories
Default
The badge as it usually appears: a count on the accent colour. Change any prop live in the Controls panel below, and set the label to 0 to see the badge disappear.
Badge Types
The two looks a badge can take:
- 3, New, 99+ — the usual round pill, as wide as its label, for a count or a short word
- High — the emphasised preset (
type="high"), with squarer corners and more padding, for a marker that should stand out
Special Badges
Markers with a meaning of their own:
- v1.2.3 — a version number (
isVersionBadge); on tablet-width screens and narrower it fills the width of its container - PRO — a paid-feature marker (
isPaidBadge): the text stays white and the label is never cut off - Muted — a grey pill for something inactive (
isMutedBadge)
Hover States
Hover and press each badge to compare:
- Default — the pill lightens on hover and darkens while pressed
- Hovered — shows the pointer cursor before the pointer arrives (
isHovered); the colour still changes only under the real pointer - No Hover — keeps the arrow cursor and its colour, for a badge that is only a marker (
noHover)
Custom Styled
For a place where the theme's pill does not fit:
- Custom — its own background, text colour, text size and weight, corners and padding
- Bordered — a transparent pill with a border around it (
border) - Large — a wider cap (
maxWidth) and more padding for a longer label
Interactive Badge
A badge that opens something when clicked: click it and watch the Actions panel (onClick). It takes no keyboard focus, so offer the same action somewhere a keyboard user can reach it.
Css Customization
The variables are listed under CSS variables on this page. The example is one high badge, because two of the three variables apply only to that type.
Minimal example
import { Badge } from "@onlyoffice/apps-ui-kit/components/badge";
export function UnreadCount({ count }: { count: number }) {
return <Badge label={count} noHover />;
}
Props
BadgeProps is TextProps plus the props below. Of what it inherits the component reads
backgroundColor, className, color, fontSize and fontWeight; title is consumed by the
tooltip wrapper the badge is built on, and style replaces the badge's own inline style — see
"Behaviour" below. The rest of TextProps is spread onto the element unread.
Inherited from TextProps
Declared by components/text and accepted here too.
Recipes
A counter that disappears at zero
The badge hides itself: label of 0, "0" or "" sets data-hidden="true", which is
display: none. You do not need to branch on the count.
import { Badge } from "@onlyoffice/apps-ui-kit/components/badge";
export function RoomRow({ name, unread }: { name: string; unread: number }) {
return (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span>{name}</span>
<Badge label={unread} noHover />
</div>
);
}
A clickable badge
onClick calls preventDefault() before your handler, and the badge is a <div>: it takes a
pointer but not the keyboard. When the badge is the only way to reach an action, put it inside
your own button instead of handling the click here.
import { Badge } from "@onlyoffice/apps-ui-kit/components/badge";
export function PendingInvites({
count,
onOpen,
}: {
count: number;
onOpen: () => void;
}) {
return (
<button
type="button"
onClick={onOpen}
style={{ background: "none", border: "none", padding: 0 }}
>
<Badge label={count} noHover />
</button>
);
}
A wide marker
The pill is capped at 50px, so anything longer than about four characters is cut off. Widen it
with maxWidth, or let isPaidBadge drop the cap entirely.
import { Badge } from "@onlyoffice/apps-ui-kit/components/badge";
export function PlanMarker() {
return (
<Badge
label="Business"
maxWidth="none"
padding="2px 8px"
backgroundColor="#4781d1"
noHover
/>
);
}
Behaviour the types don't state
- The badge hides itself when the label is falsy or
"0".<Badge />alone renders an empty hidden element, becauselabeldefaults to0. Anything else,"false"and" "included, is shown. - The label is clipped at 50px by default, without an ellipsis.
maxWidthcaps the pill and the outer element clips the overflow, while the text itself iswhite-space: nowrap, so a long label simply ends mid-character.isPaidBadgeis the one preset that removes the cap. stylereplaces the badge's own inline style rather than extending it. The component writesheight,border,borderRadiusand the background custom property into the element'sstyle, and astyleof yours arrives later in the spread and overwrites the whole object — including the background colour. UseclassName, or passbackgroundColor,heightandborderas props.- The pointer cursor is on by default, hover or not, and so is a background shift on hover and
on active: the pill turns 15% transparent under the pointer and takes 10% black while pressed,
muted grey included. A badge that is not clickable wants
noHover. type="high"overrides the spacing props. Its padding and radius are!importanton the pill, sopaddingandborderRadiusstop applying there; the outer element keeps the radius you passed. Its 13px / 400 text is a plain rule, sofontSizeandfontWeightstill win.- The presets override the colour props too.
isMutedBadgepaints the grey overbackgroundColor, andisPaidBadgeforces white text overcolor. isVersionBadgeonly does something at tablet widths and below, where it lets the badge take its content width; above that it changes nothing.- A
titlebecomes the kit's tooltip, not a native one: the badge is built onTooltipContainer, which consumestitleand opens the shared tooltip on hover — and that needs<RootTooltip />fromTooltipmounted once near the root of the app, or nothing appears. - An ancestor with the class
ai-agentsrepaints the badge grey and cancels its hover. That is the DocSpace portal's own hook and there is no prop for it.
CSS variables
isMutedBadge wins over --badge-bg: a muted badge stays grey whatever the variable says.
Accessibility
- The badge is a
<div>withrole="status",aria-live="polite"andaria-atomic="true", so a screen reader reads the whole badge out again whenever the label changes. That is what you want for a counter and wrong for a static marker in a long list — passroleandaria-liveyourself to override them. - Its accessible name is built as
`${label} ${type ?? ""}`, which leaves a trailing space and says nothing about what is being counted. A badge that has to stand on its own needs anaria-labelof yours; it reaches the element through the spread. - The pill and the label are
aria-hidden, so the name above is all that is announced. onClickdoes not make the badge operable: there is no role, notabIndexand no key handler. Put it inside a real button.
Test ids
| Element | data-testid |
|---|---|
| Outer element | badge, overridden by dataTestId |
| The pill | badge-inner |
| The label | badge-text |
The outer element also carries data-hidden, data-type, data-paid, data-muted,
data-version-badge, data-no-hover and data-is-hovered, which is what to assert on rather
than the computed style.
Related
Tag— a chip the user can click or remove.SelectedItem— a chosen value with a close cross.Text— coloured text with no pill around it.