Bluewoo HRMS
Design SystemComponents

Feedback

Toast, skeleton, and alert patterns

Feedback Components

Components for user feedback and loading states.

Toast (Sonner)

Use Sonner for toast notifications:

import { toast } from "sonner";

// Success
toast.success("Employee created successfully");

// Error
toast.error("Failed to create employee");

// Info
toast.info("Processing your request...");

// Warning
toast.warning("This action cannot be undone");

// With description
toast.success("Employee created", {
  description: "John Doe has been added to Engineering",
});

// With action
toast.error("Failed to save", {
  action: {
    label: "Retry",
    onClick: () => handleRetry(),
  },
});

Toast Setup

Add Toaster to your root layout:

// app/layout.tsx
import { Toaster } from "sonner";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Toaster
          position="bottom-right"
          toastOptions={{
            classNames: {
              toast: "bg-card border-border",
              title: "text-foreground",
              description: "text-muted-foreground",
              actionButton: "bg-primary text-primary-foreground",
            },
          }}
        />
      </body>
    </html>
  );
}

Skeleton

Loading placeholders that match content shape.

// Text skeleton
<Skeleton className="h-4 w-32" />

// Avatar skeleton
<Skeleton className="h-10 w-10 rounded-full" />

// Card skeleton
<Skeleton className="h-32 w-full rounded-2xl" />

Skeleton Component

import { cn } from "@/lib/utils";

function Skeleton({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      className={cn("animate-pulse rounded-md bg-muted", className)}
      {...props}
    />
  );
}

Table Skeleton

<ContentCard>
  <div className="space-y-4">
    {[1, 2, 3, 4, 5].map((i) => (
      <div key={i} className="flex items-center gap-4">
        <Skeleton className="h-10 w-10 rounded-full" />
        <div className="space-y-2 flex-1">
          <Skeleton className="h-4 w-1/3" />
          <Skeleton className="h-3 w-1/4" />
        </div>
        <Skeleton className="h-6 w-16 rounded-full" />
      </div>
    ))}
  </div>
</ContentCard>

Stats Card Skeleton

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
  {[1, 2, 3, 4].map((i) => (
    <Skeleton key={i} className="h-32 rounded-2xl" />
  ))}
</div>

Alert

Important messages that don't require immediate action.

<Alert>
  <Info className="h-4 w-4" />
  <AlertTitle>Information</AlertTitle>
  <AlertDescription>
    Your profile is incomplete. Add your phone number for better security.
  </AlertDescription>
</Alert>

Alert Variants

// Default
<Alert>
  <Info className="h-4 w-4" />
  <AlertTitle>Note</AlertTitle>
  <AlertDescription>...</AlertDescription>
</Alert>

// Destructive
<Alert variant="destructive">
  <AlertCircle className="h-4 w-4" />
  <AlertTitle>Error</AlertTitle>
  <AlertDescription>...</AlertDescription>
</Alert>

// Success (custom)
<Alert className="border-success/50 bg-success/10">
  <CheckCircle className="h-4 w-4 text-success" />
  <AlertTitle className="text-success">Success</AlertTitle>
  <AlertDescription>...</AlertDescription>
</Alert>

// Warning (custom)
<Alert className="border-warning/50 bg-warning/10">
  <AlertTriangle className="h-4 w-4 text-warning" />
  <AlertTitle className="text-warning">Warning</AlertTitle>
  <AlertDescription>...</AlertDescription>
</Alert>

Dialog / Modal

<Dialog>
  <DialogTrigger asChild>
    <Button>Open Modal</Button>
  </DialogTrigger>
  <DialogContent className="sm:max-w-[425px]">
    <DialogHeader>
      <DialogTitle>Edit Employee</DialogTitle>
      <DialogDescription>
        Make changes to the employee profile here.
      </DialogDescription>
    </DialogHeader>
    <div className="space-y-4 py-4">
      {/* Form fields */}
    </div>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button>Save Changes</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Dialog Animation

Use Framer Motion for entrance:

<DialogContent
  className="sm:max-w-[425px]"
  // Radix handles animation, but for custom:
>

Framer Motion variant (if custom):

const modalVariants = {
  initial: { opacity: 0, y: 20, scale: 0.95 },
  animate: { opacity: 1, y: 0, scale: 1 },
  exit: { opacity: 0, y: 20, scale: 0.95 },
};

const transition = { type: "spring", damping: 25, stiffness: 300 };

Confirmation Dialog

<AlertDialog>
  <AlertDialogTrigger asChild>
    <Button variant="destructive">Delete Employee</Button>
  </AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
      <AlertDialogDescription>
        This action cannot be undone. This will permanently delete the
        employee record and remove their data from our servers.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
        Delete
      </AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

Loading Button

<Button disabled>
  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
  Please wait
</Button>

Progress

<Progress value={66} className="h-2" />

Progress with Label

<div className="space-y-2">
  <div className="flex justify-between text-sm">
    <span>Uploading...</span>
    <span>66%</span>
  </div>
  <Progress value={66} className="h-2" />
</div>

Tooltip

<TooltipProvider>
  <Tooltip>
    <TooltipTrigger asChild>
      <Button variant="ghost" size="icon">
        <HelpCircle className="h-4 w-4" />
      </Button>
    </TooltipTrigger>
    <TooltipContent>
      <p>This is helpful information</p>
    </TooltipContent>
  </Tooltip>
</TooltipProvider>

Badge with Tooltip

<TooltipProvider>
  <Tooltip>
    <TooltipTrigger>
      <Badge variant="warning">Pending</Badge>
    </TooltipTrigger>
    <TooltipContent>
      <p>Awaiting manager approval</p>
    </TooltipContent>
  </Tooltip>
</TooltipProvider>