shadimage
tetra-ui
uibadgesource: ok · hit

Badge

Displays a badge.

npx shadcn add "https://tetra-ui.com/r/badge.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Files1 · source cached 10/9/2026, 3:26:26 AM

ui/badge.tsx
import { cva, type VariantProps } from "class-variance-authority";
import {
  Children,
  cloneElement,
  createContext,
  useContext,
  useMemo,
} from "react";
import { Text, View } from "react-native";
import { cn } from "@/lib/utils";

// Types
type InternalBadgeContextType = VariantProps<typeof badgeVariants>;

type BadgeChildProps = {
  children: React.ReactNode;
  className?: string;
};

export type BadgeProps = React.ComponentProps<typeof View> &
  InternalBadgeContextType & {
    children: React.ReactNode;
  };

// Context
const BadgeContext = createContext<InternalBadgeContextType | null>(null);

const useBadgeContext = () => {
  const context = useContext(BadgeContext);
  if (!context) {
    throw new Error("useBadgeContext must be used within a Badge component");
  }
  return context;
};

// Components
export const Badge = ({
  children,
  className,
  variant,
  ...props
}: BadgeProps) => {
  const ctx = useMemo(() => {
    return {
      variant,
    };
  }, [variant]);

  return (
    <BadgeContext.Provider value={ctx}>
      <View className={cn(badgeVariants({ className, variant }))} {...props}>
        {Children.map(children, (child) => {
          if (typeof child === "string") {
            return <BadgeText>{child}</BadgeText>;
          }

          return child;
        })}
      </View>
    </BadgeContext.Provider>
  );
};

export const BadgeText = (props: BadgeChildProps) => {
  const ctx = useBadgeContext();

  return (
    <Text {...props} className={cn(badgeTextVariants(ctx), props.className)} />
  );
};

export const BadgeIcon = ({ children, ...props }: BadgeChildProps) => {
  const ctx = useBadgeContext();

  const child = Children.only(children);

  if (!child) {
    if (__DEV__) {
      throw new Error("BadgeIcon expects a single React element as children");
    }
    return null;
  }

  return cloneElement(child as React.ReactElement<BadgeChildProps>, {
    ...props,
    className: cn(badgeIconVariants(ctx), props.className),
  });
};

// Styles
const badgeVariants = cva(
  "flex w-fit shrink-0 flex-row items-center justify-center gap-1.5 self-start overflow-hidden whitespace-nowrap rounded-md border border-border px-2 py-1 font-medium text-xs transition-[color,box-shadow] aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
  {
    defaultVariants: {
      variant: "default",
    },
    variants: {
      variant: {
        default: "border-transparent bg-primary",
        destructive: "border-transparent bg-destructive",
        outline: "",
        secondary: "border-transparent bg-secondary",
      },
    },
  }
);

const badgeTextVariants = cva(
  "whitespace-nowrap font-semibold text-sm leading-none",
  {
    defaultVariants: {
      variant: "default",
    },
    variants: {
      variant: {
        default: "text-primary-foreground",
        destructive: "text-white",
        outline: "text-foreground",
        secondary: "text-secondary-foreground",
      },
    },
  }
);

const badgeIconVariants = cva("size-4", {
  defaultVariants: {
    variant: "default",
  },
  variants: {
    variant: {
      default: "text-primary-foreground",
      destructive: "text-white",
      outline: "text-foreground",
      secondary: "text-secondary-foreground",
    },
  },
});

More from tetra-ui

Browse all components in this library →