ComponentsThemes
Palette
Default
Stone
Rose
Blue
Green
Violet
Yellow
Orange
6

← Components

Card

A container with header, title, description, content, and footer sub-components.

Installation

bin/rails g wabi:add card

Example

Standalone (no header)

For cards without a CardHeader, pass padding: :standalone to CardContent for symmetric vertical padding.

Source

app/components/ui/card.rb

# frozen_string_literal: true

module Components
  module UI
    class Card < Wabi::Base
      variants do
        base "rounded-lg border bg-card text-card-foreground shadow-sm"
      end

      def initialize(**attrs) = @attrs = attrs

      def view_template(&)
        user_class = @attrs.delete(:class)
        div(**@attrs, class: merge_class(tokens, user_class), &)
      end
    end
  end
end

app/components/ui/card_header.rb

# frozen_string_literal: true

module Components
  module UI
    class CardHeader < Wabi::Base
      variants do
        base "flex flex-col space-y-1.5 p-6"
      end

      def initialize(**attrs) = @attrs = attrs

      def view_template(&)
        user_class = @attrs.delete(:class)
        div(**@attrs, class: merge_class(tokens, user_class), &)
      end
    end
  end
end

app/components/ui/card_title.rb

# frozen_string_literal: true

module Components
  module UI
    class CardTitle < Wabi::Base
      variants do
        base "text-2xl font-semibold leading-none tracking-tight"
      end

      # level: (Integer, default 3) — heading level 1-6; allows callers to
      # match the document outline when the card is a top-level content block.
      def initialize(level: 3, **attrs)
        @level = level.to_i.clamp(1, 6)
        @attrs = attrs
      end

      def view_template(&)
        user_class = @attrs.delete(:class)
        send(:"h#{@level}", **@attrs, class: merge_class(tokens, user_class), &)
      end
    end
  end
end

app/components/ui/card_description.rb

# frozen_string_literal: true

module Components
  module UI
    class CardDescription < Wabi::Base
      variants do
        base "text-sm text-muted-foreground"
      end

      def initialize(**attrs) = @attrs = attrs

      def view_template(&)
        user_class = @attrs.delete(:class)
        p(**@attrs, class: merge_class(tokens, user_class), &)
      end
    end
  end
end

app/components/ui/card_content.rb

# frozen_string_literal: true

module Components
  module UI
    class CardContent < Wabi::Base
      variants do
        base "px-6"

        # `:with_header` (default) mantiene el comportamiento shadcn: sin padding
        # arriba porque CardContent va debajo de un CardHeader.
        # `:standalone` agrega padding vertical simétrico para cards sin header.
        variant :padding, {
          with_header: "pt-0 pb-6",
          standalone:  "py-6"
        }, default: :with_header
      end

      def initialize(padding: nil, **attrs)
        @padding = padding
        @attrs   = attrs
      end

      def view_template(&)
        user_class = @attrs.delete(:class)
        div(**@attrs, class: merge_class(tokens(padding: @padding), user_class), &)
      end
    end
  end
end
# frozen_string_literal: true

module Components
  module UI
    class CardFooter < Wabi::Base
      variants do
        base "flex items-center p-6 pt-0"
      end

      def initialize(**attrs) = @attrs = attrs

      def view_template(&)
        user_class = @attrs.delete(:class)
        div(**@attrs, class: merge_class(tokens, user_class), &)
      end
    end
  end
end

Accessibility

  • Card is a structural container with no implicit semantics; the meaning comes from contents.
  • CardTitle renders as h3 by default — verify the heading level fits the page outline.
  • footer actions should be real <button> or <a> elements (the Button component is correct here), not styled <div>s.