sveltelean Primitives
Versionv0.2.0 GitHub

Example

Tones and sizes
Draft New Paid Due soon Overdue
Small Medium Large
<div class="row">
	<span data-slean="badge">Draft</span>
	<span data-slean="badge" data-variant="accent">New</span>
	<span data-slean="badge" data-variant="success">Paid</span>
	<span data-slean="badge" data-variant="warning">Due soon</span>
	<span data-slean="badge" data-variant="danger">Overdue</span>
</div>
<div class="row">
	<span data-slean="badge" data-size="sm">Small</span>
	<span data-slean="badge">Medium</span>
	<span data-slean="badge" data-size="lg">Large</span>
</div>
Tier 0: the two sources differ only by the stylesheet import and the row layout. Each tone is a soft fill and a text color from the status tokens; the words carry the meaning.

Why this implementation exists

A badge is a word or a number in a pill. The browser renders and reads text without help, so the primitive is a stylesheet and a contract: which tokens color each tone, and how a badge takes part in the name of what it labels.

The accessibility questions are the real content. A tone is color, and color alone is not information: forced-colors mode shows every tone alike, so the word has to say it. A count on an icon button is part of the button’s name, not a separate announcement.

The browser owns

  • the text and its reading order
  • the name of the control a count badge sits in (aria-label)

Svelte Lean owns

  • badge.css: the pill, five tones from the status tokens, three sizes, tabular numbers
  • the contract: the text carries the meaning, the count belongs to the control’s name
  • documentation

Usage

The markup needs no package. Install @svelte-lean/styles for the stylesheet; @svelte-lean/primitives adds the typed contract and nothing at runtime.

npm install @svelte-lean/styles
stylesheets
import '@svelte-lean/styles/badge.css';

Tones are data-variant="accent", "success", "warning" and "danger", with the neutral look as the default. Sizes are data-size="sm", the default and "lg".

A badge that changes while the page is open is not announced. When the change matters, write it to a live region (role="status") as well.

Anatomy

PartElementdata-slean-partRequiredNotes
root<span data-slean="badge">–yesdata-variant="neutral|accent|success|warning|danger", data-size="sm|md|lg". Text inside.

Runtime profile

Tier 0: the badge has no behavior module, the Vite plugin maps badge to no module, and the native-only fixture proves the path ships no Svelte Lean JavaScript.

Accessibility contract

  • A badge is plain text in the reading order; it has no role and is not focusable.
  • The text carries the meaning; the tone repeats it, and forced colors show every tone alike with a visible border.
  • Inside a control, the count is part of the control’s name (aria-label="Inbox, 3 unread") and the badge is aria-hidden.
  • Every tone pair is a soft fill with its own text color from the tokens, checked by axe on the playground page.

Keyboard

KeyWhenResult
TabanywherePasses the badge by: it is text, not a control

Platform features

FeatureBaselineOutside the target
<span> and textWidely availableNot applicable within the support policy

Without JavaScript

Fully functional.

Server rendering

Static HTML.

Before hydration

Nothing is attached.

Styling

badge.css draws the pill, the neutral fill, the four tones from the accent and status tokens, the three sizes and tabular numbers. It positions nothing: the count recipe above is the application's CSS. The tokens it reads:

TokenDefault (light)Applies to
--slean-space-10.25remgap, padding-inline
--slean-space-20.5rempadding-inline
--slean-radius-full9999pxborder-radius
--slean-mutedvar(--slean-neutral-3)background
--slean-fgvar(--slean-neutral-12)color
--slean-text-xs0.75remfont-size
--slean-font-weight-medium500font-weight
--slean-accent-softoklch(95% 0.03 258)background
--slean-accent-soft-fgoklch(42% 0.17 258)color
--slean-success-softoklch(95.5% 0.04 150)background
--slean-success-soft-fgoklch(40% 0.12 150)color
--slean-warning-softoklch(96% 0.05 85)background
--slean-warning-soft-fgoklch(45% 0.12 75)color
--slean-danger-softoklch(95.5% 0.03 25)background
--slean-danger-soft-fgoklch(45% 0.18 25)color
--slean-space-30.75rempadding-inline
--slean-text-sm0.875remfont-size

Variant attributes: data-variant (accent, success, warning, danger); data-size (sm, lg).

Compatibility notes

Everything here is widely available.

Examples

Count on an icon button

The count belongs to the button's name, so it is in the button's aria-label and the badge is aria-hidden. Placing the badge on the corner is the application's CSS: one declaration on the button, and on the badge its position and a ring in the page color.

inbox-button.html
<button type="button" data-slean="button" data-variant="outline" data-icon-only
	aria-label="Inbox, 3 unread" class="has-count">
	<svg aria-hidden="true" viewBox="0 0 24 24" width="18" height="18">…</svg>
	<span data-slean="badge" data-variant="danger" data-size="sm" aria-hidden="true"
		class="count">3</span>
</button>
app.css
/* Application CSS: the badge package does not position anything. */
.has-count {
	position: relative;
}

.count {
	position: absolute;
	inset-block-start: -0.375rem;
	inset-inline-end: -0.375rem;
	/* A ring in the page color separates the badge from the button's border. */
	box-shadow: 0 0 0 2px var(--slean-bg);
}

Testing

Source