oriUI

Badge

A small status or count indicator. Attach it to any element as a floating pip, drop it inline next to text, or render it standalone — all three modes share the same class surface.

The examples are organised by layer: the class reference is the standalone @oriui/css layer, and the Framework API is the @oriui/vue component. Every example is live — flip its code between HTML (the standalone classes, also your htmx / Astro / Svelte / plain-HTML usage), Vue; HTML is the default.

Classes

A badge is a block class plus single-class token utilities — one class repoints one token; no base class is needed. The Vue props in Framework API map 1:1 to these. Two layout modifiers (ori-badge_dot, ori-badge_floating) and the wrapper class (ori-badge-anchor) complete the floating pattern.

ClassTypeDescription
ori-badgeBlockRequired base class. Defaults baked in: fill variant, primary color, rounded radius.
ori-variant_*Stylefill · tonal · outline · text · plain
ori-color_*Colorprimary · secondary · success · warn · danger · info · surface · background
ori-size-radius_*Radiuszero · xs · sm · md · lg · xl · rounded
ori-badge_dotLayoutcollapses the badge to a small filled circle; content is hidden
ori-badge_floatingLayoutpositions the badge in the top-end corner of its anchor wrapper
ori-badge-anchorWrapperposition:relative wrapper required for floating badges

Variants

Five visual styles, driven by the ori-variant_* single-class token.

FillTonalOutlineTextPlain
html
<span class="ori-badge">Fill</span>
<!-- swap the variant: ori-variant_tonal / _outline / _text / _plain -->
<span class="ori-badge ori-variant_tonal">Tonal</span>
vue
<OriBadge content="Fill" variant="fill" />
<OriBadge content="Tonal" variant="tonal" />
<OriBadge content="Outline" variant="outline" />
<OriBadge content="Text" variant="text" />
<OriBadge content="Plain" variant="plain" />

Colors

All semantic roles. The badge inherits the shared ori-color_* token, so variant × color compose freely.

primarysecondarysuccesswarndangerinfosurface
html
<span class="ori-badge ori-color_danger">danger</span>
vue
<OriBadge content="primary" color="primary" />
<OriBadge content="danger" color="danger" />
<OriBadge content="info" color="info" />

Variant × color compose freely — e.g. a tonal success badge or an outline danger badge:

ApprovedErrorBeta
html
<span class="ori-badge ori-variant_tonal ori-color_success">Approved</span>
<span class="ori-badge ori-variant_outline ori-color_danger">Error</span>
vue
<OriBadge content="Approved" variant="tonal" color="success" />
<OriBadge content="Error" variant="outline" color="danger" />
<OriBadge content="Beta" variant="tonal" color="info" />

Radius

From zero (square) to the default rounded (pill).

zerosmmdlgrounded
html
<span class="ori-badge ori-size-radius_zero">zero</span> <span class="ori-badge">rounded</span>
vue
<OriBadge content="zero" radius="zero" />
<OriBadge content="rounded" radius="rounded" />

Count & max

Pass a number to content. Set max to cap the display value — any number exceeding max renders as {max}+.

54299+9
html
<span class="ori-badge">5</span>
<!-- cap manually in your template when not using Vue -->
<span class="ori-badge ori-color_danger">99+</span>
vue
<OriBadge :content="5" />
<OriBadge :content="42" />
<!-- 130 > 99 → renders "99+" -->
<OriBadge :content="130" :max="99" color="danger" />

Dot

dot collapses the badge to a small filled circle. Content is hidden; it is always decorative (aria-hidden="true") unless you provide a label.

html
<!-- dot is always aria-hidden unless a label is provided -->
<span class="ori-badge ori-badge_dot ori-color_danger" aria-hidden="true"></span>
vue
<OriBadge dot />
<OriBadge dot color="danger" />
<OriBadge dot color="success" />

Floating

The badge floats over wrapped contentfloating needs the badge to wrap an anchor. In Vue, pass the anchor into the default slot and add the floating prop (without a default slot floating has nothing to position against and is ignored). In HTML, wrap the anchor in ori-badge-anchor and add ori-badge_floating to the badge itself.

399+
html
<!-- wrap anchor content in ori-badge-anchor, add ori-badge_floating to the badge -->
<span class="ori-badge-anchor">
    <button class="ori-button ori-variant_tonal ori-color_primary ori-size-radius_rounded">Inbox</button>
    <span class="ori-badge ori-badge_floating ori-color_danger" aria-label="3 unread">3</span>
</span>
vue
<!-- floating over a button -->
<OriBadge :content="3" floating color="danger">
    <button class="ori-button ori-variant_tonal ori-color_primary ori-size-radius_rounded">
        Inbox
    </button>
</OriBadge>

<!-- dot variant -->
<OriBadge dot floating color="danger">
    <button class="ori-button …">Notifications</button>
</OriBadge>

Common patterns

A notification bell, an avatar with a status pip, and a tab bar with unread counts.

399+
New
html
<!-- notification bell -->
<span class="ori-badge-anchor">
    <button class="ori-button …" aria-label="Notifications"><!-- bell icon --></button>
    <span class="ori-badge ori-badge_floating ori-color_danger" aria-label="12 unread notifications">12</span>
</span>

<!-- status dot on avatar -->
<span class="ori-badge-anchor">
    <div class="ori-avatar …"><!-- avatar --></div>
    <span class="ori-badge ori-badge_dot ori-badge_floating ori-color_success" aria-label="Online"></span>
</span>

<!-- inline tab counter -->
<span style="display: inline-flex; align-items: center; gap: 0.5rem">
    Messages
    <span class="ori-badge ori-variant_tonal">5</span>
</span>
vue
<!-- notification bell -->
<OriBadge
    :content="notifications"
    :max="99"
    floating
    color="danger"
    aria-label="`${notifications} unread notifications`"
>
    <OriButton icon="M12 22c1.1 0 2-.9 2-2h-4a2 2 0 0 0 2 2zm6-6V11c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5S10.5 3.17 10.5 4v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z" aria-label="Notifications" />
</OriBadge>

<!-- status dot on an avatar -->
<OriBadge dot floating color="success" label="Online">
    <OriAvatar text="Ada Lovelace" />
</OriBadge>

<!-- tab with unread count -->
<span style="display: inline-flex; align-items: center; gap: 0.5rem">
    Messages
    <OriBadge :content="5" variant="tonal" color="primary" />
</span>

Accessibility

The accessibility contract holds across every layer — the standalone classes and the Vue component render the same attributes.

  • A badge with visible text content (content) conveys that text to assistive technology directly.
  • A pure dot badge — or an empty badge with no label — is marked aria-hidden="true" because it carries no information for screen readers.
  • Supply a label whenever the badge conveys meaning that a sighted user can read but a screen reader cannot (e.g. a dot meaning "online", or a floating count with no textual fallback nearby).
  • A floating count that visually annotates an already-labeled control (e.g. "Inbox (3 unread)") should carry aria-label on the badge element or be hidden with aria-hidden="true" if the parent label already includes the count.
  • The badge is a <span> — it has no interactive role and no keyboard contract of its own. When a badge and its anchor together form an interactive control, apply focus management and ARIA to the outer interactive element.
AttributeConditionValue
aria-hidden="true"dot with no label, or empty badge with no label"true"
aria-labellabel prop is setlabel value

Framework API

The props, events, and slots of the Vue component. The standalone CSS layer has no component API — its surface is the classes above. (Svelte bindings are planned.)

Props

PropTypeDefaultDescription
colorThemeColor'primary'Semantic color role: primary · secondary · success · warn · danger · info · surface · background.
contentstring | numberText or number to display. Numbers are capped to max+ when max is set.
dotbooleanfalseCollapses the badge to a small filled circle; content is hidden.
floatingbooleanfalsePositions the badge in the top-end corner of the default-slot anchor (requires a default slot child).
labelstringAccessible name (aria-label). Required for dot badges or empty badges that convey meaning to sighted users.
maxnumberWhen content is a number and exceeds max, the display value is capped to {max}+.
radiusRadiusSize'rounded'Corner radius: zero · xs · sm · md · lg · xl · rounded.
variantVariant'fill'Visual style: fill · tonal · outline · text · plain.

Events & attributes

OriBadge declares no custom events. It sets inheritAttrs: false and applies v-bind="$attrs" directly to the inner .ori-badge element — so id, class, data-*, and event listeners always land on the badge element regardless of whether a default slot (the anchor wrapper) is present.

Slots

SlotDescription
defaultThe element the badge floats over. When provided, the component wraps both the slot content and the badge in an ori-badge-anchor span. When absent, the badge renders standalone.
contentCustom badge content, replacing the capped content value ({max}+) that renders by default. Only renders when not a dot. A badge that uses this slot is kept in the a11y tree (not decorative).