Skip to content

@vibe-labs/design-vue-cards

Vue 3 card components for the Vibe Design System. Compositional sub-components for headers, bodies, footers, and media.

Installation

ts
import { VibeCard, VibeCardHeader, VibeCardBody, VibeCardFooter, VibeCardMedia } from "@vibe-labs/design-vue-cards";

Requires the CSS layer from @vibe-labs/design-components-cards.

Components

VibeCard

Polymorphic card container with variant styling, interactive mode, and layout modifiers.

Usage

vue
<!-- Basic card -->
<VibeCard>
  <VibeCardHeader>Title</VibeCardHeader>
  <VibeCardBody>Content goes here.</VibeCardBody>
  <VibeCardFooter>Footer actions</VibeCardFooter>
</VibeCard>

<!-- Variants -->
<VibeCard variant="elevated">Elevated with shadow</VibeCard>
<VibeCard variant="outlined">Outlined border</VibeCard>
<VibeCard variant="ghost">Transparent</VibeCard>
<VibeCard variant="gradient">Gradient background</VibeCard>
<VibeCard variant="gradient-subtle">Subtle gradient</VibeCard>
<VibeCard variant="gradient-elevated">Gradient + shadow</VibeCard>

<!-- Sizes (controls section padding) -->
<VibeCard size="sm">Compact</VibeCard>
<VibeCard size="lg">Spacious</VibeCard>

<!-- Interactive card -->
<VibeCard interactive @click="handleClick">
  <VibeCardBody>Clickable card</VibeCardBody>
</VibeCard>

<!-- Link card (auto-detects href → as="a", interactive=true) -->
<VibeCard href="/details/123">
  <VibeCardBody>Click to navigate</VibeCardBody>
</VibeCard>

<!-- Semantic element -->
<VibeCard as="article">
  <VibeCardBody>An article card</VibeCardBody>
</VibeCard>

<!-- Horizontal layout with media -->
<VibeCard horizontal>
  <VibeCardMedia>
    <img src="/cover.jpg" alt="Album cover" />
  </VibeCardMedia>
  <VibeCardBody>Side-by-side content</VibeCardBody>
</VibeCard>

<!-- Modifiers -->
<VibeCard seamless>No internal borders</VibeCard>
<VibeCard flush>No section padding</VibeCard>

Props

PropTypeDefaultDescription
variantCardVariant"default"default · elevated · outlined · ghost · gradient · gradient-subtle · gradient-elevated
sizeCardSize"md"sm · md · lg
interactivebooleanfalseHover/focus states, pointer cursor
horizontalbooleanfalseRow layout with media capped at 40%
seamlessbooleanfalseRemove internal section borders
flushbooleanfalseRemove all section padding
asstring | Component"div"Root element (auto: "a" when href set)
hrefstringLink URL (auto-enables interactive + <a> tag)

Accessibility

  • Interactive non-link cards get role="button" and tabindex="0"
  • Link cards use native <a> semantics
  • Keyboard activation (Enter/Space) for interactive non-link cards

VibeCardHeader

Flex row section with bottom border. Typically used for title and actions.

vue
<VibeCardHeader>
  <h3>Card Title</h3>
  <VibeButton variant="ghost" icon><MoreIcon /></VibeButton>
</VibeCardHeader>

VibeCardBody

Flex-grow scrollable content area.

vue
<VibeCardBody>
  <p>Main card content goes here.</p>
</VibeCardBody>

VibeCardFooter

Flex row section with top border and subtle background.

vue
<VibeCardFooter>
  <VibeButton variant="ghost">Cancel</VibeButton>
  <VibeButton>Save</VibeButton>
</VibeCardFooter>

VibeCardMedia

Overflow-hidden container for images and video. Child <img> and <video> auto-fill.

vue
<VibeCardMedia>
  <img src="/album-art.jpg" alt="Album artwork" />
</VibeCardMedia>

Props

PropTypeDefaultDescription
position"top" | "bottom"Position hint within the card

Dependencies

PackagePurpose
@vibe-labs/design-components-cardsCSS tokens + generated styles

Build

bash
npm run build

Built with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.


Usage Guide

Setup

ts
import { VibeCard, VibeCardHeader, VibeCardBody, VibeCardFooter, VibeCardMedia } from "@vibe-labs/design-vue-cards";
import "@vibe-labs/design-components-cards";

VibeCard

Standard content card

vue
<script setup lang="ts">
import { VibeCard, VibeCardHeader, VibeCardBody, VibeCardFooter } from "@vibe-labs/design-vue-cards";
import { VibeButton } from "@vibe-labs/design-vue-buttons";
</script>

<template>
  <VibeCard variant="elevated">
    <VibeCardHeader>
      <h3>Track Details</h3>
      <VibeButton variant="ghost" icon aria-label="More options">
        <MoreIcon />
      </VibeButton>
    </VibeCardHeader>
    <VibeCardBody>
      <p>Beat Hotel — Dub Techno Mix</p>
      <p>Duration: 6:42</p>
    </VibeCardBody>
    <VibeCardFooter>
      <VibeButton variant="ghost">Cancel</VibeButton>
      <VibeButton>Add to Queue</VibeButton>
    </VibeCardFooter>
  </VibeCard>
</template>

Media card — horizontal layout

vue
<script setup lang="ts">
import { VibeCard, VibeCardMedia, VibeCardBody } from "@vibe-labs/design-vue-cards";
import { VibeImage } from "@vibe-labs/design-vue-images";

defineProps<{ title: string; artist: string; artwork: string; artworkThumb: string }>();
</script>

<template>
  <VibeCard horizontal variant="outlined">
    <VibeCardMedia>
      <VibeImage :src="artwork" :thumb-src="artworkThumb" :alt="`${title} artwork`" />
    </VibeCardMedia>
    <VibeCardBody>
      <h4>{{ title }}</h4>
      <p>{{ artist }}</p>
    </VibeCardBody>
  </VibeCard>
</template>
vue
<script setup lang="ts">
import { VibeCard, VibeCardBody } from "@vibe-labs/design-vue-cards";

defineProps<{ id: string; name: string }>();
</script>

<template>
  <!-- href auto-sets as="a" and interactive=true -->
  <VibeCard :href="`/venues/${id}`">
    <VibeCardBody>{{ name }}</VibeCardBody>
  </VibeCard>
</template>

Common Patterns

Card grid

vue
<script setup lang="ts">
import { VibeCard, VibeCardMedia, VibeCardBody } from "@vibe-labs/design-vue-cards";

defineProps<{ items: Array<{ id: string; title: string; image: string; href: string }> }>();
</script>

<template>
  <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px">
    <VibeCard v-for="item in items" :key="item.id" :href="item.href">
      <VibeCardMedia position="top">
        <img :src="item.image" :alt="item.title" />
      </VibeCardMedia>
      <VibeCardBody>{{ item.title }}</VibeCardBody>
    </VibeCard>
  </div>
</template>

Interactive card with click handler

vue
<script setup lang="ts">
import { VibeCard, VibeCardBody } from "@vibe-labs/design-vue-cards";

const emit = defineEmits<{ select: [id: string] }>();
defineProps<{ id: string; label: string }>();
</script>

<template>
  <VibeCard interactive @click="emit('select', id)">
    <VibeCardBody>{{ label }}</VibeCardBody>
  </VibeCard>
</template>

Vibe