Skip to content
Velocity X Docs
← All components

Badge / Tag Set

Free · MIT

Single 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

bash
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

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.