Skip to main content

What it is

A small indicator — a number or a plain dot — layered on the corner of another element to signal a count or an unread/active status. It wraps the underlying element’s Badge unmodified; Titan’s contribution is entirely in the theme, not the component code.

Live example

Exports

Hand-confirmed props, from Badge.tsx’s own JSDoc and its Storybook argTypes:

Vocabulary

Do not confuse a Badge with a status label. Badge carries only a number or a dot — it has no text-label slot of its own. A word like “Active” or “Pending” belongs on Tag, not squeezed into badgeContent. color="secondary" is Titan’s neutral badge. The override for colorSecondary points at badge-neutral-background / badge-neutral-text, not a purple “secondary” brand color — Titan uses the underlying element’s secondary slot to carry its own neutral intent, the same way TITAN-DIV-11 describes Button’s primary/secondary/tertiary riding on top of the underlying element’s variant values rather than its color ones. Say neutral when that’s the intent you mean.

Choose Badge when

  • A count or a simple on/off status needs to attach to the corner of another element — an icon, an avatar, a button — rather than stand on its own.
  • The information is genuinely secondary to whatever it’s attached to: an unread count, an active-session dot.

Choose something else when

Anatomy

Variants, sizes, and states

Hand-confirmed from Badge.overrides.ts and the component’s own Storybook stories — no per-component matrix emitter exists yet for this concept.

Edge and failure states

Confirmed directly against the component’s own MaximumValue, BadgeVisibilityOverride, and BadgeOverlap stories:
Badge has no test file in the Titan checkout. There is no Badge.test.tsx — every behavior on this page beyond what the stories above visibly demonstrate is unverified by any automated test.

Tokens

Hand-confirmed from Badge.overrides.ts — no per-component token emitter exists yet for this concept, so this table is hand-confirmed rather than generated. text-invert is set on the root and would apply to every color, but colorPrimary, colorSecondary, colorSuccess, colorError, and colorWarning each set their own text color on top of it — confirmed in Badge.overrides.ts, where those five keys are declared after root. So text-invert only actually reaches the screen for default and info, the two colors without a matching override. For the other five, background and contrast text pull straight from the palette (theme.vars.palette.{color}.main / theme.vars.palette.{color}.contrastText), except secondary, which uses badge-neutral-background / badge-neutral-text instead.

Composition

Badge wraps a single child element — most often an icon or an Avatar. Pair overlap="circular" with a circular child (an Avatar) and leave the default overlap="rectangular" for square or icon children — the BadgeOverlap story demonstrates this pairing directly, and mismatching them positions the badge visibly off the child’s curve.

Content

badgeContent should stay short — a count, or nothing at all for the dot variant. It has no typography treatment for longer text.

Accessibility

  • Badge has no test file in the Titan checkout, so nothing here beyond the stories’ visible behavior is independently verified.
  • Unconfirmed: whether badgeContent is announced by assistive technology as part of the wrapped element’s accessible name, or read as a separate, disconnected number. Nothing in source addresses this.
  • A dot variant carries no accessible information at all beyond whatever the wrapped element itself announces — a caller relying on the dot alone to convey “unread” needs to supply that meaning elsewhere (e.g., in the wrapped element’s own aria-label).

Constraints

Known issues

Badge: open issues

Divergences, open decisions, and undocumented gaps for Badge.

Why it works this way

Badge, like Tag, is a bare pass-through component wrapping the underlying element unmodified — its entire Titan-specific contribution lives in the theme override, not the component file. Routing color="secondary" to a neutral gray rather than a purple brand color is a real, deliberate choice recorded in that file. Leaving default and info untouched is not explained anywhere in source, which is why it is recorded as an edge case here rather than assumed to be intentional.

Status

Package @invoca/titan-core, version 3.6.3. Single export: Badge. No deprecation marker, since field, or changelog entry specific to this component exists in source. Titan’s own utilization.md records exactly 3 usages, all three inside dashboards/src/components/Tile/TileTitle.stories.js — a Storybook stories file, not application code. No production usage of Badge outside of Titan’s own demos is evidenced in the tracked data.
  • Tag — for a text status label, which Badge cannot carry
  • Avatar — the most common thing a Badge is layered onto
  • Alert — for a page-level message, rather than an in-context indicator
Last modified on September 2, 2026