Skip to content

Card

A flexible card component for displaying content in a contained, organized format. Supports images, headers, footers, and various interactive states. Built for Pulse Framework with full reactivity support.

Import

tsx
import { Card, Pulse } from '@odyssee/components';

Basic Usage

Code Éditable
Résultat

With Image

Add images to cards with flexible positioning.

Code Éditable
Résultat

Variants

The Card component supports multiple variants: default, bordered, and shadow.

Code Éditable
Résultat

Sizes

Three size options control padding: sm, md, and lg.

Code Éditable
Résultat

Add custom headers and footers to cards.

Code Éditable
Résultat

Horizontal Layout

Display cards in horizontal layout with image on the side.

Code Éditable
Résultat

Clickable Cards

Make entire cards clickable with hover effects.

tsx
const clickableCard = (
  <Card
    image="https://images.unsplash.com/photo-1680868543815-b8666dba60f7"
    title="Clickable Card"
    clickable={true}
    href="/articles/123"
    hoverEffect="shadow"
  >
    Click anywhere on this card to navigate
  </Card>
);

Hover Effects

Apply different hover effects to interactive cards.

Code Éditable
Résultat

Centered Content

Center align all content within the card.

Code Éditable
Résultat

Image Overlay

Display content over the image as an overlay.

Code Éditable
Résultat

Scrollable Content

Make card content scrollable with a fixed height.

tsx
const scrollableCard = (
  <Card
    title="Scrollable Content"
    scrollable={true}
    scrollHeight="h-64"
  >
    <p>Long content that will be scrollable...</p>
    <p>More content...</p>
    <p>Even more content...</p>
    <p>This continues...</p>
  </Card>
);

Empty State

Display an empty state with optional icon and message.

Code Éditable
Résultat

With Actions

Add action buttons to card footers.

tsx
const cardWithActions = (
  <Card
    title="Article Title"
    subtitle="Published 2 days ago"
  >
    <p>Article preview text goes here...</p>
    <div class="mt-4 flex gap-2">
      <Button variant="solid" color="primary" size="sm">
        Read More
      </Button>
      <Button variant="outline" color="secondary" size="sm">
        Share
      </Button>
    </div>
  </Card>
);

Reactive Content

Use Pulse signals to create dynamic cards.

tsx
import { Card, Button, Pulse } from '@odyssee/components';

const DynamicCard = () => {
  const likes = Pulse.signal(42);
  const isLiked = Pulse.signal(false);

  const handleLike = () => {
    if (isLiked()) {
      likes(likes() - 1);
    } else {
      likes(likes() + 1);
    }
    isLiked(!isLiked());
  };

  return (
    <Card
      image="https://images.unsplash.com/photo-1680868543815-b8666dba60f7"
      title="Interactive Card"
    >
      <p>Click the button to like this content</p>
      <div class="mt-4 flex items-center gap-3">
        <Button
          variant={isLiked() ? 'solid' : 'outline'}
          color="danger"
          size="sm"
          icon="❤️"
          onClick={handleLike}
        >
          {likes()} Likes
        </Button>
      </div>
    </Card>
  );
};

Complete Example

Here's a comprehensive example combining multiple features:

tsx
import { Card, Badge, Button, Avatar, Pulse } from '@odyssee/components';

const BlogPostCard = () => {
  const post = Pulse.signal({
    title: 'Getting Started with Pulse Framework',
    excerpt: 'Learn how to build reactive applications with Pulse Framework...',
    image: 'https://images.unsplash.com/photo-1680868543815-b8666dba60f7',
    author: {
      name: 'John Doe',
      avatar: 'https://images.unsplash.com/photo-1568602471122-7832951cc4c5'
    },
    category: 'Tutorial',
    readTime: '5 min read',
    publishedAt: '2 days ago',
    likes: 124,
    comments: 18
  });

  const isBookmarked = Pulse.signal(false);

  return (
    <Card
      image={post().image}
      imageAlt={post().title}
      hoverEffect="shadow"
      clickable={true}
    >
      {/* Header with category badge */}
      <div class="flex items-center justify-between mb-3">
        <Badge variant="soft" color="primary">
          {post().category}
        </Badge>
        <span class="text-xs text-gray-500">
          {post().readTime}
        </span>
      </div>

      {/* Title and excerpt */}
      <h3 class="text-xl font-bold mb-2 text-gray-800 dark:text-white">
        {post().title}
      </h3>
      <p class="text-gray-600 dark:text-gray-400 mb-4">
        {post().excerpt}
      </p>

      {/* Author info */}
      <div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700">
        <div class="flex items-center gap-2">
          <Avatar
            src={post().author.avatar}
            alt={post().author.name}
            size="sm"
          />
          <div>
            <p class="text-sm font-medium">{post().author.name}</p>
            <p class="text-xs text-gray-500">{post().publishedAt}</p>
          </div>
        </div>

        {/* Stats and actions */}
        <div class="flex items-center gap-3">
          <span class="text-sm text-gray-500">
            ❤️ {post().likes}
          </span>
          <span class="text-sm text-gray-500">
            💬 {post().comments}
          </span>
          <Button
            variant="ghost"
            size="sm"
            icon={isBookmarked() ? '🔖' : '📑'}
            onClick={(e) => {
              e.preventDefault();
              isBookmarked(!isBookmarked());
            }}
          />
        </div>
      </div>
    </Card>
  );
};

Props

PropTypeDefaultDescription
titlestring-Card title
subtitlestring-Card subtitle
childrenstring | HTMLElement | Array-Card body content
headerstring | HTMLElement-Custom header content
headerBorderedbooleanfalseAdd border to header
footerstring | HTMLElement-Custom footer content
footerBorderedbooleanfalseAdd border to footer
imagestring-Image URL
imageAltstring"Card Image"Image alt text
imagePosition"top" | "bottom""top"Image position
imageOverlaybooleanfalseDisplay content over image
imageRoundedbooleantrueRound image corners
variant"default" | "bordered" | "shadow""default"Card variant style
size"sm" | "md" | "lg""md"Padding size
horizontalbooleanfalseHorizontal layout
centeredbooleanfalseCenter align content
clickablebooleanfalseMake card clickable
hrefstring-Link URL (makes card an anchor)
onClick(event: Event) => void-Click handler
scrollablebooleanfalseEnable scrolling
scrollHeightstring"h-80"Height for scrollable content
emptyStatebooleanfalseShow empty state
emptyStateIconstring-Empty state icon
emptyStateTextstring"No data to show"Empty state message
hoverEffect"none" | "shadow" | "scale""none"Hover animation
classNamestring-Additional CSS classes
idstring-HTML id attribute
stylestring | CSSStyleDeclaration-Inline styles

Accessibility

The Card component follows accessibility best practices:

  • ✅ Semantic HTML structure
  • ✅ Proper heading hierarchy
  • ✅ Keyboard navigation for clickable cards
  • ✅ Focus indicators
  • ✅ Screen reader friendly content
  • ✅ Proper alt text for images

Best Practices

✅ Do

  • Use appropriate image sizes and optimize for web
  • Provide meaningful titles and descriptions
  • Use consistent card sizes in grids
  • Add loading states for async content
  • Include proper alt text for images
tsx
// Good: Well-structured card with all needed info
const goodCard = (
  <Card
    image="optimized-image.jpg"
    imageAlt="Descriptive alt text"
    title="Clear, Descriptive Title"
    subtitle="Additional context"
  >
    Meaningful content that adds value
  </Card>
);

❌ Don't

  • Don't use cards for everything (use appropriate components)
  • Don't nest cards too deeply
  • Don't forget to handle loading states
  • Don't use very large unoptimized images
tsx
// Bad: Poor structure and no context
const badCard = (
  <Card image="huge-image.png">
    Text
  </Card>
);

Use Cases

Product Cards

tsx
const ProductCard = ({ product }) => (
  <Card
    image={product.image}
    imageAlt={product.name}
    clickable={true}
    href={`/products/${product.id}`}
    hoverEffect="shadow"
  >
    <div class="flex justify-between items-start mb-2">
      <h3 class="font-bold">{product.name}</h3>
      <Badge color="success">{product.discount}% OFF</Badge>
    </div>
    <p class="text-gray-600 text-sm mb-3">{product.description}</p>
    <div class="flex items-center justify-between">
      <div>
        <span class="text-2xl font-bold">${product.price}</span>
        <span class="text-sm text-gray-500 line-through ml-2">
          ${product.originalPrice}
        </span>
      </div>
      <Button size="sm" color="primary">Add to Cart</Button>
    </div>
  </Card>
);

User Profile Cards

tsx
const UserProfileCard = ({ user }) => (
  <Card centered={true}>
    <Avatar
      src={user.avatar}
      size="xl"
      className="mb-4"
    />
    <h3 class="text-xl font-bold mb-1">{user.name}</h3>
    <p class="text-gray-500 mb-4">{user.title}</p>
    <div class="flex gap-2">
      <Badge variant="soft" color="primary">{user.followers} Followers</Badge>
      <Badge variant="soft" color="success">{user.posts} Posts</Badge>
    </div>
  </Card>
);

Dashboard Stats

tsx
const StatsCard = ({ title, value, change, icon }) => (
  <Card size="sm">
    <div class="flex items-center justify-between">
      <div>
        <p class="text-sm text-gray-500">{title}</p>
        <p class="text-2xl font-bold mt-1">{value}</p>
        <p class={`text-sm mt-1 ${change > 0 ? 'text-green-600' : 'text-red-600'}`}>
          {change > 0 ? '↑' : '↓'} {Math.abs(change)}%
        </p>
      </div>
      <div class="text-4xl">{icon}</div>
    </div>
  </Card>
);

Styling & Theming

All card styles use Tailwind CSS classes and support dark mode automatically.

Custom Styling

tsx
const customCard = (
  <Card
    className="border-l-4 border-blue-500 hover:border-blue-600 transition-colors"
    title="Custom Styled Card"
  >
    Additional custom styling with Tailwind classes
  </Card>
);

TypeScript

Full TypeScript support with complete type definitions:

tsx
import type { CardProps } from '@odyssee/components';

const props: CardProps = {
  title: 'TypeScript Card',
  variant: 'shadow',
  size: 'md',
  onClick: (e: Event) => {
    console.log('Card clicked!');
  }
};

const card = <Card {...props}>Content</Card>;
  • Avatar - Display user avatars in cards
  • Badge - Add status badges to cards
  • Button - Add action buttons to cards

Version: 1.0.0
Last Updated: January 2025

Released under the MIT License.