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
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "secondary" | "destructive" | "outline" | "ghost" | "default" | Visual style of the badge |
size | "sm" | "md" | "lg" | "sm" | Size of the badge |
icon | ReactNode | undefined | Icon to render in the parent badge |
iconPosition | "start" | "end" | "start" | Icon placement within the parent badge |
leadingBadge | ReactNode | undefined | Nested badge content or element |
leadingBadgePosition | "start" | "end" | "start" | Placement of the nested badge |
leadingBadgeIcon | ReactNode | undefined | Icon rendered inside the nested badge (when positioned at end) |
groupSize | "md" | "lg" | "md" | Parent height when using a nested badge |
render | ReactElement | ((props: HTMLProps) => ReactElement) | undefined | Polymorphic render prop from Base UI |
className | string | undefined | Additional CSS classes |
All other props from @base-ui/react span rendering are supported.
Label