v2.0 is here! New components and themes are coming soon! Stay tuned.
TWJ Labs UI

Button

Button Component, a versatile and stylish button for your UI needs.

A simple button component

A fully theme-aware, customizable React button designed for modern UI systems.
Supports multiple themes, variants, and sizes while remaining developer-friendly and easy to extend.

TL;DR: Use <Button> when you want something that looks smart, acts fast, and outclasses everything else on the page.

Installation

Using CLI

if you're using twjlabs/ui cli, it should be pretty easy for you

npx @twjlabs/ui add button
pnpm dlx @twjlabs/ui add button

Manual Installation

if you're not using the cli, you would need to install it manually,

Make sure you have twj-lib folder with necessary utility functions and types in your project. If not, you can refer here to set it up.

Finally, copy and paste the following code in your components/ui folder

components/ui/button.tsx
"use client";

import React from 'react';
import { cn } from '@/twj-lib/tw';
import { fontApplier } from '@/twj-lib/font-applier';
import type { TWJAIComponentsProps, TWJComponentsProps } from '@/twj-lib/types';
import { useTheme } from '@/contexts/ui-theme-context';
import { useAIControl } from '@/contexts/ai-context'; 

type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'link' | 'ghost';
type ButtonSize = 'small' | 'default' | 'large' | 'icon';

interface ButtonProps extends TWJAIComponentsProps {
  label?: string;
  children?: React.ReactNode;
  onClick?: () => void;
  variant?: ButtonVariant;
  size?: ButtonSize;
  className?: string;
}

export const Button = ({
  label,
  children,
  onClick,
  theme,
  variant = "primary",
  size = "default",
  className,
  aiID,
  aiDescription,
  ...props
}: ButtonProps & React.ComponentProps<"button">) => {

  const { theme: contextTheme } = useTheme();
  const [mounted, setMounted] = React.useState(false);

  // 🤖 Register with the Brain
  // We wrap the onClick so the AI can trigger "click"
 if (aiID) {
   useAIControl({
    id: aiID || '', 
    description: aiDescription || '',
    actions: {
      click: async () => {
        if (onClick) onClick();
      }
    }
  });
 }

  React.useEffect(() => {
    setMounted(true);
  }, []);

  const activeTheme = theme || contextTheme || "modern";
  const appliedTheme = mounted ? activeTheme : "modern";
  const fontClass = fontApplier(appliedTheme);
  const themeClass = `theme-${appliedTheme}`;

  return (
    <button
      onClick={onClick}
      {...props}
      className={cn(
        themeClass,
        fontClass,
        'rounded-theme font-semibold transition-all duration-200 cursor-pointer w-fit',
        'focus:outline-none',
        size === 'small' && 'text-sm py-2 px-4',
        size === 'default' && 'text-base py-2.5 px-6',
        size === 'large' && 'text-lg py-3 px-8',
        size === 'icon' && 'p-2 flex items-center justify-center aspect-square w-full max-w-10',
        
        // Variants
        variant === 'primary' && [
          'bg-primary dark:bg-primary-dark-mode text-white hover:bg-primary-dark',
          'disabled:opacity-50 disabled:cursor-not-allowed',
          appliedTheme === 'futuristic' && 'shadow-[0px_0px_15px_0px_var(--color-primary)]',
          appliedTheme === 'brutalist' && 'shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]! dark:shadow-accent! bg-primary! dark:bg-primary-dark-mode! text-background! hover:bg-primary/80!',
          appliedTheme === 'modern' && 'bg-gradient-to-r! from-[var(--color-gradient-one)]! to-[var(--color-gradient-three)]! text-white'
        ],
        variant === 'secondary' && [
          'bg-surface text-foreground border border-muted/20 hover:bg-muted/10',
          appliedTheme === 'brutalist' && 'bg-gray-200 border-2 border-black'
        ],
        variant === 'outline' && 'bg-transparent border-2 border-primary text-primary hover:bg-primary hover:text-white',
        variant === 'ghost' && 'bg-transparent text-foreground hover:bg-muted/20',
        appliedTheme === 'brutalist' && [
          'bg-background text-foreground uppercase tracking-wider border-2 border-black',
          'hover:bg-primary hover:text-background',
          'shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]',
          'active:translate-x-[2px] active:translate-y-[2px] active:shadow-none'
        ],
        className
      )}
    >
      {label}
      {children}
    </button>
  );
};

✨ Features

  • Theme support (modern, elegant, brutalist, futuristic, etc.)
  • 5 variants: primary, secondary, outline, link, ghost
  • 4 sizes: small, default, large, icon
  • Automatic theme selection from context
  • Hydration-safe for Next.js

Usage

Usage Example
import { Button } from '@/components/ui/button';

<Button 
  variant="primary" 
  size="large" 
  onClick={() => alert('Button clicked!')}
>
  Click Me
</Button>

📦 Props

PropTypeDefaultDescription
labelstringThe button text
childrenReactNodeFor icons or custom content
onClick() → voidClick handler
themeThemecontextOverrides the theme context
variant'primary' | 'secondary' | 'outline' | 'link' | 'ghost'primaryVisual style
size'small' | 'default' | 'large' | 'icon'defaultButton size
classNamestringCustom styling
...propsnative button propsAll other HTML button attributes

On this page