Appearance
@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
| Prop | Type | Default | Description |
|---|---|---|---|
variant | CardVariant | "default" | default · elevated · outlined · ghost · gradient · gradient-subtle · gradient-elevated |
size | CardSize | "md" | sm · md · lg |
interactive | boolean | false | Hover/focus states, pointer cursor |
horizontal | boolean | false | Row layout with media capped at 40% |
seamless | boolean | false | Remove internal section borders |
flush | boolean | false | Remove all section padding |
as | string | Component | "div" | Root element (auto: "a" when href set) |
href | string | — | Link URL (auto-enables interactive + <a> tag) |
Accessibility
- Interactive non-link cards get
role="button"andtabindex="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
| Prop | Type | Default | Description |
|---|---|---|---|
position | "top" | "bottom" | — | Position hint within the card |
Dependencies
| Package | Purpose |
|---|---|
@carrot/design-components-cards | CSS tokens + generated styles |
Build
bash
npm run buildBuilt 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>Navigable link card
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>