|Design System

Core Component

Button

Primary interaction element with four semantic variants and a loading state for async operations.

Preview

Source

Full component implementation using the design system tokens.

tsx
"use client";

import { Loader2 } from "lucide-react";

const variantStyles = {
  primary: "bg-accent text-background hover:bg-accent/90",
  secondary: "bg-surface border border-overlay/10 text-foreground hover:bg-surface/80",
  ghost: "text-secondary hover:bg-overlay/5 hover:text-foreground",
  destructive: "bg-red-500/10 text-red-500 hover:bg-red-500/20",
};

const sizeStyles = {
  sm: "px-3 py-1.5 text-xs rounded-lg gap-1.5",
  md: "px-4 py-2 text-sm rounded-lg gap-2",
  lg: "px-6 py-3 text-base rounded-xl gap-2",
};

export function DSButton({
  variant = "primary",
  size = "md",
  loading = false,
  disabled = false,
  children,
  ...props
}: {
  variant?: "primary" | "secondary" | "ghost" | "destructive";
  size?: "sm" | "md" | "lg";
  loading?: boolean;
  disabled?: boolean;
  children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>) {
  return (
    <button
      className={`inline-flex items-center justify-center font-medium transition-colors duration-150 ${variantStyles[variant]} ${sizeStyles[size]} ${
        disabled || loading ? "opacity-50 cursor-not-allowed" : ""
      }`}
      disabled={disabled || loading}
      {...props}
    >
      {loading && <Loader2 className="w-4 h-4 animate-spin" />}
      {children}
    </button>
  );
}

Props

All available props with types and defaults.

PropTypeDescription
children*ReactNodeButton label content
variant'primary' | 'secondary' | 'ghost' | 'destructive'Visual style variant
size'sm' | 'md' | 'lg'Button size
loadingbooleanShows spinner and disables interaction
disabledbooleanDisables the button

Variants

Primary

Default CTA button. Use for the single most important action on screen.

tsx
<DSButton variant="primary">Send message</DSButton>

Secondary

Supporting action. Pairs with primary without competing for attention.

tsx
<DSButton variant="secondary">Save draft</DSButton>

Ghost

Minimal footprint. For toolbar actions and inline controls.

tsx
<DSButton variant="ghost">Cancel</DSButton>

Destructive

Signals irreversible or high-risk actions.

tsx
<DSButton variant="destructive">Delete conversation</DSButton>

Loading

Async state. Shows spinner and prevents double-submission.

tsx
<DSButton variant="primary" loading>Generating...</DSButton>

Sizes

Three size options for different density contexts.

tsx
<DSButton size="sm">Small</DSButton>
<DSButton size="md">Medium</DSButton>
<DSButton size="lg">Large</DSButton>

Prompt Guide

Prompt Guide — Button

Use for

  • Send message / submit prompt actions
  • Save, cancel, and confirm dialogs
  • Toolbar actions in AI tool panels
  • Navigation CTAs

Don't use for

  • Inline text links — use an anchor tag
  • Toggle states — use a toggle or switch component
  • Navigation tabs — use a tab component

AI Context

In AI interfaces, the primary button is almost always 'Send' or 'Run'. Use loading state during generation. Secondary pairs with primary for 'Save draft' next to 'Send'. Ghost works for toolbar icons (copy, regenerate, edit). Destructive is for 'Delete conversation' or 'Clear context' — actions that lose AI-generated content.