Pill
Flexible badge pill component for React and Next.js applications. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design featuring status indicators, avatar integration, and customizable variants.
Powered by
'use client';import { Pill, PillAvatar, PillAvatarGroup, PillButton, PillDelta, PillIcon, PillIndicator, PillStatus,} from '@/components/ui/shadcn-io/pill';import { CheckCircleIcon, UsersIcon, XIcon } from 'lucide-react';const Example = () => ( <div className="flex flex-wrap items-center justify-center gap-2"> <Pill> <PillAvatar fallback="HB" src="https://pbs.twimg.com/profile_images/1748718473595789312/PbqJh9hk_400x400.jpg" /> @haydenbleasel </Pill> <Pill> <PillStatus> <CheckCircleIcon className="text-emerald-500" size={12} /> Passed </PillStatus> Approval Status </Pill> <Pill> #kibo-ui <PillButton size="icon" variant="ghost"> <XIcon size={12} /> </PillButton> </Pill> <Pill> <PillIndicator pulse variant="success" /> Active </Pill> <Pill> <PillIndicator variant="error" /> Error </Pill> <Pill> <PillDelta delta={10} /> Up 10% </Pill> <Pill> <PillDelta delta={-5} /> Down 5% </Pill> <Pill> <PillDelta delta={0} /> No change </Pill> <Pill> <PillIcon icon={UsersIcon} /> 17 users </Pill> <Pill> <PillAvatarGroup> <PillAvatar fallback="HB" src="https://pbs.twimg.com/profile_images/1748718473595789312/PbqJh9hk_400x400.jpg" /> <PillAvatar fallback="SC" src="https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg" /> <PillAvatar fallback="LR" src="https://pbs.twimg.com/profile_images/1862717563311968256/xfgt1L9l_400x400.jpg" /> </PillAvatarGroup> Loved by millions </Pill> </div>);export default Example;
import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from 'lucide-react';import type { ComponentProps, ReactNode } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { Badge } from '@/components/ui/badge';import { Button } from '@/components/ui/button';import { cn } from '@/lib/utils';export type PillProps = ComponentProps<typeof Badge> & { themed?: boolean;};export const Pill = ({ variant = 'secondary', themed = false, className, ...props}: PillProps) => ( <Badge className={cn('gap-2 rounded-full px-3 py-1.5 font-normal', className)} variant={variant} {...props} />);export type PillAvatarProps = ComponentProps<typeof AvatarImage> & { fallback?: string;};export const PillAvatar = ({ fallback, className, ...props}: PillAvatarProps) => ( <Avatar className={cn('-ml-1 h-4 w-4', className)}> <AvatarImage {...props} /> <AvatarFallback>{fallback}</AvatarFallback> </Avatar>);export type PillButtonProps = ComponentProps<typeof Button>;export const PillButton = ({ className, ...props }: PillButtonProps) => ( <Button className={cn( '-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5', className )} size="icon" variant="ghost" {...props} />);export type PillStatusProps = { children: ReactNode; className?: string;};export const PillStatus = ({ children, className, ...props}: PillStatusProps) => ( <div className={cn( 'flex items-center gap-2 border-r pr-2 font-medium', className )} {...props} > {children} </div>);export type PillIndicatorProps = { variant?: 'success' | 'error' | 'warning' | 'info'; pulse?: boolean;};export const PillIndicator = ({ variant = 'success', pulse = false,}: PillIndicatorProps) => ( <span className="relative flex size-2"> {pulse && ( <span className={cn( 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', variant === 'success' && 'bg-emerald-400', variant === 'error' && 'bg-rose-400', variant === 'warning' && 'bg-amber-400', variant === 'info' && 'bg-sky-400' )} /> )} <span className={cn( 'relative inline-flex size-2 rounded-full', variant === 'success' && 'bg-emerald-500', variant === 'error' && 'bg-rose-500', variant === 'warning' && 'bg-amber-500', variant === 'info' && 'bg-sky-500' )} /> </span>);export type PillDeltaProps = { className?: string; delta: number;};export const PillDelta = ({ className, delta }: PillDeltaProps) => { if (!delta) { return ( <MinusIcon className={cn('size-3 text-muted-foreground', className)} /> ); } if (delta > 0) { return ( <ChevronUpIcon className={cn('size-3 text-emerald-500', className)} /> ); } return <ChevronDownIcon className={cn('size-3 text-rose-500', className)} />;};export type PillIconProps = { icon: typeof ChevronUpIcon; className?: string;};export const PillIcon = ({ icon: Icon, className, ...props}: PillIconProps) => ( <Icon className={cn('size-3 text-muted-foreground', className)} size={12} {...props} />);export type PillAvatarGroupProps = { children: ReactNode; className?: string;};export const PillAvatarGroup = ({ children, className, ...props}: PillAvatarGroupProps) => ( <div className={cn( '-space-x-1 flex items-center', '[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]', className )} {...props} > {children} </div>);
Installation
npx shadcn@latest add https://www.shadcn.io/registry/pill.json
npx shadcn@latest add https://www.shadcn.io/registry/pill.json
pnpm dlx shadcn@latest add https://www.shadcn.io/registry/pill.json
bunx shadcn@latest add https://www.shadcn.io/registry/pill.json
Features
- Badge variants - Rounded pill styling with consistent padding using Tailwind CSS utilities
- Avatar integration - Built-in avatar support with fallback options using shadcn/ui Avatar component
- Status indicators - Success, error, warning, info variants with pulse animations using React state
- Delta displays - Change indicators for increase/decrease values using TypeScript props
- Icon support - Lucide icon integration with consistent styling for Next.js applications
- Avatar groups - Multiple avatar overlapping with configurable spacing using JavaScript positioning
- Button integration - Ghost button support for dismissible pills using event handlers
- Open source - Free pill component with themeable variants and flexible content
Examples
Avatar
A simple pill with an avatar and text.
'use client';import { Pill, PillAvatar } from '@/components/ui/shadcn-io/pill';const Example = () => ( <Pill> <PillAvatar fallback="HB" src="https://pbs.twimg.com/profile_images/1748718473595789312/PbqJh9hk_400x400.jpg" /> @haydenbleasel </Pill>);export default Example;
import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from 'lucide-react';import type { ComponentProps, ReactNode } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { Badge } from '@/components/ui/badge';import { Button } from '@/components/ui/button';import { cn } from '@/lib/utils';export type PillProps = ComponentProps<typeof Badge> & { themed?: boolean;};export const Pill = ({ variant = 'secondary', themed = false, className, ...props}: PillProps) => ( <Badge className={cn('gap-2 rounded-full px-3 py-1.5 font-normal', className)} variant={variant} {...props} />);export type PillAvatarProps = ComponentProps<typeof AvatarImage> & { fallback?: string;};export const PillAvatar = ({ fallback, className, ...props}: PillAvatarProps) => ( <Avatar className={cn('-ml-1 h-4 w-4', className)}> <AvatarImage {...props} /> <AvatarFallback>{fallback}</AvatarFallback> </Avatar>);export type PillButtonProps = ComponentProps<typeof Button>;export const PillButton = ({ className, ...props }: PillButtonProps) => ( <Button className={cn( '-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5', className )} size="icon" variant="ghost" {...props} />);export type PillStatusProps = { children: ReactNode; className?: string;};export const PillStatus = ({ children, className, ...props}: PillStatusProps) => ( <div className={cn( 'flex items-center gap-2 border-r pr-2 font-medium', className )} {...props} > {children} </div>);export type PillIndicatorProps = { variant?: 'success' | 'error' | 'warning' | 'info'; pulse?: boolean;};export const PillIndicator = ({ variant = 'success', pulse = false,}: PillIndicatorProps) => ( <span className="relative flex size-2"> {pulse && ( <span className={cn( 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', variant === 'success' && 'bg-emerald-400', variant === 'error' && 'bg-rose-400', variant === 'warning' && 'bg-amber-400', variant === 'info' && 'bg-sky-400' )} /> )} <span className={cn( 'relative inline-flex size-2 rounded-full', variant === 'success' && 'bg-emerald-500', variant === 'error' && 'bg-rose-500', variant === 'warning' && 'bg-amber-500', variant === 'info' && 'bg-sky-500' )} /> </span>);export type PillDeltaProps = { className?: string; delta: number;};export const PillDelta = ({ className, delta }: PillDeltaProps) => { if (!delta) { return ( <MinusIcon className={cn('size-3 text-muted-foreground', className)} /> ); } if (delta > 0) { return ( <ChevronUpIcon className={cn('size-3 text-emerald-500', className)} /> ); } return <ChevronDownIcon className={cn('size-3 text-rose-500', className)} />;};export type PillIconProps = { icon: typeof ChevronUpIcon; className?: string;};export const PillIcon = ({ icon: Icon, className, ...props}: PillIconProps) => ( <Icon className={cn('size-3 text-muted-foreground', className)} size={12} {...props} />);export type PillAvatarGroupProps = { children: ReactNode; className?: string;};export const PillAvatarGroup = ({ children, className, ...props}: PillAvatarGroupProps) => ( <div className={cn( '-space-x-1 flex items-center', '[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]', className )} {...props} > {children} </div>);
Status
A pill with a status indicator and text.
'use client';import { Pill, PillStatus } from '@/components/ui/shadcn-io/pill';import { CheckCircleIcon } from 'lucide-react';const Example = () => ( <Pill> <PillStatus> <CheckCircleIcon className="text-emerald-500" size={12} /> Passed </PillStatus> Approval Status </Pill>);export default Example;
import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from 'lucide-react';import type { ComponentProps, ReactNode } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { Badge } from '@/components/ui/badge';import { Button } from '@/components/ui/button';import { cn } from '@/lib/utils';export type PillProps = ComponentProps<typeof Badge> & { themed?: boolean;};export const Pill = ({ variant = 'secondary', themed = false, className, ...props}: PillProps) => ( <Badge className={cn('gap-2 rounded-full px-3 py-1.5 font-normal', className)} variant={variant} {...props} />);export type PillAvatarProps = ComponentProps<typeof AvatarImage> & { fallback?: string;};export const PillAvatar = ({ fallback, className, ...props}: PillAvatarProps) => ( <Avatar className={cn('-ml-1 h-4 w-4', className)}> <AvatarImage {...props} /> <AvatarFallback>{fallback}</AvatarFallback> </Avatar>);export type PillButtonProps = ComponentProps<typeof Button>;export const PillButton = ({ className, ...props }: PillButtonProps) => ( <Button className={cn( '-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5', className )} size="icon" variant="ghost" {...props} />);export type PillStatusProps = { children: ReactNode; className?: string;};export const PillStatus = ({ children, className, ...props}: PillStatusProps) => ( <div className={cn( 'flex items-center gap-2 border-r pr-2 font-medium', className )} {...props} > {children} </div>);export type PillIndicatorProps = { variant?: 'success' | 'error' | 'warning' | 'info'; pulse?: boolean;};export const PillIndicator = ({ variant = 'success', pulse = false,}: PillIndicatorProps) => ( <span className="relative flex size-2"> {pulse && ( <span className={cn( 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', variant === 'success' && 'bg-emerald-400', variant === 'error' && 'bg-rose-400', variant === 'warning' && 'bg-amber-400', variant === 'info' && 'bg-sky-400' )} /> )} <span className={cn( 'relative inline-flex size-2 rounded-full', variant === 'success' && 'bg-emerald-500', variant === 'error' && 'bg-rose-500', variant === 'warning' && 'bg-amber-500', variant === 'info' && 'bg-sky-500' )} /> </span>);export type PillDeltaProps = { className?: string; delta: number;};export const PillDelta = ({ className, delta }: PillDeltaProps) => { if (!delta) { return ( <MinusIcon className={cn('size-3 text-muted-foreground', className)} /> ); } if (delta > 0) { return ( <ChevronUpIcon className={cn('size-3 text-emerald-500', className)} /> ); } return <ChevronDownIcon className={cn('size-3 text-rose-500', className)} />;};export type PillIconProps = { icon: typeof ChevronUpIcon; className?: string;};export const PillIcon = ({ icon: Icon, className, ...props}: PillIconProps) => ( <Icon className={cn('size-3 text-muted-foreground', className)} size={12} {...props} />);export type PillAvatarGroupProps = { children: ReactNode; className?: string;};export const PillAvatarGroup = ({ children, className, ...props}: PillAvatarGroupProps) => ( <div className={cn( '-space-x-1 flex items-center', '[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]', className )} {...props} > {children} </div>);
Button
A pill with a button for dismissal.
'use client';import { Pill, PillButton } from '@/components/ui/shadcn-io/pill';import { XIcon } from 'lucide-react';const Example = () => ( <Pill> #kibo-ui <PillButton size="icon" variant="ghost"> <XIcon size={12} /> </PillButton> </Pill>);export default Example;
import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from 'lucide-react';import type { ComponentProps, ReactNode } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { Badge } from '@/components/ui/badge';import { Button } from '@/components/ui/button';import { cn } from '@/lib/utils';export type PillProps = ComponentProps<typeof Badge> & { themed?: boolean;};export const Pill = ({ variant = 'secondary', themed = false, className, ...props}: PillProps) => ( <Badge className={cn('gap-2 rounded-full px-3 py-1.5 font-normal', className)} variant={variant} {...props} />);export type PillAvatarProps = ComponentProps<typeof AvatarImage> & { fallback?: string;};export const PillAvatar = ({ fallback, className, ...props}: PillAvatarProps) => ( <Avatar className={cn('-ml-1 h-4 w-4', className)}> <AvatarImage {...props} /> <AvatarFallback>{fallback}</AvatarFallback> </Avatar>);export type PillButtonProps = ComponentProps<typeof Button>;export const PillButton = ({ className, ...props }: PillButtonProps) => ( <Button className={cn( '-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5', className )} size="icon" variant="ghost" {...props} />);export type PillStatusProps = { children: ReactNode; className?: string;};export const PillStatus = ({ children, className, ...props}: PillStatusProps) => ( <div className={cn( 'flex items-center gap-2 border-r pr-2 font-medium', className )} {...props} > {children} </div>);export type PillIndicatorProps = { variant?: 'success' | 'error' | 'warning' | 'info'; pulse?: boolean;};export const PillIndicator = ({ variant = 'success', pulse = false,}: PillIndicatorProps) => ( <span className="relative flex size-2"> {pulse && ( <span className={cn( 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', variant === 'success' && 'bg-emerald-400', variant === 'error' && 'bg-rose-400', variant === 'warning' && 'bg-amber-400', variant === 'info' && 'bg-sky-400' )} /> )} <span className={cn( 'relative inline-flex size-2 rounded-full', variant === 'success' && 'bg-emerald-500', variant === 'error' && 'bg-rose-500', variant === 'warning' && 'bg-amber-500', variant === 'info' && 'bg-sky-500' )} /> </span>);export type PillDeltaProps = { className?: string; delta: number;};export const PillDelta = ({ className, delta }: PillDeltaProps) => { if (!delta) { return ( <MinusIcon className={cn('size-3 text-muted-foreground', className)} /> ); } if (delta > 0) { return ( <ChevronUpIcon className={cn('size-3 text-emerald-500', className)} /> ); } return <ChevronDownIcon className={cn('size-3 text-rose-500', className)} />;};export type PillIconProps = { icon: typeof ChevronUpIcon; className?: string;};export const PillIcon = ({ icon: Icon, className, ...props}: PillIconProps) => ( <Icon className={cn('size-3 text-muted-foreground', className)} size={12} {...props} />);export type PillAvatarGroupProps = { children: ReactNode; className?: string;};export const PillAvatarGroup = ({ children, className, ...props}: PillAvatarGroupProps) => ( <div className={cn( '-space-x-1 flex items-center', '[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]', className )} {...props} > {children} </div>);
Indicator
Pills with different indicator states.
'use client';import { Pill, PillIndicator } from '@/components/ui/shadcn-io/pill';const Example = () => ( <> <Pill> <PillIndicator pulse variant="success" /> Active </Pill> <Pill> <PillIndicator variant="error" /> Error </Pill> </>);export default Example;
import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from 'lucide-react';import type { ComponentProps, ReactNode } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { Badge } from '@/components/ui/badge';import { Button } from '@/components/ui/button';import { cn } from '@/lib/utils';export type PillProps = ComponentProps<typeof Badge> & { themed?: boolean;};export const Pill = ({ variant = 'secondary', themed = false, className, ...props}: PillProps) => ( <Badge className={cn('gap-2 rounded-full px-3 py-1.5 font-normal', className)} variant={variant} {...props} />);export type PillAvatarProps = ComponentProps<typeof AvatarImage> & { fallback?: string;};export const PillAvatar = ({ fallback, className, ...props}: PillAvatarProps) => ( <Avatar className={cn('-ml-1 h-4 w-4', className)}> <AvatarImage {...props} /> <AvatarFallback>{fallback}</AvatarFallback> </Avatar>);export type PillButtonProps = ComponentProps<typeof Button>;export const PillButton = ({ className, ...props }: PillButtonProps) => ( <Button className={cn( '-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5', className )} size="icon" variant="ghost" {...props} />);export type PillStatusProps = { children: ReactNode; className?: string;};export const PillStatus = ({ children, className, ...props}: PillStatusProps) => ( <div className={cn( 'flex items-center gap-2 border-r pr-2 font-medium', className )} {...props} > {children} </div>);export type PillIndicatorProps = { variant?: 'success' | 'error' | 'warning' | 'info'; pulse?: boolean;};export const PillIndicator = ({ variant = 'success', pulse = false,}: PillIndicatorProps) => ( <span className="relative flex size-2"> {pulse && ( <span className={cn( 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', variant === 'success' && 'bg-emerald-400', variant === 'error' && 'bg-rose-400', variant === 'warning' && 'bg-amber-400', variant === 'info' && 'bg-sky-400' )} /> )} <span className={cn( 'relative inline-flex size-2 rounded-full', variant === 'success' && 'bg-emerald-500', variant === 'error' && 'bg-rose-500', variant === 'warning' && 'bg-amber-500', variant === 'info' && 'bg-sky-500' )} /> </span>);export type PillDeltaProps = { className?: string; delta: number;};export const PillDelta = ({ className, delta }: PillDeltaProps) => { if (!delta) { return ( <MinusIcon className={cn('size-3 text-muted-foreground', className)} /> ); } if (delta > 0) { return ( <ChevronUpIcon className={cn('size-3 text-emerald-500', className)} /> ); } return <ChevronDownIcon className={cn('size-3 text-rose-500', className)} />;};export type PillIconProps = { icon: typeof ChevronUpIcon; className?: string;};export const PillIcon = ({ icon: Icon, className, ...props}: PillIconProps) => ( <Icon className={cn('size-3 text-muted-foreground', className)} size={12} {...props} />);export type PillAvatarGroupProps = { children: ReactNode; className?: string;};export const PillAvatarGroup = ({ children, className, ...props}: PillAvatarGroupProps) => ( <div className={cn( '-space-x-1 flex items-center', '[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]', className )} {...props} > {children} </div>);
Delta
Pills showing different delta states.
'use client';import { Pill, PillDelta } from '@/components/ui/shadcn-io/pill';const Example = () => ( <> <Pill> <PillDelta delta={10} /> Up 10% </Pill> <Pill> <PillDelta delta={-5} /> Down 5% </Pill> <Pill> <PillDelta delta={0} /> No change </Pill> </>);export default Example;
import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from 'lucide-react';import type { ComponentProps, ReactNode } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { Badge } from '@/components/ui/badge';import { Button } from '@/components/ui/button';import { cn } from '@/lib/utils';export type PillProps = ComponentProps<typeof Badge> & { themed?: boolean;};export const Pill = ({ variant = 'secondary', themed = false, className, ...props}: PillProps) => ( <Badge className={cn('gap-2 rounded-full px-3 py-1.5 font-normal', className)} variant={variant} {...props} />);export type PillAvatarProps = ComponentProps<typeof AvatarImage> & { fallback?: string;};export const PillAvatar = ({ fallback, className, ...props}: PillAvatarProps) => ( <Avatar className={cn('-ml-1 h-4 w-4', className)}> <AvatarImage {...props} /> <AvatarFallback>{fallback}</AvatarFallback> </Avatar>);export type PillButtonProps = ComponentProps<typeof Button>;export const PillButton = ({ className, ...props }: PillButtonProps) => ( <Button className={cn( '-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5', className )} size="icon" variant="ghost" {...props} />);export type PillStatusProps = { children: ReactNode; className?: string;};export const PillStatus = ({ children, className, ...props}: PillStatusProps) => ( <div className={cn( 'flex items-center gap-2 border-r pr-2 font-medium', className )} {...props} > {children} </div>);export type PillIndicatorProps = { variant?: 'success' | 'error' | 'warning' | 'info'; pulse?: boolean;};export const PillIndicator = ({ variant = 'success', pulse = false,}: PillIndicatorProps) => ( <span className="relative flex size-2"> {pulse && ( <span className={cn( 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', variant === 'success' && 'bg-emerald-400', variant === 'error' && 'bg-rose-400', variant === 'warning' && 'bg-amber-400', variant === 'info' && 'bg-sky-400' )} /> )} <span className={cn( 'relative inline-flex size-2 rounded-full', variant === 'success' && 'bg-emerald-500', variant === 'error' && 'bg-rose-500', variant === 'warning' && 'bg-amber-500', variant === 'info' && 'bg-sky-500' )} /> </span>);export type PillDeltaProps = { className?: string; delta: number;};export const PillDelta = ({ className, delta }: PillDeltaProps) => { if (!delta) { return ( <MinusIcon className={cn('size-3 text-muted-foreground', className)} /> ); } if (delta > 0) { return ( <ChevronUpIcon className={cn('size-3 text-emerald-500', className)} /> ); } return <ChevronDownIcon className={cn('size-3 text-rose-500', className)} />;};export type PillIconProps = { icon: typeof ChevronUpIcon; className?: string;};export const PillIcon = ({ icon: Icon, className, ...props}: PillIconProps) => ( <Icon className={cn('size-3 text-muted-foreground', className)} size={12} {...props} />);export type PillAvatarGroupProps = { children: ReactNode; className?: string;};export const PillAvatarGroup = ({ children, className, ...props}: PillAvatarGroupProps) => ( <div className={cn( '-space-x-1 flex items-center', '[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]', className )} {...props} > {children} </div>);
Icon
A pill with an icon and text.
'use client';import { Pill, PillIcon } from '@/components/ui/shadcn-io/pill';import { UsersIcon } from 'lucide-react';const Example = () => ( <Pill> <PillIcon icon={UsersIcon} /> 17 users </Pill>);export default Example;
import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from 'lucide-react';import type { ComponentProps, ReactNode } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { Badge } from '@/components/ui/badge';import { Button } from '@/components/ui/button';import { cn } from '@/lib/utils';export type PillProps = ComponentProps<typeof Badge> & { themed?: boolean;};export const Pill = ({ variant = 'secondary', themed = false, className, ...props}: PillProps) => ( <Badge className={cn('gap-2 rounded-full px-3 py-1.5 font-normal', className)} variant={variant} {...props} />);export type PillAvatarProps = ComponentProps<typeof AvatarImage> & { fallback?: string;};export const PillAvatar = ({ fallback, className, ...props}: PillAvatarProps) => ( <Avatar className={cn('-ml-1 h-4 w-4', className)}> <AvatarImage {...props} /> <AvatarFallback>{fallback}</AvatarFallback> </Avatar>);export type PillButtonProps = ComponentProps<typeof Button>;export const PillButton = ({ className, ...props }: PillButtonProps) => ( <Button className={cn( '-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5', className )} size="icon" variant="ghost" {...props} />);export type PillStatusProps = { children: ReactNode; className?: string;};export const PillStatus = ({ children, className, ...props}: PillStatusProps) => ( <div className={cn( 'flex items-center gap-2 border-r pr-2 font-medium', className )} {...props} > {children} </div>);export type PillIndicatorProps = { variant?: 'success' | 'error' | 'warning' | 'info'; pulse?: boolean;};export const PillIndicator = ({ variant = 'success', pulse = false,}: PillIndicatorProps) => ( <span className="relative flex size-2"> {pulse && ( <span className={cn( 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', variant === 'success' && 'bg-emerald-400', variant === 'error' && 'bg-rose-400', variant === 'warning' && 'bg-amber-400', variant === 'info' && 'bg-sky-400' )} /> )} <span className={cn( 'relative inline-flex size-2 rounded-full', variant === 'success' && 'bg-emerald-500', variant === 'error' && 'bg-rose-500', variant === 'warning' && 'bg-amber-500', variant === 'info' && 'bg-sky-500' )} /> </span>);export type PillDeltaProps = { className?: string; delta: number;};export const PillDelta = ({ className, delta }: PillDeltaProps) => { if (!delta) { return ( <MinusIcon className={cn('size-3 text-muted-foreground', className)} /> ); } if (delta > 0) { return ( <ChevronUpIcon className={cn('size-3 text-emerald-500', className)} /> ); } return <ChevronDownIcon className={cn('size-3 text-rose-500', className)} />;};export type PillIconProps = { icon: typeof ChevronUpIcon; className?: string;};export const PillIcon = ({ icon: Icon, className, ...props}: PillIconProps) => ( <Icon className={cn('size-3 text-muted-foreground', className)} size={12} {...props} />);export type PillAvatarGroupProps = { children: ReactNode; className?: string;};export const PillAvatarGroup = ({ children, className, ...props}: PillAvatarGroupProps) => ( <div className={cn( '-space-x-1 flex items-center', '[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]', className )} {...props} > {children} </div>);
Avatar Group
A pill with multiple avatars grouped together.
'use client';import { Pill, PillAvatar, PillAvatarGroup } from '@/components/ui/shadcn-io/pill';const Example = () => ( <Pill> <PillAvatarGroup> <PillAvatar fallback="HB" src="https://pbs.twimg.com/profile_images/1748718473595789312/PbqJh9hk_400x400.jpg" /> <PillAvatar fallback="SC" src="https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg" /> <PillAvatar fallback="LR" src="https://pbs.twimg.com/profile_images/1862717563311968256/xfgt1L9l_400x400.jpg" /> </PillAvatarGroup> Loved by millions </Pill>);export default Example;
import { ChevronDownIcon, ChevronUpIcon, MinusIcon } from 'lucide-react';import type { ComponentProps, ReactNode } from 'react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';import { Badge } from '@/components/ui/badge';import { Button } from '@/components/ui/button';import { cn } from '@/lib/utils';export type PillProps = ComponentProps<typeof Badge> & { themed?: boolean;};export const Pill = ({ variant = 'secondary', themed = false, className, ...props}: PillProps) => ( <Badge className={cn('gap-2 rounded-full px-3 py-1.5 font-normal', className)} variant={variant} {...props} />);export type PillAvatarProps = ComponentProps<typeof AvatarImage> & { fallback?: string;};export const PillAvatar = ({ fallback, className, ...props}: PillAvatarProps) => ( <Avatar className={cn('-ml-1 h-4 w-4', className)}> <AvatarImage {...props} /> <AvatarFallback>{fallback}</AvatarFallback> </Avatar>);export type PillButtonProps = ComponentProps<typeof Button>;export const PillButton = ({ className, ...props }: PillButtonProps) => ( <Button className={cn( '-my-2 -mr-2 size-6 rounded-full p-0.5 hover:bg-foreground/5', className )} size="icon" variant="ghost" {...props} />);export type PillStatusProps = { children: ReactNode; className?: string;};export const PillStatus = ({ children, className, ...props}: PillStatusProps) => ( <div className={cn( 'flex items-center gap-2 border-r pr-2 font-medium', className )} {...props} > {children} </div>);export type PillIndicatorProps = { variant?: 'success' | 'error' | 'warning' | 'info'; pulse?: boolean;};export const PillIndicator = ({ variant = 'success', pulse = false,}: PillIndicatorProps) => ( <span className="relative flex size-2"> {pulse && ( <span className={cn( 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', variant === 'success' && 'bg-emerald-400', variant === 'error' && 'bg-rose-400', variant === 'warning' && 'bg-amber-400', variant === 'info' && 'bg-sky-400' )} /> )} <span className={cn( 'relative inline-flex size-2 rounded-full', variant === 'success' && 'bg-emerald-500', variant === 'error' && 'bg-rose-500', variant === 'warning' && 'bg-amber-500', variant === 'info' && 'bg-sky-500' )} /> </span>);export type PillDeltaProps = { className?: string; delta: number;};export const PillDelta = ({ className, delta }: PillDeltaProps) => { if (!delta) { return ( <MinusIcon className={cn('size-3 text-muted-foreground', className)} /> ); } if (delta > 0) { return ( <ChevronUpIcon className={cn('size-3 text-emerald-500', className)} /> ); } return <ChevronDownIcon className={cn('size-3 text-rose-500', className)} />;};export type PillIconProps = { icon: typeof ChevronUpIcon; className?: string;};export const PillIcon = ({ icon: Icon, className, ...props}: PillIconProps) => ( <Icon className={cn('size-3 text-muted-foreground', className)} size={12} {...props} />);export type PillAvatarGroupProps = { children: ReactNode; className?: string;};export const PillAvatarGroup = ({ children, className, ...props}: PillAvatarGroupProps) => ( <div className={cn( '-space-x-1 flex items-center', '[&>*:not(:first-of-type)]:[mask-image:radial-gradient(circle_9px_at_-4px_50%,transparent_99%,white_100%)]', className )} {...props} > {children} </div>);
Use Cases
- Status badges - User status, system health, and notification indicators
- Team displays - Member lists with avatars and role indicators
- Data metrics - KPI displays with delta changes and trend indicators
- Tag systems - Content categorization with dismissible pill interface
Implementation
Built with Badge component and flexible content slots. Supports avatar fallbacks and status variants. Uses Lucide icons for consistency. Button integration with event handlers for dismissal.
Avatar Group
Versatile avatar group component with motion, CSS, and stack variants. Features tooltips, hover effects, and flexible positioning for React applications with Next.js integration and TypeScript support.
Tags
Interactive tag selection component for React and Next.js applications. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design featuring search filtering, keyboard navigation, and controlled state management.