Skip to main content
The portfolio uses a design system approach with reusable UI components built on Radix UI primitives, styled with Tailwind CSS and class-variance-authority.

Button Component

The Button component is a flexible, accessible button with multiple variants and sizes.

Location

src/components/ui/button.tsx

Features

Type-Safe Variants

Uses CVA for compile-time variant validation

Radix Slot Support

Can render as any element using asChild prop

Accessible

Built-in focus states and ARIA support

Dark Mode Ready

Includes dark mode styling variants

Implementation

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 text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
        destructive:
          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
        outline:
          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
        ghost:
          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2 has-[>svg]:px-3",
        sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
        lg: "h-10 px-6 has-[>svg]:px-4",
        icon: "size-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

function Button({
  className,
  variant,
  size,
  asChild = false,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean
  }) {
  const Comp = asChild ? Slot : "button"

  return (
    <Comp
      data-slot="button"
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
}

export { Button, buttonVariants }

Props API

Button Props

variant
string
default:"default"
The visual style variant of the button.Options: default, destructive, outline, secondary, ghost, link
size
string
default:"default"
The size of the button.Options: default, sm, lg, icon
asChild
boolean
default:false
When true, the button will merge its props and behavior onto its immediate child using Radix Slot.
className
string
Additional CSS classes to apply to the button.
...props
React.ComponentProps<'button'>
All standard HTML button attributes are supported.

Variants

The primary button style with solid background.
<Button variant="default">Click me</Button>
Style: Primary color background with white text and hover darkening effect.

Sizes

<Button size="sm">Small Button</Button>
// Height: 32px (h-8), Padding: 12px (px-3)

Usage Examples

Basic Button

import { Button } from "@/components/ui/button";

function Example() {
  return (
    <Button onClick={() => console.log("clicked")}>
      Click me
    </Button>
  );
}

Button with Icon

The Button automatically styles SVG children:
import { Button } from "@/components/ui/button";
import { Github } from "pikaicons";

function Example() {
  return (
    <Button variant="outline">
      <Github />
      View on GitHub
    </Button>
  );
}
Use asChild to render the button as a different element:
import { Button } from "@/components/ui/button";
import { Link } from "react-router-dom";

function Example() {
  return (
    <Button asChild variant="ghost">
      <Link to="/about">About</Link>
    </Button>
  );
}
When asChild={true}, the Button component merges its styling and props onto the child element. This is useful for maintaining button styling while rendering as links, router components, or custom elements.

Disabled State

<Button disabled>
  Loading...
</Button>
Styling: Automatically applies pointer-events-none and opacity-50 when disabled.

Styling System

Class Variance Authority (CVA)

The button uses CVA for managing variants:
const buttonVariants = cva(
  "base-classes",  // Applied to all buttons
  {
    variants: {      // Conditional classes
      variant: { /* ... */ },
      size: { /* ... */ },
    },
    defaultVariants: { /* ... */ },
  }
)

Utility Function: cn

The cn utility combines clsx and tailwind-merge for intelligent class 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))
}
Benefits:
  • Merges Tailwind classes intelligently (e.g., px-4 px-6px-6)
  • Handles conditional classes
  • Prevents class conflicts

Custom Styling

<Button 
  variant="outline" 
  className="bg-blue-500 hover:bg-blue-600"
>
  Custom Styled
</Button>
Custom classes are merged with variant classes using cn(). Some Tailwind classes may override variant styles, while others will be intelligently merged.

Accessibility Features

  • Standard button keyboard behavior (Space/Enter to activate)
  • Custom focus-visible styles with ring indicator
  • Focus ring color: ring-ring/50 with 3px width
  • Semantic <button> element by default
  • Supports ARIA attributes via props
  • data-slot="button" attribute for component identification
  • aria-invalid styling support
  • Destructive variant has enhanced focus ring
  • Disabled state prevents interaction

Navigation

See how buttons are used in navigation

Component Overview

Learn about the component architecture

Build docs developers (and LLMs) love