A flexible and customizable Vue 3 card component for showcasing content with title, image, description, and action buttons. Perfect for Single Page Apps and Server-Side Rendered (SSR) environments like Nuxt 4.
- Features
- Installation
- Style usage
- Quick Start (SPA)
- Nuxt 3 / SSR Usage
- Component Registration Options
- Props
- Events
- Customization (Styles / Theming)
- Examples
- Accessibility
- SSR Notes
- Development
- Contributing
- License
- Clean and modern card layout with image, title, and description
- Primary and secondary action buttons
- Label/tag support with customizable limit
- Fully customizable color scheme (background, text, button styles)
- Click events for buttons and labels
- Works seamlessly in SPA and SSR (Nuxt 3) contexts
- Built on top of
@todovue/tv-buttonand@todovue/tv-label - Tree-shake friendly (Vue marked external in library build)
- TypeScript support
Using npm:
npm install @todovue/tv-cardUsing yarn:
yarn add @todovue/tv-cardUsing pnpm:
pnpm add @todovue/tv-card// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import '@todovue/tv-card/style.css'
import '@todovue/tv-button/style.css'
import '@todovue/tv-style/style.css'
import { TvCard } from '@todovue/tv-card'
const app = createApp(App)
app.component('TvCard', TvCard)
app.mount('#app')// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@todovue/tv-card/nuxt'
]
})Global registration (main.js / main.ts):
import { createApp } from 'vue'
import App from './App.vue'
import { TvCard } from '@todovue/tv-card'
createApp(App)
.use(TvCard) // enables <TvCard /> globally
.mount('#app')Local import inside a component:
<script setup>
import { TvCard } from '@todovue/tv-card'
import { ref } from 'vue'
const configCard = ref({
title: 'Create Vue.js',
description: 'Vue.js (commonly known as Vue; pronounced /vju/...',
alt: 'Card Image',
image: 'https://todovue.com/vue.webp',
primaryButtonText: 'View more',
})
function handleButton() {
console.log('Button clicked')
}
</script>
<template>
<TvCard :configCard="configCard" @click-button="handleButton" />
</template>Create a plugin file: plugins/tv-card.client.ts (client-only is fine, or without suffix for SSR as it is safe):
import { defineNuxtPlugin } from '#app'
import { TvCard } from '@todovue/tv-card'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(TvCard)
})Use anywhere:
<template>
<TvCard :configCard="myConfig" @click-button="handleAction" />
</template>Optional direct import (no plugin):
<script setup>
import { TvCard } from '@todovue/tv-card'
</script>| Approach | When to use |
|---|---|
Global via app.use(TvCard) |
Many usages across app / design system install |
Local named import { TvCard } |
Isolated / code-split contexts |
Direct default import import { TvCard } from '@todovue/tv-card' |
Single usage or manual registration |
Plugin import { TvCardPlugin } |
Explicit plugin installation |
| Prop Name | Type | Required | Default | Description |
|---|---|---|---|---|
| configCard | object | Yes | - | Configuration object for the card (see below). |
| isHorizontal | boolean | No | false | If true, renders the card in horizontal layout. |
The component accepts a single prop configCard which is an object with the following structure:
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| title | string | Yes | - | Card title text. |
| description | string | Yes | - | Card description/content text. |
| image | string | Yes | - | URL of the card image. |
| alt | string | No | '' | Alt text for the image (accessibility). |
| primaryButtonText | string | Yes | - | Text for the primary action button. |
| secondaryButtonText | string | No | - | Text for the secondary action button (optional). |
| labels | array | No | - | Array of label objects {id, name, color}. |
| limitLabels | number | No | 3 | Maximum number of labels to display. |
| backgroundColor | string | No | - | Custom background color for the card. |
| color | string | No | - | Custom text color for the card. |
| backgroundButtonColor | string | No | - | Custom background color for primary button. |
| colorButton | string | No | - | Custom text color for primary button. |
| backgroundButtonSecondaryColor | string | No | - | Custom background color for secondary button. |
| colorButtonSecondary | string | No | - | Custom text color for secondary button. |
{
id: 1, // Unique identifier
name: 'JavaScript', // Label text
color: '#F7DF1E' // Label color (hex, rgb, etc.)
}| Event name (kebab) | Payload | Description |
|---|---|---|
click-button |
- | Emitted when primary button is clicked. |
click-secondary-button |
- | Emitted when secondary button is clicked. |
click-label |
label object | Emitted when a label is clicked, returns label. |
Usage:
<TvCard
:configCard="config"
@click-button="onPrimaryAction"
@click-secondary-button="onSecondaryAction"
@click-label="onLabelClick"
/>The card supports extensive customization through the configCard object:
<script setup>
import { ref } from 'vue'
import { TvCard } from '@todovue/tv-card'
const configCard = ref({
title: 'Custom Styled Card',
description: 'This card has custom colors applied',
image: 'https://example.com/image.jpg',
primaryButtonText: 'Action',
backgroundColor: '#46627f',
color: '#ffffff',
backgroundButtonColor: '#062131',
colorButton: '#ffffff'
})
</script>
<template>
<TvCard :configCard="configCard" />
</template><script setup>
import { ref } from 'vue'
import { TvCard } from '@todovue/tv-card'
const configCard = ref({
title: 'Vue.js Tutorial',
description: 'Learn Vue.js with these comprehensive guides',
image: 'https://todovue.com/vue.webp',
primaryButtonText: 'Start Learning',
labels: [
{ id: 1, name: 'JavaScript', color: '#F7DF1E' },
{ id: 2, name: 'HTML', color: '#E34F26' },
{ id: 3, name: 'CSS', color: '#1572B6' }
],
limitLabels: 2 // Only show 2 labels
})
</script>
<template>
<TvCard :configCard="configCard" @click-label="handleLabelClick" />
</template><script setup>
import { ref } from 'vue'
import { TvCard } from '@todovue/tv-card'
const configCard = ref({
title: 'Advanced Vue Tutorial',
description: 'Deep dive into Vue.js advanced concepts',
image: 'https://todovue.com/vuejs.webp',
primaryButtonText: 'Read Article',
secondaryButtonText: 'View Source',
backgroundButtonColor: '#062131',
colorButton: '#ffffff',
backgroundButtonSecondaryColor: '#0eb096',
colorButtonSecondary: '#000000'
})
function handlePrimary() {
console.log('Primary action')
}
function handleSecondary() {
console.log('Secondary action')
}
</script>
<template>
<TvCard
:configCard="configCard"
@click-button="handlePrimary"
@click-secondary-button="handleSecondary"
/>
</template>Check out the demo files in src/utils/demos/ for more examples:
default.vue- Basic card usagewithCustomColors.vue- Full customization examplewithLabels.vue- Card with labelswithMultipleLabels.vue- Card with label limitwithTwoButtons.vue- Card with primary and secondary buttons
- Always provide
alttext for images for screen readers. - Button text should be descriptive of the action.
- Label clicks are keyboard accessible through the underlying
TvLabelcomponent. - Color contrast should be considered when using custom colors.
- No direct DOM (
window/document) access in source → safe for SSR. - Styles are automatically applied when you import the library.
- The component works seamlessly with Nuxt 3's server-side rendering.
- Dependencies (
@todovue/tv-buttonand@todovue/tv-label) are SSR-compatible. - Ensure you import
@todovue/tv-card/style.cssin an SSR-compatible entry if needed.
git clone https://github.com/TODOvue/tv-card.git
cd tv-card
npm install
npm run dev # run demo playground
npm run build # build libraryLocal demo served from Vite using index.html + src/demo examples.
PRs and issues welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
MIT © TODOvue
Crafted for the TODOvue component ecosystem
