Skip to content

PlBadge

A small mark in the corner of something else, unread mail on an inbox icon, a status dot on an avatar, a count on a tab. With no children it lays out inline instead, which is what a standalone status pill is.

React
tsx
import { PlBadge, PlButton } from 'plass-ui';

<PlBadge content={4} label="4 unread notifications">
  <PlButton aria-label="Notifications">
    <BellIcon />
  </PlButton>
</PlBadge>;
dart
import 'package:plass_ui/plass_ui.dart';

PlBadge(
  count: 4,
  label: '4 unread notifications',
  child: PlButton(semanticLabel: 'Notifications', startIcon: const BellGlyph(), onPressed: open),
);

Props

PropTypeDefaultDescription
variantshared'solid' | 'glass' | 'ghost''solid'What the marker is made of. The marker is the thing being coloured, so its sheet takes the tint. ghost is the one for a busy surface
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The marker height. Its own ladder, well below the control one — md is 18px, the smallest a two-digit number stays legible at
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'The room around the digits, and nothing else
elevationshared0 | 1 | 2 | 30Drop shadow depth. 0 means no shadow at all
contentReactNodeWhat the badge says — usually a count, sometimes a word. Omit it and it draws a dot, which is the honest thing when there is something to report but nothing to count
maxnumber99Caps a numeric content and adds a +. Numbers only: a badge cannot know how to truncate a word
dotbooleanfalseDraws the marker as a dot even with content, keeping the content for screen readers only. For the corner that has to stay quiet
showZerobooleanfalseWhether a content of 0 is shown. Zero unread messages is not news, and a badge that never goes away stops meaning anything
invisiblebooleanfalseHides the marker without unmounting the anchor. It keeps its box, so showing it again does not relayout what it sits on
placementshared'top-start' | 'top-end' | 'bottom-start' | 'bottom-end''top-end'Which corner of the anchor it sits on. Logical properties, so it flips with the writing direction
overlap'square' | 'circle''square'The shape of the thing underneath. A circle’s corner is further from its centre, so a badge tuned for an icon button hangs off an avatar
labelstringWhat a screen reader hears instead of the raw content. A 3 beside a bell is just "3"; "3 unread notifications" is the sentence
childrenReactNodeWhat the badge is pinned to. Without it the badge is a standalone marker that lays out inline
PropTypeDefaultDescription
variantsharedPlassVariantPlassVariant.solidWhat the marker is made of. The marker is the thing being coloured, so its sheet takes the tint. ghost is the one for a busy surface
sizesharedPlassSizePlassSize.mdThe marker height. Its own ladder, well below the control one — md is 18px, the smallest a two-digit number stays legible at
colorsharedPlassColorPlassColor.primarySemantic colour role. Arbitrary colour values are not accepted
densitysharedPlassDensityPlassDensity.standardThe room around the digits, and nothing else
elevationsharedint0Drop shadow depth. 0 means no shadow at all
contentWidget?What the badge says when it is not a number. Cannot be given alongside count
countint?What the badge counts. Separate from content because max and showZero only mean anything for a number — the type is the question
maxint99Caps a numeric content and adds a +. Numbers only: a badge cannot know how to truncate a word
dotboolfalseDraws the marker as a dot even with content, keeping the content for screen readers only. For the corner that has to stay quiet
showZeroboolfalseWhether a content of 0 is shown. Zero unread messages is not news, and a badge that never goes away stops meaning anything
invisibleboolfalseHides the marker without unmounting the anchor. It keeps its box, so showing it again does not relayout what it sits on
placementsharedPlassCornerPlassCorner.topEndWhich corner of the anchor it sits on. Logical properties, so it flips with the writing direction
overlapPlBadgeOverlapPlBadgeOverlap.squareThe shape of the thing underneath. A circle’s corner is further from its centre, so a badge tuned for an icon button hangs off an avatar
labelString?What a screen reader hears instead of the raw content. A 3 beside a bell is just "3"; "3 unread notifications" is the sentence
childWidget?What the badge is pinned to. Without it the badge is a standalone marker that lays out inline

Every native <span> attribute passes straight through, onto the marker rather than onto the shell around the anchor. color and content are excluded from the pass-through because both are Plass props here.

content and count are two parameters rather than one. max and showZero only mean anything for a number, and React's single prop has to ask at runtime what it was handed; here the type is the question, and passing both is an error the constructor asserts on.

What the shared axes (variant size color density elevation) mean across the library is in prop conventions.

Examples

content, max and showZero

contentcount is usually a count and sometimes a word. A number past max is capped with a +; a word is left alone, because a badge cannot know how to truncate one.

A count of 0 draws nothing at all unless showZero is on. Zero unread messages is not news, and a badge that never goes away stops meaning anything.

An invisible badge keeps its box, a Visibility with maintainSize on, so nothing around it moves when it comes back. Visibility rather than opacity: a half-faded badge is a badge you have to squint at to find out whether it is there.

React

dot

Omit the content and the badge is a dot, the honest shape when there is something to report but nothing to count. dot forces it even when there is content, and the content is still announced: a quiet corner is not a silent one.

React

variant

A badge is the thing being coloured, so its sheet takes the tint, as an alert's does, and unlike a card's. It is also the one component in the library allowed to be a pill: a Plass corner is a moulded fillet on a surface, and a badge is a mark laid on one.

React

placement and overlap

placement is logical throughout, so the corner flips with the writing direction rather than staying stuck on the right.

Pinned with a directional Stack rather than a transform, which is the same choice the React build makes with a negative margin: the house rule against moving a control with a transform is absolute, and a corner is two alignments and a pair of insets either way.

overlap is the shape of the thing underneath. A circle's corner is about 15% of its diameter inside the box the badge is positioned against, so a badge tuned for an icon button floats off an avatar with a gap under it.

React
React

color

React

size

Its own ladder, well below the control one. A control's height is what a row lines up on; a badge lines up on nothing, it hangs off the corner of something else.

React

Accessibility

  • content={3} beside a bell reads out as "3", which means nothing. Give it label="3 unread notifications" and the sentence is what is announced instead of the number.
  • A dot draws nothing but still reads whichever of label and content it was given.
  • An invisible badge and an empty one are hidden from the accessibility tree entirely, and hold no text at all. Text left behind in a clipped box is text a find-on-page still turns up.
  • The badge adds no role and no tab stop. What is interactive is the anchor inside it, and the anchor is the caller's own element.
  • The shell around the anchor is inline-flex and exactly as wide as what it wraps, so a badged icon button still lines up with a bare one beside it.
  • count: 3 beside a bell reads out as "3", which means nothing. Give it label: '3 unread notifications' and the sentence is what is announced instead of the number.
  • A dot draws nothing but still reads whichever of label and the count it was given.
  • An invisible badge and an empty one are excluded from the semantics tree entirely.
  • The badge adds no role and no focus stop. What is interactive is the anchor inside it, and the anchor is the caller's own widget.
  • The shell around the anchor is measured by the anchor alone, so a badged icon button still lines up with a bare one beside it.

Differences from the React build

ReactFlutterWhy
one content propcontent and countmax and showZero only mean anything for a number. Two parameters make the type the question rather than a runtime typeof.
a negative margina directional StackThe same decision for the same reason. Neither one moves the marker with a transform.
visibility: hiddenVisibility(maintainSize: true)The same thing said in Flutter's words: the box stays, so nothing moves when the badge comes back.
childrenchildFlutter's name.
className, styleThere is no class list and no style attribute to pass through.

Released under the MIT License