Card
VCard groups content about one subject: media, title, body and actions. With href, the whole card becomes a link.
Usage
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.
A border on the page surface.
A raised surface with a shadow.
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.
<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>The media slot is drawn edge to edge above the content. Give images an alt text, or alt="" when they are decorative.
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.
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.
A cliff path above the sea.
A shaded walk under old oaks.
A short climb to a viewpoint facing west.
<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.
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.
<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
Slots
CSS variables