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

Card

Card Component, a flexible and stylish container for displaying content

A flexible and stylish container.

The Card component is a flexible container used for surfaces such as:

  • cards
  • modals
  • panels
  • sections
  • dashboards

It supports themes, font styling, and automatic theme propagation to nested components like Buttons.


TL;DR: The Card component provides a versatile and visually appealing way to present content in a structured format. It is ideal for grouping related information, highlighting key details, and enhancing the overall user interface.

Installation

Using CLI

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

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

Manual Installation

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

Now 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/card.tsx
"use client"

import React from "react";
import type { Theme, TWJComponentsProps } from "@/twj-lib/types";
import { cn } from "@/twj-lib/tw";
import { fontApplier } from "@/twj-lib/font-applier";
import { useTheme, ThemeProvider } from "@/contexts/ui-theme-context";

// ----------------------------------------------------
// 🔵 Card Component
// ----------------------------------------------------
interface CardProps extends TWJComponentsProps {
  children?: React.ReactNode;
  className?: string; 
}

export const Card = ({ theme, children, className }: CardProps) => {
  const { theme: contextTheme } = useTheme();
  const [mounted, setMounted] = React.useState(false);

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

  const activeTheme = theme || contextTheme || "modern";
  const appliedTheme = mounted ? activeTheme : "modern";

  const fontClass = fontApplier(appliedTheme);
  const themeClass = `theme-${appliedTheme}`;

  const childrenWithTheme = React.Children.map(children, (child) => {
    if (React.isValidElement(child)) {
      return React.cloneElement(child as React.ReactElement<any>, { 
        theme: appliedTheme 
      });
    }
    return child;
  });

  return (
    <ThemeProvider initialTheme={appliedTheme} key={appliedTheme}>
      <div 
        data-theme={appliedTheme} 
        className={cn(
          themeClass,
          fontClass,
          
          // --- BASE STYLES ---
          'rounded-theme font-semibold transition-all duration-200',
          'p-4 w-full focus:outline-none',

          // --- EXPLICIT DARK MODE SWITCHING ---
          // Light Mode Class            // Dark Mode Class
          'bg-card                       dark:bg-card-dark',
          'border border-border          dark:border-border-dark',
          'text-card-foreground          dark:text-card-foreground-dark',

          // --- BRUTALIST OVERRIDES ---
          appliedTheme === 'brutalist' && [
            'uppercase tracking-wider',
            
            // Explicitly swap border color for brutalist (Black -> White)
            'border-2 border-black dark:border-white', 
            
            // Explicitly swap shadow color (Black Shadow -> White Shadow)
            'shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] dark:shadow-[4px_4px_0px_0px_rgba(255,255,255,1)]',
          ],
          
          className 
        )}
      >
        {childrenWithTheme}
      </div>
    </ThemeProvider>
  );
};

// ----------------------------------------------------
// 🟣 Card Header
// ----------------------------------------------------
interface CardHeaderProps {
  title?: string;
  icon?: React.ReactNode;
  theme?: Theme; 
  description?: string;
  className?: string;
  children?: React.ReactNode; 
}

export const CardHeader = ({ title, icon, description, theme, className, children }: CardHeaderProps) => {
  return (
    <div className={cn(
        'mb-4 flex flex-col items-start gap-2',
        // Brutalist: Border Black -> White
        theme === 'brutalist' && 'border-b-2 border-black dark:border-white pb-4 mb-4',
        className
    )}>
      {/* Icon Wrapper */}
      {icon && (
        <div className={cn(
          'mb-2 text-2xl',
          theme === 'brutalist' && [
             'p-2 border-2',
             // Border: Black -> White
             'border-black dark:border-white', 
             
             // Background: Primary -> Primary Dark Mode
             'bg-primary dark:bg-primary-dark-mode', 
             
             // Text: Foreground -> Foreground Dark
             'text-primary-foreground dark:text-primary-foreground-dark',
             
             // Shadow: Black -> White
             'shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] dark:shadow-[2px_2px_0px_0px_rgba(255,255,255,1)]'
          ]
        )}>
          {icon}
        </div>
      )}
      
      {/* Title */}
      {title && (
        <h2 className="text-2xl font-bold leading-none tracking-tight">
          {title}
        </h2>
      )}
      
      {/* Description */}
      {description && (
        <p className={cn(
          "text-sm",
          // Muted -> Muted Dark
          "text-muted-foreground dark:text-muted-foreground-dark"
        )}>
          {description}
        </p>
      )}

      {children}
    </div>
  );
};
CardHeader.displayName = "CardHeader";

// ----------------------------------------------------
// 🟢 Card Body
// ----------------------------------------------------
interface CardBodyProps {
  children?: React.ReactNode;
  className?: string;
  theme?: Theme; 
}

export const CardBody = ({ children, className, theme }: CardBodyProps) => {
  return (
    <div className={cn(
      "text-sm",
      // Foreground -> Foreground Dark
      "text-card-foreground dark:text-card-foreground-dark",
      className
    )}>
      {children}
    </div>
  );
};
CardBody.displayName = "CardBody";

// ----------------------------------------------------
// 🟠 Card Footer
// ----------------------------------------------------
interface CardFooterProps {
  children?: React.ReactNode;
  theme?: Theme;
  className?: string;
}

export const CardFooter = ({ children, className, theme }: CardFooterProps) => {
  return (
    <div className={cn(
      "flex items-center pt-4 mt-4",
      // Brutalist Divider: Black -> White
      theme === 'brutalist' && "pt-4  border-black dark:border-white", 
      className
    )}>
      {children}
    </div>
  );
};
CardFooter.displayName = "CardFooter";

Usage

Import the Button component and use it in your React application like so:

import { Card, CardHeader, CardBody, CardFooter } from "@/components/ui/card";

<Card className='max-w-[400px]' theme="brutalist">
  <CardHeader title='Card #1' description='This is a simple card component' />
  <CardBody>
    Hello
  </CardBody>
    <CardFooter>
      <Button>Click me</Button>
    </CardFooter>
</Card>

Card Props

PropTypeDefaultDescription
themeThememodernTheme override
childrenReactNodeCard content
classNamestringCustom Tailwind classes

Card Header Props

PropTypeDescription
titlestringHeader title
descriptionstringSubtitle or explanation
iconReactNodeOptional icon
childrenReactNodeCustom header content
classNamestringExtra styles
themeThemeAuto-injected by Card

Card Body Props

PropTypeDescription
childrenReactNodeBody content
classNamestringExtra styles
themeThemeOptional, rarely used
PropTypeDescription
childrenReactNodeFooter elements (buttons etc.)
classNamestringExtra styles
themeThemeAuto-injected

On this page