MC Badge

A small label for status or categorization.

Overview

The mc-badge is a compact label used to highlight status, categories, or metadata. It is built on @base-ui/react and styled with Tailwind CSS v4 for consistent design.

Preview

Loading...
import { ArrowRight } from 'lucide-react';import { McBadge } from '../ui/mc-badge';const DotIcon = ({ className }: { className?: string }) => (  <span className={`rounded-full bg-current inline-block ${className ?? 'size-1.5'}`} />);export default function McBadgeDemo() {  return (    <div className="flex w-full flex-wrap gap-2">      <McBadge>Label</McBadge>      <McBadge variant="secondary">Secondary</McBadge>      <McBadge variant="destructive">Destructive</McBadge>      <McBadge variant="outline">Outline</McBadge>      <McBadge icon={<DotIcon className="size-1.5" />} iconPosition="start">        Status      </McBadge>      <McBadge icon={<ArrowRight />} iconPosition="end">        Learn more      </McBadge>      <McBadge        groupSize="md"        leadingBadge="New feature"        leadingBadgePosition="start"        icon={<ArrowRight />}        iconPosition="end"      >        We’ve just released a new feature      </McBadge>      <McBadge image="https://flagcdn.com/w40/au.png" imageAlt="Australia">        Label      </McBadge>    </div>  );}

Add to Your Project

Deploy the mc-badge directly to your components/ui directory:

npx mcoli-ui@latest add mc-badge
pnpm dlx mcoli-ui@latest add mc-badge
yarn dlx mcoli-ui@latest add mc-badge
bunx mcoli-ui@latest add mc-badge

Manual Setup

If you prefer manual control:

Install the core dependencies
npm install @base-ui/react
pnpm add @base-ui/react
yarn add @base-ui/react
bun add @base-ui/react
Copy the source code
import { mergeProps } from '@base-ui/react/merge-props';import { useRender } from '@base-ui/react/use-render';import { cva, type VariantProps } from 'class-variance-authority';import type { ReactNode } from 'react';import { cloneElement, isValidElement } from 'react';import { cn } from '@/lib/utils';const McBadgeVariants = cva(  'group/badge inline-flex shrink-0 items-center justify-center gap-1 overflow-hidden rounded-[16px] border border-[#E6E9FF] text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:size-3 [&>svg]:shrink-0 [&>svg]:self-center',  {    variants: {      variant: {        default: 'bg-primary-foreground text-primary [a]:hover:bg-primary-foreground/80',        secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',        destructive:          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',        outline: 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',        ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',        leading: 'bg-accent-foreground text-accent',      },      size: {        sm: 'h-[22px] min-w-[47px] px-2 py-0.5 text-sm leading-[18px]',        md: 'h-[24px] min-w-[56px] px-[10px] py-0.5 text-sm leading-[20px]',        lg: 'h-[32px] min-w-[65px] px-3 py-1 text-sm leading-[24px]',        groupMd: 'h-[22px] min-w-[47px] px-2 py-0.5 text-sm leading-[18px]',        groupLg: 'h-[28px] min-w-[56px] px-[10px] py-1 text-sm leading-[20px]',      },    },    defaultVariants: {      variant: 'default',      size: 'sm',    },  });function McBadge({  className,  variant = 'default',  size = 'sm',  render,  icon,  iconPosition = 'start',  iconOnly = false,  image,  imageAlt = '',  imagePosition = 'start',  leadingBadge,  leadingBadgePosition = 'start',  leadingBadgeIcon,  groupSize = 'md',  children,  ...props}: useRender.ComponentProps<'span'> &  VariantProps<typeof McBadgeVariants> & {    icon?: ReactNode;    iconPosition?: 'start' | 'end';    iconOnly?: boolean;    image?: string;    imageAlt?: string;    imagePosition?: 'start' | 'end';    leadingBadge?: ReactNode;    leadingBadgePosition?: 'start' | 'end';    leadingBadgeIcon?: ReactNode;    groupSize?: 'md' | 'lg';  }) {  const groupSizeClasses = {    md: 'h-[30px] leading-[22px]',    lg: 'h-[36px] leading-[26px]',  } as const;  const groupChildSize = groupSize === 'md' ? 'groupMd' : 'groupLg';  const imageEl = image ? (    <img src={image} alt={imageAlt} className="size-4 shrink-0 rounded-full object-cover" />  ) : null;  const useParentIcon = !leadingBadge || leadingBadgePosition === 'start';  const rawIcon = useParentIcon ? icon : undefined;  const parentIcon = rawIcon ? (    <span style={{ display: 'contents', pointerEvents: 'none' }}>      <span        style={{          width: 12,          height: 12,          display: 'inline-flex',          alignItems: 'center',          justifyContent: 'center',          flexShrink: 0,        }}      >        {rawIcon}      </span>    </span>  ) : undefined;  const parentIconPlacement = parentIcon    ? iconPosition === 'end'      ? 'inline-end'      : 'inline-start'    : undefined;  const contentWithIcon = parentIcon ? (    iconPosition === 'end' ? (      <>        {children}        {parentIcon}      </>    ) : (      <>        {parentIcon}        {children}      </>    )  ) : (    children  );  const content = imageEl ? (    imagePosition === 'end' ? (      <>        {contentWithIcon}        {imageEl}      </>    ) : (      <>        {imageEl}        {contentWithIcon}      </>    )  ) : (    contentWithIcon  );  const resolvedLeadingBadge = leadingBadge ? (    isValidElement(leadingBadge) ? (      cloneElement(leadingBadge as React.ReactElement<Record<string, unknown>>, {        size: groupChildSize,        variant: 'leading', // ← forcé        icon: leadingBadgePosition === 'end' ? leadingBadgeIcon : undefined,        iconPosition: 'end',      })    ) : (      <McBadge        size={groupChildSize}        variant="leading" // ← forcé        icon={leadingBadgePosition === 'end' ? leadingBadgeIcon : undefined}        iconPosition="end"      >        {leadingBadge}      </McBadge>    )  ) : null;  const groupedContent = resolvedLeadingBadge ? (    leadingBadgePosition === 'end' ? (      <div className="flex items-center gap-3">        {content}        {resolvedLeadingBadge}      </div>    ) : (      <div className="flex items-center gap-3">        {resolvedLeadingBadge}        {content}      </div>    )  ) : (    content  );  const dataIconProps = parentIconPlacement    ? ({ 'data-icon': parentIconPlacement } as Record<string, string>)    : undefined;  const iconOnlyClasses = iconOnly ? 'min-w-0 px-1.5' : undefined;  const imagePaddingClasses = image    ? imagePosition === 'start'      ? 'pl-[3px]'      : 'pr-[3px]'    : undefined;  const groupPaddingClasses = resolvedLeadingBadge    ? leadingBadgePosition === 'start'      ? 'pl-1 pr-[10px]'      : 'pl-[10px] pr-1'    : undefined;  return useRender({    defaultTagName: 'span',    props: mergeProps<'span'>(      {        className: cn(          McBadgeVariants({ variant, size }),          leadingBadge ? groupSizeClasses[groupSize] : undefined,          iconOnlyClasses,          imagePaddingClasses,          groupPaddingClasses,          className        ),        ...(dataIconProps ?? {}),        children: groupedContent,      },      props    ),    render,    state: {      slot: 'badge',      variant,      size,    },  });}export { McBadge, McBadgeVariants };
Adjust import paths

Ensure your @/lib/utils or equivalent classname helper is correctly imported.

Usage

import { McBadge } from '@/components/ui/mc-badge';

export default function Example() {
  return <McBadge>Label</McBadge>;
}

Examples

Variants

<McBadge variant="default">Default</McBadge>
<McBadge variant="secondary">Secondary</McBadge>
<McBadge variant="destructive">Destructive</McBadge>
<McBadge variant="outline">Outline</McBadge>
<McBadge variant="ghost">Ghost</McBadge>

Sizes

<McBadge size="sm">Small</McBadge>
<McBadge size="md">Medium</McBadge>
<McBadge size="lg">Large</McBadge>

Icon Support

import { ArrowRight, Dot } from "lucide-react"

<McBadge icon={<Dot />} iconPosition="start">
  Status
</McBadge>

<McBadge icon={<ArrowRight />} iconPosition="end">
  More
</McBadge>

Badge Group

Use leadingBadge to render a nested badge before or after the parent content. When the child badge is after the content, the optional icon is rendered inside the child badge.

import { ArrowRight } from "lucide-react"

<McBadge
  groupSize="md"
  leadingBadge="New"
  leadingBadgePosition="start"
  icon={<ArrowRight />}
  iconPosition="end"
>
  We just shipped a feature
</McBadge>

<McBadge
  groupSize="lg"
  leadingBadge="New"
  leadingBadgePosition="end"
  leadingBadgeIcon={<ArrowRight />}
>
  We just shipped a feature
</McBadge>

API Reference

Props

PropTypeDefaultDescription
variant"default" | "secondary" | "destructive" | "outline" | "ghost""default"Visual style of the badge
size"sm" | "md" | "lg""sm"Size of the badge
iconReactNodeundefinedIcon to render in the parent badge
iconPosition"start" | "end""start"Icon placement within the parent badge
leadingBadgeReactNodeundefinedNested badge content or element
leadingBadgePosition"start" | "end""start"Placement of the nested badge
leadingBadgeIconReactNodeundefinedIcon rendered inside the nested badge (when positioned at end)
groupSize"md" | "lg""md"Parent height when using a nested badge
renderReactElement | ((props: HTMLProps) => ReactElement)undefinedPolymorphic render prop from Base UI
classNamestringundefinedAdditional CSS classes

All other props from @base-ui/react span rendering are supported.

On this page