Canvas Design System
Main Site Tokens

Badge

A small, non-interactive label that attaches a fact — duration, category, status, count — to something else.

When to Use

Use when: You're annotating a card, row, or heading with metadata the reader scans rather than reads — "45 min", "Ideation", "Completed", "3". Badges are static by design.
Don't use when: The label does something when clicked — use Button. For an activity type use Activity Badge, for a removable tag use Pill, and for anything longer than three words use plain text.

Variants

Types

Six variants. category takes a custom color; status takes a status key; the rest are self-contained.

Default 45 min Ideation Team Building NEW 3
<?= ws_badge('Default') ?>
<?= ws_badge('45 min', ['variant' => 'duration', 'icon' => 'schedule']) ?>
<?= ws_badge('Ideation', ['variant' => 'category', 'color' => '#F59E0B']) ?>
<?= ws_badge('NEW', ['variant' => 'new']) ?>
<?= ws_badge('3', ['variant' => 'count']) ?>

Status

Four semantic colours. Reach for the ws_status() shorthand rather than assembling the variant and status keys by hand.

Completed Pending Failed In Review
<?= ws_status('Completed', 'success') ?>
<?= ws_status('Pending', 'warning') ?>
<?= ws_status('Failed', 'error') ?>
<?= ws_status('In Review', 'info') ?>

Sizes

Small Medium Large
<?= ws_badge('Small', ['size' => 'sm']) ?>
<?= ws_badge('Large', ['size' => 'lg']) ?>

Pill

Swaps the default radius for a full round. Pick one shape per surface and hold it — mixed radii in the same meta row read as a bug.

Rounded Ideation
<?= ws_badge('Rounded', ['pill' => true]) ?>

Shorthand helpers

Three convenience wrappers cover the cases that appear most. Each just calls ws_badge() with fixed options, so anything below applies to them too.

45 min 2 hours Strategy Retrospective
HelperEquivalent to
ws_duration($text, $withIcon = true)ws_badge($text, ['variant' => 'duration', 'icon' => 'schedule', 'size' => 'sm']) — pass false to drop the icon
ws_category($name, $color = null)ws_badge($name, ['variant' => 'category', 'color' => $color])
ws_status($text, $status = 'info')ws_badge($text, ['variant' => 'status', 'status' => $status])
<?= ws_duration('45 min') ?>
<?= ws_category('Strategy', '#3B82F6') ?>
<?= ws_status('Completed', 'success') ?>

States

StateBehavior
DefaultThe only state. Badges are static <span>s — no hover, focus, active, or disabled treatment.
Inside an interactive parentA badge in a hoverable card inherits nothing; the parent owns the hover. The --transition-fast token is present for parent-driven colour changes.
Empty textRenders an empty badge — a visible coloured sliver. Guard against empty strings at the call site.

Real-World Usage

A library card's meta row: category from the database with its stored colour, duration via the shorthand, and a status badge only when the item needs attention.

Assumption Mapping
Discovery 30 min New
<div class="card-meta">
    <?= ws_category($ex['category_name'], $ex['category_color']) ?>
    <?= ws_duration($ex['duration'] . ' min') ?>
    <?php if ($ex['is_new']): ?><?= ws_status('New', 'info') ?><?php endif; ?>
</div>

Options

OptionTypeDefaultPurpose
$textstring''Positional label. Keep it to one or two words.
variantstring'default'default | duration | category | status | count | new
sizestring'md'sm | md | lg
iconstringnullMaterial icon rendered before the text. Not auto-selected — even duration needs it passed explicitly (the ws_duration() shorthand does this for you).
colorstringnullHex colour for the category variant, applied inline
statusstringnullsuccess | warning | error | info. Only meaningful with variant: 'status'.
pillboolfalseFull border-radius
idstringnullElement ID
classstring''Additional CSS classes
attrsarray[]Extra HTML attributes as key/value pairs

Accessibility

ConcernBehavior
ARIANone applied. The badge is a text <span> in the document flow, so its content is announced in reading order along with whatever it annotates.
KeyboardNot focusable. If a badge ever needs to be actionable, that's a Button.
Colour independenceThe label always carries the meaning in words — "Completed", not a bare green dot. Preserve that: don't ship a status badge whose text is generic while only the colour distinguishes it.
Context for countsThe count variant renders a bare number, which is meaningless alone to a screen-reader user. Put the noun in adjacent text or an aria-label on the parent — "3 unread notes", not "3".
ContrastBuilt-in variants pair tested token foreground/background. A custom color on the category variant bypasses that — check it before shipping.

Tokens

TokenUsed for
--badge-bg / --badge-color / --badge-radiusBase fill, text, and corner radius
--gray-100 / --gray-200 / --gray-500 / --gray-600Default and duration variant neutrals
--mainsite-primarynew and count variant fill
--color-success-light / --color-success-darkSuccess status
--color-warning-light / --color-warning-darkWarning status
--color-error-light / --color-error-darkError status
--color-info-light / --color-info-darkInfo status
--text-inverseText on solid fills
--radius-fullPill shape and the circular count badge
--text-micro / --text-caption / --text-meta / --text-small / --text-bodyType scale across sizes
--font-body / --font-medium / --font-semiboldFace and weight
--transition-fastColour transition when a parent changes state

CSS Classes

ClassPurpose
.ws-badgeBase styles
.ws-badge--defaultNeutral variant
.ws-badge--durationDuration variant
.ws-badge--categoryCategory variant (accepts the inline custom colour)
.ws-badge--new"New" label variant
.ws-badge--countCircular count variant
.ws-badge--success / --warning / --error / --infoStatus colours, emitted from the status option
.ws-badge--sm / --md / --lgSize modifiers
.ws-badge--pillFull radius
.ws-badge__iconLeading icon wrapper
.ws-badge__textLabel wrapper — emitted but currently has no CSS rule. A hook for overrides, not an active style.

Files

FilePurpose
includes/components/helpers.phpws_badge() plus the ws_duration(), ws_category(), and ws_status() shorthands
includes/components/badge.phpTemplate — variant and status class assembly
includes/components/components.cssStyles (.ws-badge rules)