Skip to content
Download ZIP

Blue accent palette · 4.8 KB ZIP File receipt View as Markdown View code

Preview

Fit to the available width. The frame follows the height of its content; previews taller than the maximum auto-height scroll inside it.

Give this component to your coding agent Copy a prompt that fetches this exact version and palette through the PageSugar MCP server.
cmp_badge_01 · version 1.0.0 · Blue accent palette
Using the PageSugar MCP server, fetch component cmp_badge_01 version 1.0.0 with variant "blue", first inspect its requirements and license status and confirm this project uses Svelte 5 and Tailwind CSS 4. Retrieve every manifest file, including binary assets and any manifest-only response files, preserving relative paths. Then integrate the source and follow its usage notes. Run project checks, review the browser result and report anything unverified. Do not substitute another version or invent missing files.

Not connected yet? Set up the MCP server

Code

Palette
Blue accent
Version
1.0.0
Digest
Full digest
sha256-36da6ac16a8b43f8ab1017b0715c8fbd4c317fe8cfaf4e5339f54e9eadb33340
Badge.svelte Svelte · 5.1 KB Raw
<script lang="ts" module>
	export type BadgeTone = 'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger';
	export type BadgeVariant = 'soft' | 'solid' | 'outline';
	export type BadgeSize = 'sm' | 'md';
</script>

<script lang="ts">
	import type { Snippet } from 'svelte';

	interface Props {
		/** Colour role. Neutral by default; the text, not the colour, carries the meaning. */
		tone?: BadgeTone;
		/** Fill style: a tint, a solid fill, or a hairline outline. */
		variant?: BadgeVariant;
		/** Height and text size: sm is 20 px for tables and dense rows, md is 24 px. */
		size?: BadgeSize;
		/** Show a small dot before the label, in the tone's colour. Ignored when icon is set. */
		dot?: boolean;
		/** Leading decorative icon, sized by the badge; hidden from assistive technology. */
		icon?: Snippet;
		/** Extra classes on the badge, for margins or a tighter max-width. */
		class?: string;
		/** The label: one or two words. Long labels truncate rather than wrap. */
		children: Snippet;
	}

	let {
		tone = 'neutral',
		variant = 'soft',
		size = 'md',
		dot = false,
		icon,
		class: className,
		children
	}: Props = $props();

	/*
	 * Each tone is one ink. The tint, the hairline ring and the solid fill are all mixed from it
	 * in the style block, so soft, solid and outline agree on the colour and never change the
	 * box. Complete class names, never interpolated.
	 */
	const TONES: Record<BadgeTone, string> = {
		neutral: 'badge--neutral [--_tone:var(--_ink)] [--_mark:var(--_muted)] [--_on-tone:#ffffff]',
		accent: '[--_tone:var(--_accent)] [--_mark:var(--_accent)] [--_on-tone:var(--_on-accent)]',
		info: '[--_tone:var(--_info)] [--_mark:var(--_info)] [--_on-tone:#ffffff]',
		success: '[--_tone:var(--_success)] [--_mark:var(--_success)] [--_on-tone:#ffffff]',
		warning: '[--_tone:var(--_warning)] [--_mark:var(--_warning)] [--_on-tone:#ffffff]',
		danger: '[--_tone:var(--_danger)] [--_mark:var(--_danger)] [--_on-tone:#ffffff]'
	};

	const VARIANTS: Record<BadgeVariant, { root: string; mark: string }> = {
		soft: {
			root: 'bg-(--_tint) text-(--_tone) inset-ring inset-ring-(--_ring)',
			mark: 'text-(--_mark)'
		},
		solid: { root: 'bg-(--_tone) text-(--_on-tone)', mark: 'text-(--_on-tone)' },
		outline: { root: 'text-(--_tone) inset-ring inset-ring-(--_outline)', mark: 'text-(--_mark)' }
	};

	/*
	 * The label's line box is the badge's full height, so it sits centred and is the one item
	 * aligned by baseline: the badge then shares the label's baseline with the text around it,
	 * whether or not an icon or dot leads. The side with an icon or dot sits closer to the
	 * edge, so the mark does not look indented.
	 */
	const SIZES: Record<BadgeSize, { root: string; bare: string; marked: string; mark: string }> = {
		sm: {
			root: 'h-5 gap-1 rounded-sm text-xs leading-5',
			bare: 'px-1.5',
			marked: 'ps-1 pe-1.5',
			mark: 'size-3'
		},
		md: {
			root: 'h-6 gap-1.5 rounded-md text-[0.8125rem] leading-6',
			bare: 'px-2',
			marked: 'ps-1.5 pe-2',
			mark: 'size-3.5'
		}
	};

	const step = $derived(SIZES[size] ?? SIZES.md);
	const look = $derived(VARIANTS[variant] ?? VARIANTS.soft);
	const marked = $derived(Boolean(icon) || dot);
</script>

<span
	class={[
		'badge inline-flex max-w-full min-w-0 items-center font-medium tracking-normal whitespace-nowrap tabular-nums',
		TONES[tone] ?? TONES.neutral,
		look.root,
		step.root,
		marked ? step.marked : step.bare,
		className
	]}
>
	{#if icon}
		<span
			class={['badge__icon inline-flex shrink-0 items-center justify-center', look.mark, step.mark]}
			aria-hidden="true"
		>
			{@render icon()}
		</span>
	{:else if dot}
		<span
			class={['inline-flex shrink-0 items-center justify-center', look.mark, step.mark]}
			aria-hidden="true"
		>
			<span class="size-1.5 rounded-full bg-current"></span>
		</span>
	{/if}
	<span class="badge__label min-w-0 self-baseline truncate">{@render children()}</span>
</span>

<style>
	/*
	 * Public tokens: set --badge-* on the badge or any ancestor to retone it. Each tone is one
	 * colour; its tint and rings are mixed from it below, so one variable retones all three
	 * variants. The defaults clear 4.5:1 for the label on white, zinc-50 and zinc-100 pages.
	 */
	.badge {
		--_accent: var(--badge-accent, #1d4ed8);
		--_on-accent: var(--badge-on-accent, #ffffff);
		--_ink: var(--badge-ink, #18181b);
		--_muted: var(--badge-muted, #52525b);
		--_hairline: var(--badge-hairline, rgb(0 0 0 / 0.1));
		--_info: var(--badge-info, #0369a1);
		--_success: var(--badge-success, #046c4e);
		--_warning: var(--badge-warning, #9a4a00);
		--_danger: var(--badge-danger, #b91c1c);

		/* Formulas: the soft tint, its ring, and the outline's stronger ring, from one ink. */
		--_tint: color-mix(in srgb, var(--_tone) 8%, transparent);
		--_ring: color-mix(in srgb, var(--_tone) 14%, transparent);
		--_outline: color-mix(in srgb, var(--_tone) 24%, transparent);
	}

	/* The neutral tint is already close to black at 8%, so its ring is the hairline token. */
	.badge--neutral {
		--_ring: var(--_hairline);
	}

	/* Icons passed in as a snippet fill the mark box and follow its colour. */
	.badge__icon :global(:where(svg)) {
		width: 100%;
		height: 100%;
	}
</style>

Put the label in children and pick a tone, variant and size; add an icon snippet or dot for a leading mark. It renders one span of static text. It is not a button or link, has no remove action, does not count or update live, and does not map data states to tones (use a status badge or tag chip for those).

Suggested location
src/lib/components/badge-01
Required props
children

Limitations

  • The label never wraps. Past the width of its container it truncates with an ellipsis, so keep labels to one or two words; a 40-character label in a narrow card shows only its start.
  • Colour is a hint, not the message: the tone does not add any text, so the label itself has to say what it means.
  • The accent tone's soft and outline fills use the accent as text colour. A light accent (yellow, a pastel) will fail contrast there; use the solid variant with a dark --badge-on-accent instead.
  • Light mode only. The tints are mixed with transparency, so they work on white, zinc-50 and zinc-100 pages; on a dark page set --badge-ink, --badge-muted and each tone to lighter colours.
  • The badge sits on the text baseline. Next to a large display heading you may want vertical-align: middle or a small raise through the class prop.

Example

Svelte
<script lang="ts">
	import Badge from '$lib/components/badge-01/Badge.svelte';
</script>

<h3>
	Guest access <Badge class="ms-2">Beta</Badge>
</h3>

<Badge tone="success" dot>Shipped</Badge>
<Badge tone="accent" variant="solid" size="sm">New</Badge>
<Badge tone="warning" variant="outline">
	{#snippet icon()}
		<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="5.5" /><path d="M8 5v3l2 1.25" /></svg>
	{/snippet}
	Due Friday
</Badge>

Badge#

A badge is a label, not a control. Use it to say what kind of thing an item is ("Beta", "Business plan", "Vegan") or to flag one notable fact about it. It never changes on its own and never does anything when clicked.

Picking a tone#

Most badges should be neutral. Colour earns its place when the label maps to a meaning a reader already knows: success for done or safe, warning for "look before you go on", danger for blocked or destructive, info for neutral system facts. Accent is for the one label you want to pull the eye, such as "New" on a navigation item. A panel where every row carries a coloured badge has no exceptions left to point at.

Picking a variant#

  • Soft (default): a tint with a hairline ring. Quiet enough to sit beside body text.
  • Solid: the strongest. One per view, at most.
  • Outline: a ring only, for secondary facts beside a soft badge, such as a plan name.

In a heading or a row#

Svelte
<h3 class="text-base font-semibold">
	Guest access <Badge class="ms-2">Beta</Badge>
</h3>

The badge is inline-flex and sits on the text baseline. Keep a space or margin between it and the text. Inside a flex row, put it after the label and let the row's items-center align it.

Data-driven badges#

If the label comes from a status field (draft, in_review, merged) and the tone should follow it, map the field to a label and tone in your own code, or use a status badge component that does the mapping for you. This component deliberately takes neither a status key nor a count.

Props and content inputs#

On this page
NameTypeRequiredDefaultDescription
tone'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger'No'neutral'Colour role. Neutral suits most labels; accent follows --badge-accent. The text, not the colour, carries the meaning.
variant'soft' | 'solid' | 'outline'No'soft'Fill style: a tint with a hairline ring, a solid fill, or a ring alone. All three keep the same box size.
size'sm' | 'md'No'md'sm is 20 px tall with 12 px text, for tables and dense rows; md is 24 px with 13 px text.
dotbooleanNofalseShow a small dot in the tone's colour before the label. Ignored when an icon is given.
iconSnippetNoNoneLeading decorative icon, sized to 12 or 14 px and coloured by the tone. Hidden from assistive technology.
classstringNoNoneExtra classes on the badge, for margins or a tighter max-width.
childrenSnippetYesNoneThe label: one or two words.

Customization#

On this page

Choose tone, variant and size through props. Retone with --badge-* variables: one per tone, and the tints and rings follow. Edit the TONES, VARIANTS and SIZES maps in the source to change the recipe.

  • Brand colour: set --badge-accent (and --badge-on-accent for the solid fill's text) on the page or any ancestor. The accent tone's tint and ring are mixed from it, so all three variants follow.
  • Semantic colours: --badge-info, --badge-success, --badge-warning and --badge-danger each set one tone. The label must clear 4.5:1 against each variant's own background: the page for outline, the 8% tint of the colour for soft (a colour that only just passes on white fails there), and the colour itself against white text for solid.
  • Neutral tone: --badge-ink is the label colour, --badge-muted the colour of its dot or icon, and --badge-hairline its soft ring.
  • Dark or tinted page: set --badge-ink to zinc-100, --badge-muted to zinc-400, --badge-hairline to rgb(255 255 255 / 0.12), and each tone to its 300 or 400 step. Solid fills then want a dark label: change the #ffffff values in TONES to zinc-950.
  • Shape: the badge is a 4 px (sm) or 6 px (md) rectangle. For a pill, change rounded-sm and rounded-md to rounded-full in SIZES.
  • In a heading: put the badge after the text with a margin (class="ms-2"). It sits on the baseline; add align-middle through class if you prefer it centred on a large title.
  • Width: pass class="max-w-40" to truncate earlier in a dense table.

Public CSS variables

VariableToken
--badge-accentaccent
--badge-on-accentonAccent
--badge-inkink
--badge-mutedmuted
--badge-hairlinehairline
--badge-infoinfo
--badge-successsuccess
--badge-warningwarning
--badge-dangerdanger

Accessibility#

On this page
  • Renders a plain span with no role and no live region: a badge is static text read in place, so it is not announced on its own.
  • The label is the meaning. Tone colour only reinforces it, so never rely on a colour difference (a green Shipped and a red Shipped) to say something the text does not.
  • Icons and dots are aria-hidden. If an icon carries meaning the label does not, put that meaning in the label.
  • A truncated label is cut visually only; the whole text stays in the DOM and is read in full. Sighted users see only the start, so keep labels short or give the full text elsewhere.
  • Every tone and variant clears 4.5:1 for its label in the default palette on white, zinc-50 and zinc-100 pages. Re-check if you set a tone variable.
  • Not focusable and not interactive. For a removable or clickable token use a tag chip, which brings its own button semantics.

Known limitations

  • Contrast is checked for the default colours on light pages only; retoned or dark pages are the consumer's to check.

Release details#

On this page
Integration
  • Presentational
  • Works without client-side JavaScript
  • Server-side rendering supported
Dependencies
No additional runtime packages beyond Svelte and Tailwind CSS
License

MIT. Default license approval is pending; see the license status before adopting the source.

Version history
  • 1.0.0 (Published) Current release · 1 October 2026

Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.