Skip to main content
The portfolio uses shadcn/ui components built on top of Radix UI primitives with custom Tailwind CSS styling.

Installation Pattern

All UI components follow the shadcn/ui pattern:
npx shadcn-ui@latest add button
npx shadcn-ui@latest add accordion
npx shadcn-ui@latest add tooltip
Components are located in src/components/ui/ and can be customized directly.

Button Component

The Button component uses class-variance-authority for variant management with custom hero styles.

API

src/components/ui/button.tsx
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg...",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90...",
        destructive: "bg-destructive text-destructive-foreground...",
        outline: "border border-border bg-transparent...",
        secondary: "bg-secondary text-secondary-foreground...",
        ghost: "text-foreground hover:bg-secondary...",
        link: "text-primary underline-offset-4 hover:underline",
        hero: "bg-primary text-primary-foreground font-semibold hover:shadow-[0_0_40px_hsl(24_100%_58%/0.5)] hover:-translate-y-0.5",
        heroOutline: "border-2 border-muted-foreground/30 bg-transparent hover:border-primary/50 hover:bg-secondary/50",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-12 rounded-xl px-8 text-base",
        xl: "h-14 rounded-xl px-10 text-lg",
        icon: "h-10 w-10",
      },
    },
  }
);

export 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} />;
  }
);

Custom Variants

The portfolio adds two custom hero variants:
variant="hero"
// Styling:
// - Primary background with semibold font
// - Glowing shadow on hover: 0_0_40px_hsl(24_100%_58%/0.5)
// - Lift effect: hover:-translate-y-0.5
Usage:
<Button variant="hero" size="lg" asChild>
  <a href="#contact">
    <Send className="w-4 h-4" />
    Disponível para Freelas
  </a>
</Button>

Sizes

<Button variant="hero" size="sm">
  Small Button
</Button>
// h-9 rounded-md px-3

asChild Prop

The asChild prop uses Radix UI’s Slot component to render the button as a different element:
// Renders as <a> instead of <button>
<Button variant="hero" size="lg" asChild>
  <a href="#contact">
    Contact Me
  </a>
</Button>

Accordion Component

Collapsible accordion built with Radix UI primitives.

API

src/components/ui/accordion.tsx
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";

const Accordion = AccordionPrimitive.Root;

const AccordionItem = React.forwardRef<...>(({ className, ...props }, ref) => (
  <AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
));

const AccordionTrigger = React.forwardRef<...>(({ className, children, ...props }, ref) => (
  <AccordionPrimitive.Header className="flex">
    <AccordionPrimitive.Trigger
      className={cn(
        "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
        className
      )}
    >
      {children}
      <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
    </AccordionPrimitive.Trigger>
  </AccordionPrimitive.Header>
));

const AccordionContent = React.forwardRef<...>(({ className, children, ...props }, ref) => (
  <AccordionPrimitive.Content
    className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
  >
    <div className={cn("pb-4 pt-0", className)}>{children}</div>
  </AccordionPrimitive.Content>
));

Usage

import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
} from "@/components/ui/accordion";

<Accordion type="single" collapsible>
  <AccordionItem value="item-1">
    <AccordionTrigger>What technologies do you use?</AccordionTrigger>
    <AccordionContent>
      React, TypeScript, Node.js, Python, and more...
    </AccordionContent>
  </AccordionItem>
  <AccordionItem value="item-2">
    <AccordionTrigger>What's your experience?</AccordionTrigger>
    <AccordionContent>
      Over 1.5 years in data analysis and development...
    </AccordionContent>
  </AccordionItem>
</Accordion>
Features:
  • Auto-rotating chevron icon on open/close
  • Smooth accordion-up/down animations
  • Single or multiple item expansion
  • Keyboard navigation support

Tooltip Component

Tooltip component for displaying contextual information.

API

src/components/ui/tooltip.tsx
import * as TooltipPrimitive from "@radix-ui/react-tooltip";

const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<...>(
  ({ className, sideOffset = 4, ...props }, ref) => (
    <TooltipPrimitive.Content
      ref={ref}
      sideOffset={sideOffset}
      className={cn(
        "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95...",
        className
      )}
      {...props}
    />
  )
);

Usage

import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip";

<TooltipProvider>
  <Tooltip>
    <TooltipTrigger asChild>
      <button>Hover me</button>
    </TooltipTrigger>
    <TooltipContent>
      <p>Helpful tooltip information</p>
    </TooltipContent>
  </Tooltip>
</TooltipProvider>
Note: The TooltipProvider must wrap the entire app (see src/App.tsx).

Toast Components

Dual toast notification system using both Radix UI Toast and Sonner.

Radix UI Toast

import * as ToastPrimitives from "@radix-ui/react-toast";
import { X } from "lucide-react";

const Toast = React.forwardRef<...>(({ className, variant, ...props }, ref) => {
  return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />;
});

const ToastTitle = React.forwardRef<...>(({ className, ...props }, ref) => (
  <ToastPrimitives.Title ref={ref} className={cn("text-sm font-semibold", className)} {...props} />
));

const ToastDescription = React.forwardRef<...>(({ className, ...props }, ref) => (
  <ToastPrimitives.Description ref={ref} className={cn("text-sm opacity-90", className)} {...props} />
));

Sonner Toast

Simplified toast library with better DX:
src/components/ui/sonner.tsx
import { Toaster as Sonner, toast } from "sonner";

const Toaster = ({ ...props }: ToasterProps) => {
  return (
    <Sonner
      theme="dark"
      className="toaster group"
      toastOptions={{
        classNames: {
          toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground...",
          description: "group-[.toast]:text-muted-foreground",
          actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
          cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
        },
      }}
      {...props}
    />
  );
};

export { Toaster, toast };
Usage:
import { toast } from "@/components/ui/sonner";

// Simple notification
toast("Event has been created.");

// With description
toast("Event created", {
  description: "Your event has been scheduled for tomorrow.",
});

// Success variant
toast.success("Message sent successfully!");

// Error variant
toast.error("Failed to send message. Please try again.");

Custom Hook: use-toast

src/components/ui/use-toast.ts
// Re-export from shadcn/ui
export { useToast, toast } from "@/hooks/use-toast";

Styling Utilities

All components use the cn() utility for class name merging:
src/lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
This enables conditional class merging and Tailwind class conflict resolution:
<Button 
  className={cn(
    "custom-class",
    isActive && "bg-primary",
    isDisabled && "opacity-50 pointer-events-none"
  )}
/>

Icon System

All icons use Lucide React:
import { 
  Github, 
  Linkedin, 
  Mail, 
  Instagram, 
  Download,
  Sparkles,
  Play,
  Send,
  Eye,
  Menu,
  X 
} from "lucide-react";

<Send className="w-4 h-4" />
<Github className="w-5 h-5" />
Sizing convention:
  • w-4 h-4 - Small icons (16px) for buttons
  • w-5 h-5 - Medium icons (20px) for links
  • w-6 h-6 - Large icons (24px) for mobile menu

Next Steps

Component Architecture

Learn about the overall component structure

Animations

Explore ScrollReveal and animation patterns

Build docs developers (and LLMs) love