Badge / Tag Set
Free · MITSingle pill component covering solid, soft, and outline badge variants across brand and semantic tones, with an optional removable tag mode.
- Category
- Core UI
- Framework
- astro
- Version
- 1.0.0
- Peer deps
- none
Install
velocityx add badge-tag
Free and MIT licensed — no account needed. The file lands under src/components/velocity/badge-tag/ by default (override with --dest), and it's yours to edit from the moment it's written.
Demo
View the live demo on the showcase site ↗
Source
Pulled live from the registry — this is the exact file velocityx add badge-tag writes into your project right now.
components/velocity/badge-tag/BadgeTag.astro
---
// VELOCITY COMPONENT · theming
//
// Colours here are Tailwind `primary-*` / `accent-*`, which resolve through
// the Velocity preset. Out of the box they are a neutral indigo — NOT the
// Aidxn brand — so this drops into your project without dragging our look in.
//
// ⚡ NOT USING VELOCITY? Run this once, in your project root:
//
// velocityx theme
//
// It writes velocity-preset.cjs + velocity-theme.css. Add the preset to your
// tailwind.config, import the css, then set your whole brand in ONE place:
//
// :root {
// --vx-brand: custom;
// --vx-primary-600: 220 38 38; <- your action colour
// --vx-primary-700: 185 28 28;
// }
//
// ⚠️ Raw RGB channels, space separated — NOT hex. `#DC2626` here silently
// breaks every /opacity variant in the library (bg-primary-600/20 etc).
//
// Prefer not to install anything? Find-and-replace `primary-` with your own
// scale name. Nothing else in this file depends on Velocity.
/**
* BadgeTag — single-pill badge/tag. Covers the badge set: solid, soft,
* outline tones across brand + semantic colours, with an optional
* removable "x" (fires a CustomEvent('tagremove') on the pill and removes
* itself — no framework required to wire up a tag input).
*
* Usage:
* <BadgeTag tone="primary">New</BadgeTag>
* <BadgeTag tone="success" variant="soft">Active</BadgeTag>
* <BadgeTag tone="secondary" variant="outline" removable>React</BadgeTag>
*/
export interface Props {
tone?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
variant?: 'solid' | 'soft' | 'outline';
size?: 'sm' | 'md';
removable?: boolean;
dot?: boolean;
}
const { tone = 'primary', variant = 'soft', size = 'md', removable = false, dot = false } = Astro.props;
const TONES = {
primary: { solid: 'bg-primary-600 text-white', soft: 'bg-primary-50 text-primary-700 dark:bg-primary-500/15 dark:text-primary-300', outline: 'border border-primary-300 text-primary-700 dark:border-primary-700 dark:text-primary-300', dot: 'bg-primary-500' },
secondary: { solid: 'bg-secondary-700 text-white', soft: 'bg-secondary-100 text-secondary-700 dark:bg-secondary-800 dark:text-secondary-300', outline: 'border border-secondary-300 text-secondary-700 dark:border-secondary-700 dark:text-secondary-300', dot: 'bg-secondary-500' },
success: { solid: 'bg-green-600 text-white', soft: 'bg-green-50 text-green-700 dark:bg-green-500/15 dark:text-green-300', outline: 'border border-green-300 text-green-700 dark:border-green-700 dark:text-green-300', dot: 'bg-green-500' },
warning: { solid: 'bg-orange-500 text-white', soft: 'bg-orange-50 text-orange-700 dark:bg-orange-500/15 dark:text-orange-300', outline: 'border border-orange-300 text-orange-700 dark:border-orange-700 dark:text-orange-300', dot: 'bg-orange-500' },
error: { solid: 'bg-red-600 text-white', soft: 'bg-red-50 text-red-700 dark:bg-red-500/15 dark:text-red-300', outline: 'border border-red-300 text-red-700 dark:border-red-700 dark:text-red-300', dot: 'bg-red-500' },
info: { solid: 'bg-blue-600 text-white', soft: 'bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300', outline: 'border border-blue-300 text-blue-700 dark:border-blue-700 dark:text-blue-300', dot: 'bg-blue-500' },
} as const;
const sizeClass = size === 'sm' ? 'text-[11px] px-2 py-0.5 gap-1' : 'text-xs px-2.5 py-1 gap-1.5';
const toneClass = TONES[tone][variant];
---
<span
class:list={['badge-tag inline-flex items-center rounded-full font-medium whitespace-nowrap', sizeClass, toneClass]}
>
{dot && <span aria-hidden="true" class:list={['h-1.5 w-1.5 rounded-full', TONES[tone].dot]} />}
<slot />
{removable && (
<button
type="button"
class="tag-remove -mr-0.5 inline-flex h-3.5 w-3.5 items-center justify-center rounded-full opacity-70 hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-current"
aria-label="Remove"
>
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
</button>
)}
</span>
{removable && (
<script>
document.querySelectorAll('.tag-remove').forEach((btn) => {
btn.addEventListener('click', () => {
const pill = btn.closest('.badge-tag');
pill?.dispatchEvent(new CustomEvent('tagremove', { bubbles: true }));
pill?.remove();
});
});
</script>
)}
Theming
Uses the shared primary-* / accent-* scale — see Theming if colours look unstyled after install.