Keyboard shortcut: Ctrl + K
Get started

Card

VCard groups content about one subject: media, title, body and actions. With href, the whole card becomes a link.

Usage

vue

Coastal trail

12 km · 4 hours

A cliff path above the sea, with two beaches to stop at and a lighthouse at the end.
<script setup lang="ts">
import { VButton, VCard } from 'vectis-ui'
</script>

<template>
  <VCard title="Coastal trail" subtitle="12 km · 4 hours" class="card">
    A cliff path above the sea, with two beaches to stop at and a lighthouse at the end.
    <template #footer>
      <VButton size="sm">Book a guide</VButton>
    </template>
  </VCard>
</template>

<style scoped>
.card {
  max-inline-size: 22rem;
}
</style>

Examples

Variants

variant selects outline, elevated or filled.

vue

Outline

A border on the page surface.

Elevated

A raised surface with a shadow.

Filled

A muted surface with no border.
<script setup lang="ts">
import { VCard } from 'vectis-ui'
</script>

<template>
  <div class="grid">
    <VCard variant="outline" title="Outline">A border on the page surface.</VCard>
    <VCard variant="elevated" title="Elevated">A raised surface with a shadow.</VCard>
    <VCard variant="filled" title="Filled">A muted surface with no border.</VCard>
  </div>
</template>

<style scoped>
.grid {
  display: grid;
  flex: 1;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--vectis-space-4);
}
</style>

Sizes

size sets the padding and the gaps between parts.

vue

Small

Dense lists

Less padding.

Medium

The default

Standard padding.

Large

Featured content

More padding.
<script setup lang="ts">
import { VCard } from 'vectis-ui'
</script>

<template>
  <div class="grid">
    <VCard size="sm" title="Small" subtitle="Dense lists">Less padding.</VCard>
    <VCard size="md" title="Medium" subtitle="The default">Standard padding.</VCard>
    <VCard size="lg" title="Large" subtitle="Featured content">More padding.</VCard>
  </div>
</template>

<style scoped>
.grid {
  display: grid;
  flex: 1;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  align-items: start;
  gap: var(--vectis-space-4);
}
</style>

Media

The media slot is drawn edge to edge above the content. Give images an alt text, or alt="" when they are decorative.

vue

Forest loop

6 km · 2 hours

A shaded walk under old oaks, suitable for children.
<script setup lang="ts">
import { VCard } from 'vectis-ui'
</script>

<template>
  <VCard title="Forest loop" subtitle="6 km · 2 hours" class="card">
    <template #media>
      <div class="cover" />
    </template>
    A shaded walk under old oaks, suitable for children.
  </VCard>
</template>

<style scoped>
.card {
  max-inline-size: 22rem;
}

.cover {
  aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, oklch(0.55 0.12 150), oklch(0.35 0.08 170));
}
</style>

Horizontal

orientation="horizontal" places the media at the start. In a narrow card, the media moves back above the content.

vue

Coastal trail

12 km · 4 hours

A cliff path above the sea, with two beaches to stop at and a lighthouse at the end.
<script setup lang="ts">
import { VButton, VCard } from 'vectis-ui'
</script>

<template>
  <VCard orientation="horizontal" title="Coastal trail" subtitle="12 km · 4 hours" class="card">
    <template #media>
      <div class="cover" />
    </template>
    A cliff path above the sea, with two beaches to stop at and a lighthouse at the end.
    <template #footer>
      <VButton size="sm">Book a guide</VButton>
    </template>
  </VCard>
</template>

<style scoped>
.card {
  flex: 1;
}

.cover {
  min-block-size: 8rem;
  background: linear-gradient(135deg, oklch(0.6 0.12 230), oklch(0.4 0.1 250));
}
</style>

Linked cards

With href, the title is a link whose clickable area covers the card. Buttons inside the card stay separate targets. Attributes other than class and style go to the link. headingLevel renders the title as a heading.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VCard } from 'vectis-ui'

const trails = [
  { id: 'coast', title: 'Coastal trail', text: 'A cliff path above the sea.', hue: 230 },
  { id: 'forest', title: 'Forest loop', text: 'A shaded walk under old oaks.', hue: 150 },
  {
    id: 'ridge',
    title: 'Sunset ridge',
    text: 'A short climb to a viewpoint facing west.',
    hue: 40,
  },
]

const saved = ref<string[]>([])

function toggle(id: string) {
  saved.value = saved.value.includes(id)
    ? saved.value.filter((item) => item !== id)
    : [...saved.value, id]
}
</script>

<template>
  <ul class="grid">
    <VCard
      v-for="trail in trails"
      :key="trail.id"
      as="li"
      variant="elevated"
      :heading-level="3"
      :href="`#${trail.id}`"
      :title="trail.title"
    >
      <template #media>
        <div class="cover" :style="{ '--hue': trail.hue }" />
      </template>
      {{ trail.text }}
      <template #footer>
        <VButton
          size="sm"
          variant="outline"
          tone="neutral"
          :aria-pressed="saved.includes(trail.id)"
          @click="toggle(trail.id)"
        >
          Save
        </VButton>
      </template>
    </VCard>
  </ul>
</template>

<style scoped>
.grid {
  display: grid;
  flex: 1;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--vectis-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.cover {
  aspect-ratio: 4 / 3;
  background: linear-gradient(135deg, oklch(0.6 0.12 var(--hue)), oklch(0.4 0.1 var(--hue)));
}
</style>

Disabled

disabled removes the link’s destination and dims the title and media. Controls in the slots are not affected.

vue

Mountain pass

Closed for the winter

The pass reopens in May, when the snow has melted.
<script setup lang="ts">
import { VCard } from 'vectis-ui'
</script>

<template>
  <VCard
    href="#closed"
    disabled
    title="Mountain pass"
    subtitle="Closed for the winter"
    class="card"
  >
    The pass reopens in May, when the snow has melted.
  </VCard>
</template>

<style scoped>
.card {
  max-inline-size: 22rem;
}
</style>

Loading

loading shows placeholders and hides the footer. Set loadingText to announce the loading to screen readers.

vue
Loading the trail
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VCard } from 'vectis-ui'

const loading = ref(true)
</script>

<template>
  <div class="demo">
    <VButton variant="outline" tone="neutral" @click="loading = !loading">
      {{ loading ? 'Show content' : 'Show loading' }}
    </VButton>
    <VCard
      :loading="loading"
      loading-text="Loading the trail"
      title="Forest loop"
      subtitle="6 km · 2 hours"
      class="card"
    >
      <template #media>
        <div class="cover" />
      </template>
      A shaded walk under old oaks, suitable for children.
    </VCard>
  </div>
</template>

<style scoped>
.demo {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--vectis-space-3);
}

.card {
  inline-size: 22rem;
  max-inline-size: 100%;
}

.cover {
  aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, oklch(0.55 0.12 150), oklch(0.35 0.08 170));
}
</style>

API

Props

PropTypeDefault
variantCardVariant'outline' | 'elevated' | 'filled''outline'
Visual style.
orientationCardOrientation'vertical' | 'horizontal''vertical'
Places the media above the content or at its start.
sizeCardSize'sm' | 'md' | 'lg''md'
Padding and gap size.
titlestringnone
Title. With href, the text of the card’s link.
subtitlestringnone
Text below the title.
headingLevelCardHeadingLevel1 | 2 | 3 | 4 | 5 | 6none
Renders the title as a heading of this level. Otherwise, the title is a paragraph.
hrefstringnone
Makes the card a link. Requires title.
disabledbooleanfalse
Disables the card’s link and dims the title and media.
loadingbooleanfalse
Shows placeholders instead of the content and hides the footer.
loadingTextstringnone
Text announced while loading. Without it, the loading state is silent.
asstring'div'
Root element, such as article or li.

Slots

SlotType
default{}
Card body.
media{}
Image, video or illustration, drawn edge to edge.
header{}
Replaces the title and subtitle. A linked card loses its link.
footer{}
Actions, aligned at the bottom of the card.

CSS variables

TokenValue
--vectis-control-size-skeleton-surface6rem
--vectis-control-size-card-media12rem
--vectis-control-size-card-body-min16rem