Card

Cards are flexible content containers that display information in a structured, visually appealing format. They're one of the most versatile UI components, perfect for showcasing products, articles, user profiles, or any grouped content.

Key Features

  • Flexible content structure with optional header, body, and footer
  • Hover effects with lift animation and shadow enhancement
  • Clickable cards that act as interactive links
  • Theme-aware styling with automatic light/dark mode support
  • Customisable shadows, borders, and spacing

When to Use Cards

  • Content Collections: Blog posts, products, team members, portfolio items
  • Dashboards: Statistics widgets, activity feeds, metric displays
  • Navigation: Feature highlights, service offerings, call-to-action blocks
  • Forms: Multi-step wizards, settings panels, grouped inputs

Basic Usage

// HTML structure
<div id="my-card" class="card">
    <div class="card-header">Card Header</div>
    <div class="card-body">
        <h3 class="card-title">Card Title</h3>
        <p class="card-text">Card content goes here.</p>
    </div>
    <div class="card-footer">Card Footer</div>
</div>

// JavaScript (optional - for hover/click effects)
const card = Domma.elements.card('#my-card', {
    hover: true,
    clickable: false
});

Quick Start

Get started with a basic card in seconds. Cards work with pure CSS (no JavaScript required), but you can enhance them with hover effects using the Card component.

CSS-Only Card (No JavaScript)

<div class="card">
    <div class="card-body">
        <h3 class="card-title">Simple Card</h3>
        <p class="card-text">This card requires no JavaScript.</p>
    </div>
</div>

Simple Card

This card requires no JavaScript. It uses only CSS classes for styling.

Card with Hover Effect (JavaScript)

// HTML
<div id="hover-card" class="card">
    <div class="card-body">
        <h3 class="card-title">Hover Me</h3>
        <p class="card-text">Move your cursor over this card.</p>
    </div>
</div>

// JavaScript
Domma.elements.card('#hover-card', { hover: true });

Hover Me

Move your cursor over this card to see the lift effect.

Step-by-Step Tutorial

Learn how to build an interactive product card from scratch, step by step.

Step 1: HTML Structure

Start with the basic card structure using semantic class names:

<div id="product-card" class="card">
    <div class="card-body">
        <h3 class="card-title">Product Name</h3>
        <p class="card-text">Brief description of the product.</p>
    </div>
</div>

Step 2: Add Header and Footer

Expand the structure with a header (for images or badges) and footer (for actions):

<div id="product-card" class="card">
    <div class="card-header">
        <span class="badge badge-primary">New</span>
    </div>
    <div class="card-body">
        <h3 class="card-title">Premium Headphones</h3>
        <p class="card-text">High-quality wireless headphones with noise cancellation.</p>
        <p class="card-text"><strong>£149.99</strong></p>
    </div>
    <div class="card-footer">
        <button class="btn btn-primary">Add to Cart</button>
    </div>
</div>

Step 3: Enable Hover Effect

Add the hover effect to make the card interactive:

const productCard = Domma.elements.card('#product-card', {
    hover: true  // Enables lift animation on hover
});

Step 4: Make It Clickable

Turn the entire card into a clickable link:

const productCard = Domma.elements.card('#product-card', {
    hover: true,
    clickable: true,
    onClick: (event) => {
        console.log('Card clicked!');
        window.location.href = '/product/premium-headphones';
    }
});

Step 5: Complete Working Demo

Here's the finished interactive product card:

New

Premium Headphones

High-quality wireless headphones with active noise cancellation and 30-hour battery life.

£149.99

Examples & Variations

Example 1: Basic Card

Simple card with title and content:

Basic Card

A simple card with just a title and some text content. No header or footer required.

Example 2: Card with Hover Effect

Card that lifts on hover with enhanced shadow:

Hover Card

Hover over this card to see the lift animation and enhanced shadow effect.

Example 3: Clickable Card

Entire card acts as a button:

Clickable Card

Click anywhere on this card to trigger an action.

Example 4: Styled Card (Primary)

Card with primary colour border:

Featured

Premium Plan

Unlock all features with our premium plan.

Example 5: Profile Card

User profile with avatar and actions:

JD

Jane Doe

Senior Developer

Passionate about building beautiful, accessible web experiences.

Example 6: Statistics Card

Dashboard metric with icon:

Total Sales

£24,580

↑ 12.5% from last month

Example 7: Horizontal Card

Card with side-by-side layout:

Wireless Mouse

Ergonomic design with precision tracking and long battery life. Perfect for productivity.

Example 8: Grid of Cards

Multiple cards in a responsive grid:

Feature One

Fast and reliable performance.

Feature Two

Secure data encryption.

Feature Three

24/7 customer support.

Best Practices

Accessibility

Performance

  • Lazy Initialisation: Only initialise hover/click effects on cards that need them
  • Event Delegation: For grids with many cards, consider single parent event listener instead of individual listeners
  • Destroy Instances: Call card.destroy() when removing cards from DOM to prevent memory leaks
  • CSS-First Approach: Use CSS classes (e.g., .card-hover) when JavaScript isn't needed
  • Debounce Hover: For expensive hover effects, consider debouncing the animation triggers

Common Gotchas

Issue Solution
Hover effect not working Ensure you're calling elements.card() with hover: true. CSS-only cards don't have hover effects unless you add .card-hover class manually.
Card stretching in grid Cards in CSS Grid will stretch by default. Add align-self: start or use height: 100% on cards for equal-height rows.
Click events not firing Check z-index of child elements. Buttons/links inside cards may intercept clicks. Use event.stopPropagation() on child elements if needed.
Theme colours not applying Ensure domma-themes.css is loaded and a theme class (e.g., .dm-theme-charcoal-dark) is on .

Tips & Tricks

  • Loading States: Combine cards with the Loader component for skeleton screens
  • Animations: Stagger card entrance animations for visual interest (use CSS animation-delay)
  • Equal Heights: Use CSS Grid or Flexbox on the parent to create equal-height card rows
  • Responsive Images: Use object-fit: cover for card header images to maintain aspect ratio
  • Truncation: Limit card text with CSS: display: -webkit-box; -webkit-line-clamp: 3; overflow: hidden;
  • Integration: Cards work great inside Modals, Tabs, and Accordion panels

API Reference

Constructor Options

Option Type Default Description
hover Boolean false Enable hover lift effect with enhanced shadow
clickable Boolean false Make the entire card clickable
onClick Function null Callback function when card is clicked (requires clickable: true)

Methods

Method Parameters Returns Description
destroy() None void Removes event listeners and cleans up the card instance

Events

Event Parameters Description
onClick event: MouseEvent Fired when a clickable card is clicked

CSS Classes

Class Description
.card Base card container with background, border, and shadow
.card-header Optional top section with subtle background
.card-body Main content area with padding
.card-footer Optional bottom section with subtle background
.card-title Primary heading within card body
.card-subtitle Secondary heading with muted colour
.card-text Body text with appropriate line-height
.card-hover CSS-only hover effect (alternative to JavaScript)
.card-primary Card with primary colour border and themed header

Hybrid CSS + JavaScript

Cards work beautifully with pure CSS, but you can enhance them with JavaScript for dynamic content loading, filtering, sorting, animations, and interactive features using Domma's declarative configuration.

Dynamic Content Loading

$.setup({
    '#product-grid': {
        events: {
            ready: async (e, $container) => {
                // Load products from API
                const products = await Domma.http.get('/api/products');

                // Render cards
                const html = _.map(products, product => `
                    <div class="card card-hover">
                        <img src="${product.image}" alt="${product.name}" class="card-img-top">
                        <div class="card-body">
                            <h4 class="card-title">${product.name}</h4>
                            <p class="card-text">${_.truncate(product.description, { length: 100 })}</p>
                            <div class="card-footer">
                                <span class="price">$${product.price}</span>
                                <button class="btn btn-primary btn-sm"
                                        data-id="${product.id}">
                                    Add to Cart
                                </button>
                            </div>
                        </div>
                    </div>
                `).join('');

                $container.html(html);
            },

            'click [data-id]': (e) => {
                const productId = $(e.currentTarget).attr('data-id');
                // Add to cart logic
                Domma.elements.toast.success('Added to cart!');
            }
        }
    }
});

Filterable Card Grid

$.setup({
    '#blog-grid': {
        initial: {
            data: { posts: [], filteredPosts: [], activeCategory: 'all' }
        },
        events: {
            ready: async (e, $el) => {
                const posts = await Domma.http.get('/api/posts');
                $el.data('posts', posts);
                $el.data('filteredPosts', posts);
                renderCards($el, posts);
            },

            'click [data-category]': (e, $el) => {
                const category = $(e.currentTarget).attr('data-category');
                const allPosts = $el.data('posts');

                // Filter posts
                const filtered = category === 'all'
                    ? allPosts
                    : _.filter(allPosts, { category });

                $el.data('activeCategory', category);
                $el.data('filteredPosts', filtered);

                // Update UI
                $('.category-filter').removeClass('active');
                $(e.currentTarget).addClass('active');

                // Render with animation
                $('.cards-container').fadeOut(200, function() {
                    renderCards($el, filtered);
                    $(this).fadeIn(200);
                });
            }
        }
    }
});

function renderCards($container, posts) {
    const html = _.map(posts, post => `
        <div class="card">
            <div class="card-body">
                <h4 class="card-title">${post.title}</h4>
                <p class="card-text">${_.truncate(post.excerpt, { length: 120 })}</p>
                <a href="/post/${post.slug}" class="btn btn-outline">Read More</a>
            </div>
        </div>
    `).join('');

    $container.find('.cards-container').html(html);
}

Sortable Cards

$.setup({
    '#team-grid': {
        initial: {
            data: { members: [] }
        },
        events: {
            ready: async (e, $el) => {
                const members = await Domma.http.get('/api/team');
                $el.data('members', members);
                renderTeam($el, members);
            },

            'change #sort-by': (e, $el) => {
                const sortBy = $(e.target).val();
                const members = $el.data('members');

                // Sort with utils
                const sorted = sortBy === 'name'
                    ? _.sortBy(members, 'name')
                    : _.orderBy(members, ['role', 'name'], ['asc', 'asc']);

                renderTeam($el, sorted);
            }
        }
    }
});

function renderTeam($container, members) {
    const html = _.map(members, member => `
        <div class="card text-center">
            <img src="${member.avatar}" alt="${member.name}"
                 class="card-img-top" class="team-member-avatar">
            <div class="card-body">
                <h4 class="card-title">${member.name}</h4>
                <p class="card-text">${member.role}</p>
            </div>
        </div>
    `).join('');

    $container.find('.team-container').html(html);
}

Interactive Card Actions

$.setup({
    '.product-card': {
        component: 'card',
        options: { hover: true, clickable: true },
        events: {
            click: (e, $card) => {
                const productId = $card.attr('data-product');
                window.location.href = `/product/${productId}`;
            },

            'click .btn-favorite': (e) => {
                e.stopPropagation(); // Don't trigger card click

                const $btn = $(e.currentTarget);
                const $card = $btn.closest('.card');
                const productId = $card.attr('data-product');

                // Toggle favorite
                $btn.toggleClass('active');
                const isFavorite = $btn.hasClass('active');

                Domma.http.post('/api/favorites', { productId, favorite: isFavorite })
                    .then(() => {
                        Domma.elements.toast.success(
                            isFavorite ? 'Added to favorites' : 'Removed from favorites'
                        );
                    });
            },

            'mouseenter': (e, $card) => {
                $card.find('.quick-actions').fadeIn(200);
            },

            'mouseleave': (e, $card) => {
                $card.find('.quick-actions').fadeOut(200);
            }
        }
    }
});

Benefits of Hybrid Approach

  • Performance - CSS handles layout instantly, JS loads dynamic content
  • Flexibility - Static cards + dynamic data = powerful combination
  • Interactivity - Filtering, sorting, actions enhance UX
  • Maintainability - Centralized logic with $.setup()

CSS-Only vs CSS+JS Hybrid

CSS-Only (Static)

<div class="card">
    <img src="image.jpg"
         class="card-img-top"
         alt="Product">
    <div class="card-body">
        <h4 class="card-title">
            Static Product
        </h4>
        <p class="card-text">
            Fixed content
        </p>
        <a href="#" class="btn">
            View
        </a>
    </div>
</div>

<!-- No dynamic content -->

CSS+JS Hybrid (Dynamic)

<div id="products">
    <!-- Cards loaded via JS -->
</div>

<script>
$.setup({
    '#products': {
        events: {
            ready: async (e, $el) => {
                const products = await
                    Domma.http.get('/api/products');

                const html = _.map(products, p => `
                    <div class="card">
                        ...
                    </div>
                `).join('');

                $el.html(html);
            }
        }
    }
});
</script>
                    

Related Elements