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.
import { PlBadge, PlButton } from 'plass-ui';
<PlBadge content={4} label="4 unread notifications">
<PlButton aria-label="Notifications">
<BellIcon />
</PlButton>
</PlBadge>;import 'package:plass_ui/plass_ui.dart';
PlBadge(
count: 4,
label: '4 unread notifications',
child: PlButton(semanticLabel: 'Notifications', startIcon: const BellGlyph(), onPressed: open),
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| 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 |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth. 0 means no shadow at all |
| content | ReactNode | — | What 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 |
| max | number | 99 | Caps a numeric content and adds a +. Numbers only: a badge cannot know how to truncate a word |
| dot | boolean | false | Draws the marker as a dot even with content, keeping the content for screen readers only. For the corner that has to stay quiet |
| showZero | boolean | false | Whether a content of 0 is shown. Zero unread messages is not news, and a badge that never goes away stops meaning anything |
| invisible | boolean | false | Hides 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 |
| label | string | — | What a screen reader hears instead of the raw content. A 3 beside a bell is just "3"; "3 unread notifications" is the sentence |
| children | ReactNode | — | What the badge is pinned to. Without it the badge is a standalone marker that lays out inline |
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | PlassVariant | PlassVariant.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 | PlassSize | PlassSize.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 | PlassColor | PlassColor.primary | Semantic colour role. Arbitrary colour values are not accepted |
| densityshared | PlassDensity | PlassDensity.standard | The room around the digits, and nothing else |
| elevationshared | int | 0 | Drop shadow depth. 0 means no shadow at all |
| content | Widget? | — | What the badge says when it is not a number. Cannot be given alongside count |
| count | int? | — | What the badge counts. Separate from content because max and showZero only mean anything for a number — the type is the question |
| max | int | 99 | Caps a numeric content and adds a +. Numbers only: a badge cannot know how to truncate a word |
| dot | bool | false | Draws the marker as a dot even with content, keeping the content for screen readers only. For the corner that has to stay quiet |
| showZero | bool | false | Whether a content of 0 is shown. Zero unread messages is not news, and a badge that never goes away stops meaning anything |
| invisible | bool | false | Hides the marker without unmounting the anchor. It keeps its box, so showing it again does not relayout what it sits on |
| placementshared | PlassCorner | PlassCorner.topEnd | Which corner of the anchor it sits on. Logical properties, so it flips with the writing direction |
| overlap | PlBadgeOverlap | PlBadgeOverlap.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 |
| label | String? | — | What a screen reader hears instead of the raw content. A 3 beside a bell is just "3"; "3 unread notifications" is the sentence |
| child | Widget? | — | 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.
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.
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.
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.
color
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.
Accessibility
content={3}beside a bell reads out as "3", which means nothing. Give itlabel="3 unread notifications"and the sentence is what is announced instead of the number.- A dot draws nothing but still reads whichever of
labelandcontentit was given. - An
invisiblebadge 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-flexand exactly as wide as what it wraps, so a badged icon button still lines up with a bare one beside it.
count: 3beside a bell reads out as "3", which means nothing. Give itlabel: '3 unread notifications'and the sentence is what is announced instead of the number.- A dot draws nothing but still reads whichever of
labeland the count it was given. - An
invisiblebadge 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
| React | Flutter | Why |
|---|---|---|
one content prop | content and count | max and showZero only mean anything for a number. Two parameters make the type the question rather than a runtime typeof. |
| a negative margin | a directional Stack | The same decision for the same reason. Neither one moves the marker with a transform. |
visibility: hidden | Visibility(maintainSize: true) | The same thing said in Flutter's words: the box stays, so nothing moves when the badge comes back. |
children | child | Flutter's name. |
className, style | — | There is no class list and no style attribute to pass through. |