Hireable LogoHireable
Frontend

UI Components

Reusable UI components built with Radix UI and Tailwind CSS

Overview

The component library is built on Radix UI primitives with Tailwind CSS styling, using class-variance-authority (CVA) for variant management.

Component Location

src/components/
├── ui/                       # Primitive UI components
│   ├── accordion.tsx
│   ├── avatar.tsx
│   ├── badge.tsx
│   ├── button.tsx
│   ├── card.tsx
│   ├── chart.tsx
│   ├── glass-card.tsx
│   ├── input.tsx
│   ├── password-input.tsx
│   ├── progress.tsx
│   ├── select.tsx
│   └── index.ts
├── AuthBackground/
├── ErrorBoundary/
├── ParticleBackground/
└── Navbar.tsx

Button Component

The Button component supports multiple variants and sizes:

import { Button } from "@/components/ui/button";
 
// Variants
<Button variant="default">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
 
// Sizes
<Button size="default">Default</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon">🔍</Button>
 
// As child (polymorphic)
<Button asChild>
  <a href="/login">Login</a>
</Button>

Button Implementation

import { cn } from "@/lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
 
const buttonVariants = cva(
  "inline-flex cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
        outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
        secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 rounded-md px-3 text-xs",
        lg: "h-10 rounded-md px-8",
        icon: "h-9 w-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);
 
interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}
 
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button";
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    );
  }
);

Input Component

import { Input } from "@/components/ui/input";
 
<Input type="text" placeholder="Enter your name" />
<Input type="email" placeholder="email@example.com" />
<Input type="password" placeholder="Password" />
<Input disabled placeholder="Disabled" />

Password Input

A specialized input with show/hide toggle:

import { PasswordInput } from "@/components/ui/password-input";
 
<PasswordInput placeholder="Enter password" />

Select Component

Built on Radix UI Select:

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
 
<Select>
  <SelectTrigger>
    <SelectValue placeholder="Select a role" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="employer">Employer</SelectItem>
    <SelectItem value="talent">Talent</SelectItem>
  </SelectContent>
</Select>

Card Component

import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
 
<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
    <CardDescription>Card description text</CardDescription>
  </CardHeader>
  <CardContent>
    <p>Card content goes here</p>
  </CardContent>
  <CardFooter>
    <Button>Action</Button>
  </CardFooter>
</Card>

Glass Card

A glassmorphism-styled card variant:

import { GlassCard } from "@/components/ui/glass-card";
 
<GlassCard>
  <h2>Glassmorphism Effect</h2>
  <p>Content with blur background</p>
</GlassCard>

Avatar Component

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
 
<Avatar>
  <AvatarImage src="/avatar.jpg" alt="User" />
  <AvatarFallback>JD</AvatarFallback>
</Avatar>

Badge Component

import { Badge } from "@/components/ui/badge";
 
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>

Progress Component

import { Progress } from "@/components/ui/progress";
 
<Progress value={33} />
<Progress value={66} />
<Progress value={100} />

Accordion Component

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion";
 
<Accordion type="single" collapsible>
  <AccordionItem value="item-1">
    <AccordionTrigger>Is it accessible?</AccordionTrigger>
    <AccordionContent>
      Yes. It adheres to the WAI-ARIA design pattern.
    </AccordionContent>
  </AccordionItem>
</Accordion>

Utility Function

The cn utility merges Tailwind classes:

// lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Usage:

<div className={cn("base-class", condition && "conditional-class", className)}>

Importing Components

All UI components are exported from the barrel file:

import {
  Button,
  Input,
  Card,
  CardHeader,
  CardContent,
  Select,
  Avatar,
  Badge,
  Progress,
} from "@/components/ui";

On this page