shadimage
bjork-ui
componentanimated-status-badgesource: ok · hit

Animated status badge

Animated status badge is a production-ready badges component from BJORK UI, packaged for shadcn registries.

npx shadcn add "https://ui.isaiahbjork.com/animated-status-badge.json"
free

Where it goes

stats

Interaction

animated-counter

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
animationframer-motion
iconslucide-react

Files3 · source cached 10/9/2026, 10:17:17 PM

components/bjork-ui/badges/animated-status-badge.tsx
"use client"

import { useState, useEffect } from "react"
import { AnimatePresence, useReducedMotion } from "framer-motion"
import { Loader2, Check } from "lucide-react"
import { BjorkBadge } from "@/components/bjork-ui/primitives/badge"
import { cn } from "@/lib/utils"

interface AnimatedStatusBadgeProps {
  trigger: boolean
  onAnimationComplete?: () => void
  className?: string
}

export function AnimatedStatusBadge({ 
  trigger, 
  onAnimationComplete,
  className = ""
}: AnimatedStatusBadgeProps) {
  const [isAnimating, setIsAnimating] = useState(false)
  const [isCompleted, setIsCompleted] = useState(false)
  const reduce = useReducedMotion()

  const startAnimation = () => {
    setIsAnimating(true)
    setIsCompleted(false)
    setTimeout(() => {
      setIsAnimating(false)
      setTimeout(() => {
        setIsCompleted(true)
        // Make completed badge disappear after 3 seconds
        setTimeout(() => {
          setIsCompleted(false)
          if (onAnimationComplete) {
            onAnimationComplete()
          }
        }, 3000)
      }, 300) // Delay the appearance of "Completed" badge
    }, 3000) // Animation duration
  }

  useEffect(() => {
    if (!isAnimating && !isCompleted) {
      setIsCompleted(false)
    }
  }, [isAnimating, isCompleted])

  useEffect(() => {
    if (trigger) {
      startAnimation()
    }
  }, [trigger])

  return (
    <>
      <AnimatePresence>
        {isAnimating && (
          <BjorkBadge
            variant="outline"
            className={cn(
              "absolute right-0 top-0 z-0 border-[#ec5c13]/28 bg-[#ec5c13]/12 text-xs font-medium text-[#bd4514] shadow-[0_12px_18px_-14px_rgba(236,92,19,0.5)] dark:text-[#d86a2c]",
              className
            )}
            initial={reduce ? false : { y: 40, opacity: 1 }}
            animate={{ y: -32, opacity: 1 }}
            exit={{
              y: [-37, 40], // First go up 5px, then slide down
              opacity: [1, 1, 0], // Maintain opacity until the end of the animation
              scale: [1, 0.8, 0.8], // Scale down as it starts to disappear
            }}
            transition={{
              duration: reduce ? 0 : 0.5,
              times: [0, 0.2, 1], // Timing for the exit animation stages
              ease: "easeInOut",
            }}
          >
            <Loader2 className={cn("h-3 w-3 mr-1", !reduce && "animate-spin")} />
            <span>Running</span>
          </BjorkBadge>
        )}
      </AnimatePresence>
      <AnimatePresence>
        {isCompleted && (
          <BjorkBadge
            variant="accent"
            className={cn(
              "absolute right-0 top-0 z-0 text-xs font-medium shadow-[0_12px_18px_-14px_rgba(236,92,19,0.5)]",
              className
            )}
            initial={reduce ? false : { y: 40, opacity: 1 }}
            animate={{ y: -32, opacity: 1 }}
            exit={{
              y: [-37, 40], // First go up 5px, then slide down
              opacity: [1, 1, 0], // Maintain opacity until the end of the animation
              scale: [1, 0.8, 0.8], // Scale down as it starts to disappear
            }}
            transition={{
              duration: reduce ? 0 : 0.5,
              times: [0, 0.2, 1], // Timing for the exit animation stages
              ease: "easeInOut",
            }}
          >
            <Check className="h-3 w-3 mr-1" />
            <span>Completed</span>
          </BjorkBadge>
        )}
      </AnimatePresence>
    </>
  )
}
components/bjork-ui/primitives/badge.tsx
"use client";

import * as React from "react";
import { motion, useReducedMotion, type HTMLMotionProps } from "framer-motion";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const bjorkBadgeVariants = cva(
  "inline-flex w-fit shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-[9px] border font-medium outline-none transition-[background-color,border-color,color,box-shadow,opacity,transform] duration-200 focus-visible:ring-2 focus-visible:ring-[color:var(--bjork-accent-muted)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bjork-ring-offset)] [&_svg]:size-3",
  {
    variants: {
      variant: {
        default:
          "border-transparent bg-[var(--bjork-text)] text-[color:var(--bjork-inverted-text)] shadow-[var(--bjork-shadow-soft)]",
        secondary:
          "border-transparent bg-[var(--bjork-surface-active)] text-[color:var(--bjork-text)] shadow-[var(--bjork-shadow-soft)]",
        outline:
          "border-[color:var(--bjork-border)] bg-[var(--bjork-field-inset)] text-[color:var(--bjork-text-medium)]",
        muted:
          "border-transparent bg-[var(--bjork-field-muted)] text-[color:var(--bjork-text-soft)]",
        accent:
          "border-[color:var(--bjork-accent-muted)] bg-[var(--bjork-accent-badge)] text-[color:var(--bjork-accent-badge-foreground)] shadow-[var(--bjork-shadow-soft)]",
      },
      size: {
        sm: "px-1.5 py-0.5 text-[11px] has-[>svg:first-child]:pl-1 has-[>svg:last-child]:pr-1",
        md: "px-2 py-1 text-xs has-[>svg:first-child]:pl-1.5 has-[>svg:last-child]:pr-1.5",
        lg: "px-2.5 py-1.5 text-sm has-[>svg:first-child]:pl-2 has-[>svg:last-child]:pr-2",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  },
);

export interface BjorkBadgeProps
  extends Omit<HTMLMotionProps<"span">, "ref">,
    VariantProps<typeof bjorkBadgeVariants> {}

export function BjorkBadge({
  className,
  variant,
  size,
  ...props
}: BjorkBadgeProps) {
  const shouldReduceMotion = useReducedMotion();

  return (
    <motion.span
      data-slot="bjork-badge"
      className={cn(bjorkBadgeVariants({ variant, size, className }))}
      whileHover={shouldReduceMotion ? undefined : { y: -1, opacity: 0.92 }}
      transition={{ type: "spring", stiffness: 420, damping: 30 }}
      {...props}
    />
  );
}

export { bjorkBadgeVariants };
lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

More from bjork-ui

Browse all components in this library →