# Badge

A numeric/status badge, typically used for message counts or new-content indicators.

## Basic usage

<DemoBlock title="Numeric badges">
  <oas-badge value="5">
    <oas-tag>Notifications</oas-tag>
  </oas-badge>
  <oas-badge value="88">
    <oas-tag>Unread</oas-tag>
  </oas-badge>
</DemoBlock>

## Max display

When the value exceeds `max`, `max+` is displayed.

<DemoBlock title="Max display">
  <oas-badge value="120" max="99">
    <oas-tag>Comments</oas-tag>
  </oas-badge>
</DemoBlock>

## Dot

<DemoBlock title="Status dot">
  <oas-badge dot>
    <oas-tag>Online</oas-tag>
  </oas-badge>
</DemoBlock>

## Zero value

`0` is hidden by default; shown when `showZero` is set.

<DemoBlock title="Zero value control">
  <oas-badge value="0">
    <oas-tag>Hidden by default</oas-tag>
  </oas-badge>
  <oas-badge value="0" showZero>
    <oas-tag>Show 0</oas-tag>
  </oas-badge>
</DemoBlock>

## Standalone badge

When no child content is wrapped, the badge falls back from the "top-end corner" position to a static inline element (never collapses), so it can sit in a text flow or a menu row.

<DemoBlock title="Standalone badge">
  <span>New messages <oas-badge value="3"></oas-badge></span>
  <span>To-dos <oas-badge value="12" color="success"></oas-badge></span>
  <span>System running <oas-badge dot color="green"></oas-badge></span>
</DemoBlock>

## Icon badge

`icon` takes an icon name (lookup prefers custom registrations, falling back to the built-in icon set) and renders a small icon inside the badge (e.g. a √ / ! status corner). It is mutually exclusive with `value` / `dot`: `icon` wins and shows the icon instead of a number/dot; an invalid icon name silently falls back to the numeric logic.

<DemoBlock title="Icon badges">
  <oas-badge icon="check" color="success" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>Done</oas-tag>
  </oas-badge>
  <oas-badge icon="warning" color="warning" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>Attention</oas-tag>
  </oas-badge>
  <oas-badge icon="error" color="danger">
    <oas-tag>Failed</oas-tag>
  </oas-badge>
</DemoBlock>

## Badge colors

`color` accepts the four semantic colors (`primary` / `success` / `warning` / `danger`), any CSS color value, and the 11 preset names (`magenta` / `red` / `volcano` / `orange` / `gold` / `lime` / `green` / `cyan` / `blue` / `geekblue` / `purple`, mapped to `--oas-preset-*` tokens, auto-brightened in dark). It works uniformly across count / dot / ribbon modes; the solid text color uses a fixed `text-on-*` token for semantic/preset colors, and is picked black/white by background luminance only for arbitrary CSS color values.

<DemoBlock title="count semantic colors">
  <oas-badge value="5" color="primary" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>primary</oas-tag>
  </oas-badge>
  <oas-badge value="5" color="success" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>success</oas-tag>
  </oas-badge>
  <oas-badge value="5" color="warning" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>warning</oas-tag>
  </oas-badge>
  <oas-badge value="5" color="danger">
    <oas-tag>danger</oas-tag>
  </oas-badge>
</DemoBlock>

<DemoBlock title="count preset colors">
  <oas-badge value="3" color="magenta" style="margin-inline-end: var(--oas-space-3)">
    <oas-tag>magenta</oas-tag>
  </oas-badge>
  <oas-badge value="3" color="geekblue" style="margin-inline-end: var(--oas-space-3)">
    <oas-tag>geekblue</oas-tag>
  </oas-badge>
  <oas-badge value="3" color="gold" style="margin-inline-end: var(--oas-space-3)">
    <oas-tag>gold</oas-tag>
  </oas-badge>
  <oas-badge value="3" color="cyan">
    <oas-tag>cyan</oas-tag>
  </oas-badge>
</DemoBlock>

<DemoBlock title="count / dot custom colors">
  <oas-badge value="8" color="#7c3aed" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>#7c3aed purple</oas-tag>
  </oas-badge>
  <oas-badge dot color="#e11d48" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>dot #e11d48</oas-tag>
  </oas-badge>
  <oas-badge dot color="purple">
    <oas-tag>dot purple</oas-tag>
  </oas-badge>
</DemoBlock>

## Outline variant

`variant="outline"` switches to an outlined badge: a transparent background with the border and text following the `color` semantics (`solid` filled is the default; invalid values fall back). Good for stacking on images/colored surfaces; composes with the `bordered` white ring.

<DemoBlock title="outline variant">
  <oas-badge value="5" variant="outline" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>outline default</oas-tag>
  </oas-badge>
  <oas-badge value="5" variant="outline" color="success" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>outline success</oas-tag>
  </oas-badge>
  <oas-badge dot variant="outline" color="warning" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>outline dot</oas-tag>
  </oas-badge>
  <oas-badge value="5" variant="outline" color="#7c3aed" bordered>
    <oas-tag>outline custom color + bordered</oas-tag>
  </oas-badge>
</DemoBlock>

## Offset

`offset="x,y"` (px numbers) shifts the corner badge from its default position; x is positive rightward and y positive downward (screen coordinates, independent of the corner direction). Invalid values (non-numeric, missing coordinate) are silently ignored. It composes with `corner`: pick the corner first, then fine-tune.

<DemoBlock title="Offset">
  <oas-badge value="5" offset="10,5" style="margin-inline-end: var(--oas-space-5)">
    <oas-tag>Shift right-down</oas-tag>
  </oas-badge>
  <oas-badge value="5" offset="0,8">
    <oas-tag>Shift down</oas-tag>
  </oas-badge>
</DemoBlock>

## Corner placement

`corner` pins the badge to one of the four corners of the host: `top-right` (default) / `top-left` / `bottom-right` / `bottom-left`; invalid values silently fall back to `top-right`. `offset` is a precise fine-tune on top of the `corner` result (corner first, then shift; they compose).

<DemoBlock title="Four corners">
  <oas-badge value="5" style="margin-inline-end: var(--oas-space-5)">
    <oas-tag>top-right default</oas-tag>
  </oas-badge>
  <oas-badge value="5" corner="top-left" style="margin-inline-end: var(--oas-space-5)">
    <oas-tag>top-left</oas-tag>
  </oas-badge>
  <oas-badge value="5" corner="bottom-right" style="margin-inline-end: var(--oas-space-5)">
    <oas-tag>bottom-right</oas-tag>
  </oas-badge>
  <oas-badge value="5" corner="bottom-left">
    <oas-tag>bottom-left</oas-tag>
  </oas-badge>
</DemoBlock>

<DemoBlock title="corner + offset">
  <oas-badge value="5" corner="bottom-left" offset="4,4" style="margin-inline-end: var(--oas-space-5)">
    <oas-tag>bottom-left shifted right-down</oas-tag>
  </oas-badge>
  <oas-badge value="5" corner="top-left" offset="0,6">
    <oas-tag>top-left shifted down</oas-tag>
  </oas-badge>
</DemoBlock>

## Circular overlap

When wrapping circular content (e.g. an avatar), add `overlap` so the badge tucks inside the circle edge: the translate amount shrinks from 50% to ~29% (the 1-√2/2 geometric inset). Only affects corner badge mode.

<DemoBlock title="overlap circular inset">
  <oas-badge value="5" overlap style="margin-inline-end: var(--oas-space-5)">
    <oas-avatar size="48">张</oas-avatar>
  </oas-badge>
  <oas-badge dot overlap color="success" style="margin-inline-end: var(--oas-space-5)">
    <oas-avatar size="48">李</oas-avatar>
  </oas-badge>
  <oas-badge value="99" max="99" overlap corner="bottom-right">
    <oas-avatar size="48">王</oas-avatar>
  </oas-badge>
</DemoBlock>

## White outline

`bordered` adds a 2px background-color ring around the badge (`box-shadow: 0 0 0 2px var(--oas-color-bg)`) so it stands out against complex backgrounds like avatars/images; visual-only, never changes positioning (composes with `corner` / `offset` / `overlap`).

<DemoBlock title="bordered white outline">
  <oas-badge value="5" bordered style="margin-inline-end: var(--oas-space-5)">
    <oas-avatar size="48">张</oas-avatar>
  </oas-badge>
  <oas-badge dot bordered color="success" style="margin-inline-end: var(--oas-space-5)">
    <oas-avatar size="48">李</oas-avatar>
  </oas-badge>
  <oas-badge value="3" bordered corner="bottom-right" offset="2,2">
    <oas-avatar size="48">王</oas-avatar>
  </oas-badge>
</DemoBlock>

## Status point

`status` renders an inline standalone element of "status dot + `text`" (not a corner badge) and is mutually exclusive with ribbon / dot / count modes (rendered first when set). The `processing` dot pulses (`prefers-reduced-motion` disables it).

<DemoBlock title="Status points">
  <oas-badge status="success" text="Published" style="margin-inline-end: var(--oas-space-4)"></oas-badge>
  <oas-badge status="processing" text="Processing" style="margin-inline-end: var(--oas-space-4)"></oas-badge>
  <oas-badge status="default" text="Default" style="margin-inline-end: var(--oas-space-4)"></oas-badge>
  <oas-badge status="error" text="Error" style="margin-inline-end: var(--oas-space-4)"></oas-badge>
  <oas-badge status="warning" text="Warning"></oas-badge>
</DemoBlock>

## Size tiers

`size` provides three tiers: `small` (compact, numeric badge ~13px tall, dot 6px) / `medium` (default, ~16px, dot 8px) / `large` (~20px, dot 10px); invalid values silently fall back to `medium`.

<DemoBlock title="Size tiers small / medium / large">
  <oas-badge value="5" size="small" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>small</oas-tag>
  </oas-badge>
  <oas-badge value="99" max="99" size="small" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>small 99+</oas-tag>
  </oas-badge>
  <oas-badge dot size="small" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>small dot</oas-tag>
  </oas-badge>
  <oas-badge value="5" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>medium default</oas-tag>
  </oas-badge>
  <oas-badge value="5" size="medium" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>medium explicit</oas-tag>
  </oas-badge>
  <oas-badge value="5" size="large" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>large</oas-tag>
  </oas-badge>
  <oas-badge value="99" max="99" size="large" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>large 99+</oas-tag>
  </oas-badge>
  <oas-badge dot size="large">
    <oas-tag>large dot</oas-tag>
  </oas-badge>
</DemoBlock>

## Attention animation

`attention="pulse"` makes the badge emit a periodic outward pulse ring (the pulse color can be customized via `--oas-badge-pulse-color`, defaulting to the badge background); `attention="bounce"` makes the badge bounce up and down slightly. It only applies to count / dot / standalone badges (the ribbon is unaffected) and is disabled under `prefers-reduced-motion`.

<DemoBlock title="pulse">
  <oas-badge value="5" attention="pulse" style="margin-inline-end: var(--oas-space-5)">
    <oas-tag>pulse</oas-tag>
  </oas-badge>
  <oas-badge dot attention="pulse" color="success" style="margin-inline-end: var(--oas-space-5)">
    <oas-tag>dot pulse</oas-tag>
  </oas-badge>
  <oas-badge
    value="3"
    attention="pulse"
    color="purple"
    style="--oas-badge-pulse-color: var(--oas-color-warning)"
  >
    <oas-tag>custom pulse color</oas-tag>
  </oas-badge>
</DemoBlock>

<DemoBlock title="bounce">
  <oas-badge value="5" attention="bounce" style="margin-inline-end: var(--oas-space-5)">
    <oas-tag>bounce</oas-tag>
  </oas-badge>
  <oas-badge dot attention="bounce" color="warning">
    <oas-tag>dot bounce</oas-tag>
  </oas-badge>
</DemoBlock>

## Native tooltip

Adding the native `title` attribute to the badge host gives a hover tooltip with zero JS.

<DemoBlock title="Native tooltip">
  <oas-badge value="3" title="3 unread messages">
    <oas-tag>Unread</oas-tag>
  </oas-badge>
</DemoBlock>

## Dynamic increment

`value` updates take effect immediately; the host can add an animation class on click for a transition feedback (this example uses `::part(badge)` for a scale/opacity micro-animation).

The numeric badge carries `role="status"` + `aria-live="polite"` + `aria-atomic="true"`: whenever the number (including the max-truncated display value) changes, it announces via `aria-label` using the locale (e.g. "5 unread notifications") — no extra JS required.

<DemoBlock title="Dynamic increment">
  <oas-badge id="badge-dyn" value="5">
    <oas-tag>Dynamic count</oas-tag>
  </oas-badge>
  <oas-button id="badge-dec" size="small" aria-label="Decrease">−</oas-button>
  <oas-button id="badge-inc" size="small" type="primary" aria-label="Increase">＋</oas-button>
</DemoBlock>

<script setup>
import { onMounted } from 'vue'
onMounted(() => {
  const badge = document.getElementById('badge-dyn')
  const bump = () => {
    badge.classList.remove('bump')
    void badge.offsetWidth
    badge.classList.add('bump')
  }
  document.getElementById('badge-inc')?.addEventListener('click', () => {
    badge.setAttribute('value', String(Number(badge.getAttribute('value') || 0) + 1))
    bump()
  })
  document.getElementById('badge-dec')?.addEventListener('click', () => {
    badge.setAttribute('value', String(Math.max(0, Number(badge.getAttribute('value') || 0) - 1)))
    bump()
  })
})
</script>

<style>
oas-badge#badge-dyn.bump::part(badge) {
  animation: oas-badge-dyn-bump 220ms var(--oas-ease-out);
}
@keyframes oas-badge-dyn-bump {
  0%,
  100% {
    transform: translate(50%, -50%) scale(1);
    opacity: 1;
  }
  45% {
    transform: translate(50%, -50%) scale(1.22);
    opacity: 0.72;
  }
}
</style>

## Ribbon corner

The `ribbon` boolean attribute (or `mode="ribbon"`) enables a ribbon corner — an angled folded ribbon on the top edge of the wrapped content. Text is provided via the `text` attribute; it sits at the inline-end (`placement="end"`) by default and can be moved to `placement="start"`.

<DemoBlock title="Basic ribbon">
  <oas-badge ribbon text="HOT" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>Default ribbon (top-end)</p></oas-card>
  </oas-badge>
  <oas-badge mode="ribbon" text="LIMITED" placement="start">
    <oas-card><p>mode="ribbon" (top-start)</p></oas-card>
  </oas-badge>
</DemoBlock>

## Ribbon position

`ribbon-position` controls the ribbon's vertical position: `hang` (default, hangs below the top edge) / `edge` (flush with the top edge) / `cross` (straddles the top edge, pressing against the card border for the strongest wrap-around feel). All three positions are orthogonal to `placement` (start / end) and work on both sides.

<DemoBlock title="Ribbon position">
  <oas-badge ribbon text="HANG" ribbon-position="hang" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>hang (default, hangs below)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="EDGE" ribbon-position="edge" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>edge (flush with top edge)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="CROSS" ribbon-position="cross">
    <oas-card><p>cross (straddling top edge)</p></oas-card>
  </oas-badge>
</DemoBlock>

<DemoBlock title="Ribbon position (placement=start, left side)">
  <oas-badge ribbon text="HANG" ribbon-position="hang" placement="start" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>hang (hangs below)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="EDGE" ribbon-position="edge" placement="start" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>edge (flush with top edge)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="CROSS" ribbon-position="cross" placement="start">
    <oas-card><p>cross (straddling top edge)</p></oas-card>
  </oas-badge>
</DemoBlock>

## Ribbon forms

`ribbon-form` switches the ribbon shape: `fold` (default, straight band + folded corner) / `diagonal` (45° diagonal band sweeping from the top corner; the band extends outside the card, so the host needs `overflow: hidden` to clip it) / `triangle` (a pure corner triangle holding a small icon or `slot="ribbon"` content) / `bookmark` (a vertical tab hanging from the top edge with a swallow-tail notch) / `side` (a vertical strip hung at mid-height of the side edge) / `seal` (a circular serrated stamp with centered text) / `banner` (a full-width strip across the top edge with angled ends) / `flag` (a side swallow-tail banner: a horizontal band with a V notch at the card-facing end; the outer end stays flush with the card edge, and the notch always faces the card-inner end). Invalid values silently fall back to `fold`. `ribbon-direction` controls the notch direction of `bookmark` (`down` default, hanging from the top edge / `left`, tab on the right edge with the notch pointing left / `right`, tab on the left edge with the notch pointing right; physical-direction semantics, not flipped in RTL), and only takes effect for `bookmark`; `ribbon-vertical` controls the vertical position of the side-mount forms (`center` default, vertically centered / `top`, flush with the top edge / `bottom`, flush with the bottom edge). `ribbon-size` offers `sm` (default) / `md` / `lg` tiers for `diagonal` (a tier only changes the fallback band height / font size / pin depth; host `--oas-badge-diagonal-*` custom properties take precedence). The `ribbon-position` vertical trio only affects the `fold` shape; the other shapes have their own vertical placement. `offset` fine-tunes the position of every ribbon shape (`offset="x,y"` px shift, negative values allowed). `rolled` is a boolean modifier that adds an end roll (a large end radius + an inner gradient darkening that reads as a rolled cylinder); it composes with `fold` / `flag` / `rounded` / `zigzag` / `arrow` (`banner` keeps its own angled ends and gets no roll; `diagonal` / `triangle` / `bookmark` / `side` / `seal` silently ignore it).

<DemoBlock title="Ribbon forms">
  <oas-badge ribbon text="HOT" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>fold</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="diagonal" style="margin-inline-end: var(--oas-space-4); overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>diagonal</p></oas-card>
  </oas-badge>
  <oas-badge ribbon ribbon-form="triangle" style="margin-inline-end: var(--oas-space-4)">
    <oas-icon slot="ribbon" name="star" size="18"></oas-icon>
    <oas-card><p>triangle</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="NEW" ribbon-form="bookmark" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>bookmark</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="NEW" ribbon-form="bookmark" ribbon-direction="left" style="margin-inline-end: var(--oas-space-4)">
    <oas-card style="width: 180px"><p>bookmark direction=left (notch left)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="NEW" ribbon-form="bookmark" ribbon-direction="right">
    <oas-card style="width: 180px"><p>bookmark direction=right (notch right)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="NEW" ribbon-form="bookmark" ribbon-direction="left" ribbon-vertical="top" style="margin-inline-end: var(--oas-space-4)">
    <oas-card style="width: 180px"><p>bookmark left + vertical=top</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="NEW" ribbon-form="bookmark" ribbon-direction="right" ribbon-vertical="bottom" style="margin-inline-end: var(--oas-space-4)">
    <oas-card style="width: 180px"><p>bookmark right + vertical=bottom</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="side" placement="start" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>side</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="BEST" ribbon-form="seal" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>seal</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="LIMITED DEAL" ribbon-form="banner" placement="start">
    <oas-card><p>banner</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="flag" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>flag (side swallow-tail)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="rounded" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>rounded</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="zigzag" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>zigzag</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="arrow" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>arrow</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" rolled style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>fold + rolled end</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="50% OFF" ribbon-form="diagonal" ribbon-size="lg" style="overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 240px"><p>diagonal + ribbon-size=lg</p></oas-card>
  </oas-badge>
</DemoBlock>

`ribbon-size` offers three tiers for `diagonal` (`sm` default / `md` / `lg`); a tier only changes the fallback band height, font size and pin depth. The host can override `--oas-badge-diagonal-height` / `--oas-badge-diagonal-font` / `--oas-badge-diagonal-pin` / `--oas-badge-diagonal-text-inset` custom properties, which take precedence over the tier.

<DemoBlock title="diagonal size tiers sm / md / lg">
  <oas-badge ribbon text="HOT" ribbon-form="diagonal" style="margin-inline-end: var(--oas-space-4); overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>ribbon-size=sm (default)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="SALE" ribbon-form="diagonal" ribbon-size="md" style="margin-inline-end: var(--oas-space-4); overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>ribbon-size=md</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="50% OFF" ribbon-form="diagonal" ribbon-size="lg" style="overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 200px"><p>ribbon-size=lg</p></oas-card>
  </oas-badge>
</DemoBlock>

`offset` fine-tunes the position of every ribbon shape: `offset="x,y"` shifts by px on top of the shape's own anchor (x positive rightward, y positive downward, negative values allowed), composing orthogonally with `placement` / `ribbon-position` / `ribbon-vertical`.

<DemoBlock title="Ribbon offset fine-tune">
  <oas-badge ribbon text="NEW" ribbon-form="bookmark" ribbon-direction="left" offset="0,20" style="margin-inline-end: var(--oas-space-4)">
    <oas-card style="width: 180px"><p>bookmark left + offset=0,20</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" offset="0,-8" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>fold + offset=0,-8</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="SALE" ribbon-form="diagonal" offset="-30,0" style="overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>diagonal + offset=-30,0</p></oas-card>
  </oas-badge>
</DemoBlock>

`ribbon-anchor` unifies the ribbon anchor, with the anchor set adapted per shape semantics (industry practice: shape and position are bound together):

- **diagonal / triangle**: four corners only
- **fold / side / banner / seal / bookmark / flag** (and the remaining shapes): all 8 positions (4 edge centers + 4 corners)
Anchors pin the ribbon to an edge/corner (the fold notch auto-flips on bottom anchors), and `offset` fine-tunes any arbitrary position.

<DemoBlock title="Ribbon anchors: angular corners">
  <oas-badge ribbon text="HOT" ribbon-form="diagonal" ribbon-anchor="top-left" style="margin-inline-end: var(--oas-space-4); overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>diagonal top-left</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="diagonal" ribbon-anchor="top-right" style="margin-inline-end: var(--oas-space-4); overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>diagonal top-right</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="diagonal" ribbon-anchor="bottom-left" style="margin-inline-end: var(--oas-space-4); overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>diagonal bottom-left</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="diagonal" ribbon-anchor="bottom-right" style="overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>diagonal bottom-right</p></oas-card>
  </oas-badge>
</DemoBlock>

<DemoBlock title="Ribbon anchors: fold positions (hug left/right)">
  <oas-badge ribbon text="HOT" ribbon-anchor="left" style="margin-inline-end: var(--oas-space-4)">
    <oas-card style="width: 160px"><p>fold left (edge center)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-anchor="right" style="margin-inline-end: var(--oas-space-4)">
    <oas-card style="width: 160px"><p>fold right (edge center)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-anchor="bottom-left" style="margin-inline-end: var(--oas-space-4)">
    <oas-card style="width: 160px"><p>fold bottom-left (notch flipped)</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-anchor="bottom-right">
    <oas-card style="width: 160px"><p>fold bottom-right (notch flipped)</p></oas-card>
  </oas-badge>
</DemoBlock>

## Premium metallic

`premium` adds a metallic gold treatment to the ribbon: a multi-stop light-gold-to-dark-gold gradient with a deep-gold fine outline (clipped shapes get the outline traced along their clip-path silhouette), and the text color is fixed to near-black `#1a1a1a`. It composes orthogonally with `color` with the priority `premium` > `color` > semantic default, and works with every `ribbon-form`; dark theme adapts automatically (driven by the `--oas-preset-gold` token).

<DemoBlock title="Premium metallic">
  <oas-badge ribbon text="HOT" premium style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>premium fold</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="LIMITED" premium color="success" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>premium overrides color</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="NEW" ribbon-form="bookmark" premium style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>premium bookmark</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="BEST" ribbon-form="seal" premium style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>premium seal</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="HOT" ribbon-form="diagonal" premium style="margin-inline-end: var(--oas-space-4); overflow: hidden; border-radius: var(--oas-radius-lg)">
    <oas-card style="width: 160px"><p>premium diagonal</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="DEAL" ribbon-form="banner" premium placement="start">
    <oas-card><p>premium banner</p></oas-card>
  </oas-badge>
</DemoBlock>

## Colored ribbon

`color` supports the four semantic colors `primary` / `success` / `warning` / `danger`, following the theme (light/dark); preset names and arbitrary color values are also supported (injected via `--oas-preset-*` tokens / the raw value).

<DemoBlock title="Colored ribbon">
  <oas-badge ribbon text="HOT" color="primary" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>primary</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="FREE" color="success" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>success</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="ENDING" color="warning" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>warning</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="SOLD OUT" color="danger">
    <oas-card><p>danger</p></oas-card>
  </oas-badge>
</DemoBlock>

<DemoBlock title="Ribbon preset & custom colors">
  <oas-badge ribbon text="LIMITED" color="geekblue" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>geekblue</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="DEAL" color="#7c3aed">
    <oas-card><p>#7c3aed</p></oas-card>
  </oas-badge>
</DemoBlock>

## Custom ribbon content

Besides the `text` attribute, arbitrary content can be passed through `slot="ribbon"` (the slot takes precedence when present).

<DemoBlock title="Custom content">
  <oas-badge ribbon>
    <span slot="ribbon">New - 20% off</span>
    <oas-card><p>Custom content via slot="ribbon"</p></oas-card>
  </oas-badge>
</DemoBlock>

## Comparison with count / dot

The same `oas-badge` can serve as a count badge or a ribbon: the count badge is a small number pinned to the top-end corner, `dot` is a textless status point, while the ribbon spans the top edge of the wrapped content; `status` is a standalone inline "dot + text" element.

<DemoBlock title="count / dot / ribbon comparison">
  <oas-badge value="5" style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>count numeric badge</p></oas-card>
  </oas-badge>
  <oas-badge dot style="margin-inline-end: var(--oas-space-4)">
    <oas-card><p>dot status point</p></oas-card>
  </oas-badge>
  <oas-badge ribbon text="NEW">
    <oas-card><p>ribbon</p></oas-card>
  </oas-badge>
</DemoBlock>

## Badge Shape

`shape` controls the badge shape: `round` (default, rounded — the base class, no extra class written) / `square` (right angles) / `pill` (capsule, fully rounded ends). It applies to both numeric badges and `dot` points: `square` turns a dot into a square point, while `pill` keeps it circular; invalid values silently fall back to `round`.

<DemoBlock title="shape: round / square / pill">
  <oas-badge value="5" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>round default</oas-tag>
  </oas-badge>
  <oas-badge value="5" shape="square" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>square</oas-tag>
  </oas-badge>
  <oas-badge value="5" shape="pill" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>pill</oas-tag>
  </oas-badge>
  <oas-badge dot style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>dot round</oas-tag>
  </oas-badge>
  <oas-badge dot shape="square" style="margin-inline-end: var(--oas-space-4)">
    <oas-tag>dot square</oas-tag>
  </oas-badge>
  <oas-badge dot shape="pill">
    <oas-tag>dot pill</oas-tag>
  </oas-badge>
</DemoBlock>

## Loading Badge (composed with oas-spin)

The badge itself has no built-in spinner prop — put an `oas-spin` in the default slot to express "badge containing a loading ring": the corner count / dot / status point still come from `oas-badge`, while the indicator's form (`variant`) and size (`size`) are controlled independently by `oas-spin`, letting you compose them freely.

<DemoBlock title="Corner badge + loading ring (composed with oas-spin)">
  <oas-badge dot color="primary" style="margin-inline-end: var(--oas-space-5)">
    <oas-spin size="small"></oas-spin>
  </oas-badge>
  <oas-badge value="3" style="margin-inline-end: var(--oas-space-5)">
    <oas-spin size="medium"></oas-spin>
  </oas-badge>
  <oas-badge dot overlap color="success" style="margin-inline-end: var(--oas-space-5)">
    <oas-spin size="large"></oas-spin>
  </oas-badge>
  <oas-badge dot variant="outline" color="warning" style="margin-inline-end: var(--oas-space-5)">
    <oas-spin size="medium" variant="dot"></oas-spin>
  </oas-badge>
  <oas-badge value="2" bordered>
    <oas-spin size="large" tip="Syncing"></oas-spin>
  </oas-badge>
</DemoBlock>

## API

### oas-badge

#### Attributes

| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| `attention` | Attention animation: `pulse` (an outward pulse ring; the pulse color follows the `--oas-badge-pulse-color` custom property, defaulting to the badge background) / `bounce` (slight up-and-down bounce); applies only to count / dot / standalone badges (the ribbon is unaffected) and is disabled under `prefers-reduced-motion` | `BadgeAttention` | — |
| `bordered` | White outline: boolean, adds a 2px background-color ring around the badge (box-shadow: 0 0 0 2px var(--oas-color-bg)) to separate it from an avatar/image background; visual-only, never changes positioning (composes with corner/offset/overlap) | `boolean` | — |
| `color` | Badge color: the four semantic colors (`primary` / `success` / `warning` / `danger`), any CSS color value, or one of the 11 preset names (`magenta` / `red` / `volcano` / `orange` / `gold` / `lime` / `green` / `cyan` / `blue` / `geekblue` / `purple`, mapped to `--oas-preset-*` tokens, auto-brightened in dark). Applies uniformly across count / dot / ribbon; the solid text color uses a fixed `text-on-*` token for semantic/preset colors, and is picked black/white by background luminance only for arbitrary CSS color values | `BadgeColor \| BadgePresetColor` | — |
| `corner` | Corner placement: `top-right` (default) / `top-left` / `bottom-right` / `bottom-left`, affects count / dot corner badges only (the ribbon uses `placement`); `offset` fine-tunes in screen px on top of the corner result (x positive rightward, y positive downward, independent of the corner direction), corner first then shift, they compose; invalid values silently fall back to `top-right` | `BadgeCorner` | `top-right` |
| `dot` | Dot mode | `boolean` | — |
| `icon` | In-badge icon: a built-in icon name (e.g. check / warning), rendered as an inline SVG (1em following the badge font size, decorative and hidden from screen readers); mutually exclusive with value/dot (icon wins) | `string` | — |
| `max` | Upper limit | `string` | — |
| `mode` | Mode: `count` (default, numeric/dot badge) or `ribbon` (ribbon corner, same as `ribbon` attribute) | `BadgeMode` | `count` |
| `offset` | Position fine-tune: `"x,y"` (px numbers, negative values allowed), shifts the corner badge or ribbon on top of its anchor position (x positive rightward, y positive downward); composes with `corner` / `placement` / `ribbon-vertical` (anchor first, then shift); invalid values (non-numeric, missing coordinate) are silently ignored | `string` | — |
| `overlap` | Circular inset: when wrapping circular content (e.g. an avatar), the badge tucks inside the circle edge (the translate amount shrinks from 50% to ~29%, the 1-√2/2 geometric inset); affects corner badge mode only | `boolean` | — |
| `placement` | Ribbon position: `start` (inline-start) / `end` (inline-end, default) | `BadgePlacement` | `end` |
| `premium` | Metallic treatment: a multi-stop gold gradient with a deep-gold fine outline (clipped shapes get the outline traced along their clip-path silhouette), text color fixed to near-black `#1a1a1a`; composes orthogonally with `color` with the priority `premium` > `color` > semantic default; works with every `ribbon-form`, dark theme adapts automatically (driven by the `--oas-preset-gold` token) | `boolean` | — |
| `ribbon` | Ribbon corner mode (boolean, same as `mode="ribbon"`) | `boolean` | — |
| `ribbon-anchor` | Unified ribbon anchor, with the anchor set adapted per shape semantics: `fold` (left/right edge centers + four corners, a horizontal band hugs left/right with the vertical position adjustable), `diagonal`/`triangle` (four corners), `side` (left/right edge centers + four corners), `banner` (top/bottom), `seal` (all 8 positions), `bookmark` (four corners + four edge centers); an anchor pins the ribbon to an edge/corner (the fold notch auto-flips on bottom anchors) and `offset` fine-tunes any arbitrary position; when unset it falls back to the `placement` / `ribbon-position` / `ribbon-direction` / `ribbon-vertical` aliases, invalid values or unsupported anchor-shape combos silently fall back | `BadgeRibbonAnchor` | — |
| `ribbon-direction` | Swallow-tail notch direction of `bookmark`: `down` (default, hangs from the top edge with the notch downward) / `left` (tab against the right edge with the notch pointing left) / `right` (tab against the left edge with the notch pointing right); physical-direction semantics (not flipped in RTL); only takes effect with `ribbon-form="bookmark"`, invalid values silently fall back to `down` | `BadgeRibbonDirection` | `down` |
| `ribbon-form` | Ribbon shape: `fold` (default, straight band + folded corner) / `diagonal` (45° diagonal band from the top corner; the band extends outside the card, so the host needs `overflow: hidden` to clip it) / `triangle` (a pure corner triangle holding a small icon or `slot="ribbon"` content) / `bookmark` (a vertical tab hanging from the top edge with a swallow-tail notch) / `side` (a vertical strip hung at mid-height of the side edge) / `seal` (a circular serrated stamp with centered text) / `banner` (a full-width strip across the top edge with angled ends) / `flag` (a side swallow-tail banner: a horizontal band with a V notch at the protruding end, always facing the protruding side) / `rounded` (rounded end) / `zigzag` (serrated end) / `arrow` (arrow-point end); the `ribbon-position` trio only affects `fold`, the other shapes have their own vertical placement, invalid values silently fall back to `fold` | `BadgeRibbonForm` | `fold` |
| `ribbon-position` | Ribbon vertical position: `hang` (default, hangs below the top edge) / `edge` (flush with the top edge) / `cross` (straddles the top edge, pressing against the card border for the strongest wrap-around feel); orthogonal to `placement` (start / end), affects the `ribbon-form="fold"` shape only, invalid values silently fall back to `hang` | `BadgeRibbonPosition` | `hang` |
| `ribbon-size` | Diagonal band size tier: `small` (default, 30px tall / xs font) / `medium` (33px / sm font, 35px pin) / `large` (36px / md font, 45px pin, wide large-type scenes); `sm`/`md`/`lg` are accepted aliases; a tier only changes the fallback of `--oas-badge-diagonal-*`, host custom properties take precedence; only takes effect with `ribbon-form="diagonal"`, invalid values silently fall back to `small` | `BadgeRibbonSize` | `small` |
| `ribbon-vertical` | Vertical position of the side-mount `bookmark` forms (`ribbon-direction="left"` / `"right"`): `center` (default, vertically centered) / `top` (flush with the top edge) / `bottom` (flush with the bottom edge); only affects the side-mount forms, invalid values silently fall back to `center` | `BadgeRibbonVertical` | `center` |
| `rolled` | End roll: a boolean modifier that rolls the card-inner end (large radius on the inner end + a darkening gradient reading as a rolled cylinder; the outer end sits flush with the card edge and no longer protrudes; pure CSS). Composes with `fold` / `flag` / `rounded` / `zigzag` / `arrow` (`banner` keeps its own angled ends and gets no roll; `diagonal` / `triangle` / `bookmark` / `side` / `seal` silently ignore it) | `boolean` | — |
| `shape` | Badge shape `round` (default) / `square` / `pill`, applying to both counts and dots | `string` | — |
| `showZero` | Whether to show when value=0 | `boolean` | — |
| `size` | Size tier: small (compact, numeric badge ~13px tall, dot 6px) / medium (default, ~16px) / large (~20px, dot 10px); invalid values silently fall back to medium | `BadgeSize` | `medium` |
| `status` | Status point: `success` / `processing` / `default` / `error` / `warning`, renders an inline standalone element of "status dot + `text`", mutually exclusive with ribbon / dot / count modes (rendered first when set); the `processing` dot pulses (`prefers-reduced-motion` disables it) | `BadgeStatus` | — |
| `text` | Ribbon or status text; the `ribbon` slot takes precedence when it has content | `string` | — |
| `value` | Number | `string` | — |
| `variant` | Variant: solid (default, filled) or outline (transparent background, border and text follow the color semantics); invalid values silently fall back to solid | `BadgeVariant` | — |

#### Slots

| Name | Description |
| --- | --- |
| default | Wrapped content (card, button, etc.); with no content the badge falls back to a standalone inline element |
| `ribbon` | Custom ribbon content |

#### CSS Variables

| CSS Variable | Description | Default |
| --- | --- | --- |
| `--oas-badge-bg` | — | `var(--oas-color-danger)` |
| `--oas-badge-diagonal-font` | — | `var(--oas-font-size-xs)` |
| `--oas-badge-diagonal-height` | — | `30px` |
| `--oas-badge-diagonal-pin` | — | `25px` |
| `--oas-badge-diagonal-text-inset` | — | `0px` |
| `--oas-badge-on-color` | — | `var(--oas-color-text-on-danger)` |
| `--oas-badge-outline` | Border and text color of the outline variant (the component injects the `-text` safe variant for semantic/preset `color`; falls back to the badge background color) | `var(--oas-badge-bg, var(--oas-color-danger))` |
| `--oas-badge-pos` | — | `translate(50%, -50%)` |
| `--oas-badge-pulse-color` | — | `var(--oas-badge-bg, var(--oas-color-danger))` |
| `--oas-diag-pin` | — | `var(--oas-badge-diagonal-pin, 25px)` |
| `--oas-ribbon-anchor-x` | — | `0px` |
| `--oas-ribbon-anchor-y` | — | `0px` |
| `--oas-ribbon-offset-x` | — | `0px` |
| `--oas-ribbon-offset-y` | — | `0px` |
| `--oas-status-color` | — | `var(--oas-color-text-secondary)` |
