Skip to content

@carrot/design-vue-cards

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

Installation

ts
import { CarrotCard, CarrotCardHeader, CarrotCardBody, CarrotCardFooter, CarrotCardMedia } from "@carrot/design-vue-cards";

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

Components

CarrotCard

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

Usage

vue
<!-- Basic card -->
<CarrotCard>
  <CarrotCardHeader>Title</CarrotCardHeader>
  <CarrotCardBody>Content goes here.</CarrotCardBody>
  <CarrotCardFooter>Footer actions</CarrotCardFooter>
</CarrotCard>

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

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

<!-- Interactive card -->
<CarrotCard interactive @click="handleClick">
  <CarrotCardBody>Clickable card</CarrotCardBody>
</CarrotCard>

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

<!-- Semantic element -->
<CarrotCard as="article">
  <CarrotCardBody>An article card</CarrotCardBody>
</CarrotCard>

<!-- Horizontal layout with media -->
<CarrotCard horizontal>
  <CarrotCardMedia>
    <img src="/cover.jpg" alt="Album cover" />
  </CarrotCardMedia>
  <CarrotCardBody>Side-by-side content</CarrotCardBody>
</CarrotCard>

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

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

CarrotCardHeader

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

vue
<CarrotCardHeader>
  <h3>Card Title</h3>
  <CarrotButton variant="ghost" icon><MoreIcon /></CarrotButton>
</CarrotCardHeader>

CarrotCardBody

Flex-grow scrollable content area.

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

CarrotCardFooter

Flex row section with top border and subtle background.

vue
<CarrotCardFooter>
  <CarrotButton variant="ghost">Cancel</CarrotButton>
  <CarrotButton>Save</CarrotButton>
</CarrotCardFooter>

CarrotCardMedia

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

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

Props

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

Dependencies

PackagePurpose
@carrot/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 { CarrotCard, CarrotCardHeader, CarrotCardBody, CarrotCardFooter, CarrotCardMedia } from "@carrot/design-vue-cards";
import "@carrot/design-components-cards";

CarrotCard

Standard content card

vue
<script setup lang="ts">
import { CarrotCard, CarrotCardHeader, CarrotCardBody, CarrotCardFooter } from "@carrot/design-vue-cards";
import { CarrotButton } from "@carrot/design-vue-buttons";
</script>

<template>
  <CarrotCard variant="elevated">
    <CarrotCardHeader>
      <h3>Track Details</h3>
      <CarrotButton variant="ghost" icon aria-label="More options">
        <MoreIcon />
      </CarrotButton>
    </CarrotCardHeader>
    <CarrotCardBody>
      <p>Beat Hotel — Dub Techno Mix</p>
      <p>Duration: 6:42</p>
    </CarrotCardBody>
    <CarrotCardFooter>
      <CarrotButton variant="ghost">Cancel</CarrotButton>
      <CarrotButton>Add to Queue</CarrotButton>
    </CarrotCardFooter>
  </CarrotCard>
</template>

Media card — horizontal layout

vue
<script setup lang="ts">
import { CarrotCard, CarrotCardMedia, CarrotCardBody } from "@carrot/design-vue-cards";
import { CarrotImage } from "@carrot/design-vue-images";

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

<template>
  <CarrotCard horizontal variant="outlined">
    <CarrotCardMedia>
      <CarrotImage :src="artwork" :thumb-src="artworkThumb" :alt="`${title} artwork`" />
    </CarrotCardMedia>
    <CarrotCardBody>
      <h4>{{ title }}</h4>
      <p>{{ artist }}</p>
    </CarrotCardBody>
  </CarrotCard>
</template>
vue
<script setup lang="ts">
import { CarrotCard, CarrotCardBody } from "@carrot/design-vue-cards";

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

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

Common Patterns

Card grid

vue
<script setup lang="ts">
import { CarrotCard, CarrotCardMedia, CarrotCardBody } from "@carrot/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">
    <CarrotCard v-for="item in items" :key="item.id" :href="item.href">
      <CarrotCardMedia position="top">
        <img :src="item.image" :alt="item.title" />
      </CarrotCardMedia>
      <CarrotCardBody>{{ item.title }}</CarrotCardBody>
    </CarrotCard>
  </div>
</template>

Interactive card with click handler

vue
<script setup lang="ts">
import { CarrotCard, CarrotCardBody } from "@carrot/design-vue-cards";

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

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

Carrot