Examples
Basic
| Prop | Default | Type | Description |
|---|---|---|---|
title | - | string | Sets the main heading text displayed in the card header |
description | - | string | Provides secondary text shown below the title |
Login to your account
Enter your email below to login to your account
<template>
<div class="grid place-items-center">
<NCard class="max-w-sm w-full">
<NCardHeader>
<NCardTitle>Login to your account</NCardTitle>
<NCardDescription>
Enter your email below to login to your account
</NCardDescription>
</NCardHeader>
<NCardContent>
<form>
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<NLabel html-for="email">
Email
</NLabel>
<NInput
id="email"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<NLabel html-for="password">
Password
</NLabel>
<a
href="#"
class="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<NInput id="password" type="password" required />
</div>
</div>
</form>
</NCardContent>
<NCardFooter class="flex-col gap-2">
<NButton type="submit" class="w-full">
Login
</NButton>
<NButton btn="outline-gray" class="w-full">
Login with Google
</NButton>
<div class="mt-4 text-center text-sm">
Don't have an account?
<a href="#" class="underline underline-offset-4">
Sign up
</a>
</div>
</NCardFooter>
</NCard>
</div>
</template>
Variant
| Prop | Default | Type | Description |
|---|---|---|---|
card | solid | {variant} | Controls the visual style of the card. |
| Variant | Description |
|---|---|
solid | Adds a border while maintaining a solid background. |
outline | Adds a subtle border while maintaining a clean background. |
soft | Applies a light background color without a border. |
~ | Removes all variant styling, keeping only core card structure. |
Solid variant
A simple solid variant card with a border. This is the default variant if none is specified.
Outline variant
A simple outline variant card without a border. This is the default variant if none is specified.
Soft variant
A soft variant card with a subtle background color and border.
Base variant
A base variant card without any predefined styles except for the base card styles.
<template>
<div class="flex flex-col gap-4">
<NCard
title="Solid variant"
description="A simple solid variant card with a border. This is the default variant if none is specified."
card="solid"
/>
<NCard
title="Outline variant"
description="A simple outline variant card without a border. This is the default variant if none is specified."
card="outline"
/>
<NCard
title="Soft variant"
description="A soft variant card with a subtle background color and border."
card="soft"
/>
<NCard
title="Base variant"
description="A base variant card without any predefined styles except for the base card styles."
card="~"
/>
</div>
</template>
Color
| Prop | Default | Type | Description |
|---|---|---|---|
card | {variant}-primary | {variant}-{color} | Combines variant and color to define the card's appearance (e.g. soft-blue) |
The color variant only affects the card's background and border colors. Other elements like text and icons maintain their default styling.
Free Plan
Perfect for getting started
- Up to 3 projects
- Community support
- Basic analytics
Pro Plan
Best for professionals
- Unlimited projects
- Priority support
- Advanced analytics
- Custom domains
Enterprise
For large organizations
- Everything in Pro
- 24/7 Support
- SLA guarantee
- Custom integration
<template>
<div class="mx-auto max-w-4xl w-full flex flex-col gap-4 md:flex-row">
<!-- Free Plan Card -->
<NCard
title="Free Plan"
description="Perfect for getting started"
card="outline-gray"
class="flex-1"
:una="{
cardContent: 'space-y-4',
cardDescription: 'text-accent-foreground',
}"
>
<template #content>
<div class="flex items-center justify-between">
<span class="text-3xl font-bold">$0</span>
<span class="text-sm text-muted-foreground">/month</span>
</div>
<ul class="space-y-2">
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Up to 3 projects</span>
</li>
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Community support</span>
</li>
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Basic analytics</span>
</li>
</ul>
<NButton label="Get Started" btn="solid-black" class="w-full" />
</template>
</NCard>
<!-- Pro Plan Card -->
<NCard
title="Pro Plan"
description="Best for professionals"
card="soft-primary"
class="flex-1 scale-105"
:una="{
cardContent: 'space-y-4',
cardDescription: 'text-accent-foreground',
}"
>
<template #content>
<div class="flex items-center justify-between">
<span class="text-3xl font-bold">$29</span>
<span class="text-sm text-accent-foreground">/month</span>
</div>
<ul class="space-y-2">
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Unlimited projects</span>
</li>
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Priority support</span>
</li>
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Advanced analytics</span>
</li>
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Custom domains</span>
</li>
</ul>
<NButton label="Get Started" btn="solid-primary" class="w-full" />
</template>
</NCard>
<!-- Enterprise Plan Card -->
<NCard
title="Enterprise"
description="For large organizations"
card="outline-gray"
class="flex-1"
:una="{
cardContent: 'space-y-4',
cardDescription: 'text-accent-foreground',
}"
>
<template #content>
<div class="flex items-center justify-between">
<span class="text-3xl font-bold">$99</span>
<span class="text-sm text-muted-foreground">/month</span>
</div>
<ul class="space-y-2">
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Everything in Pro</span>
</li>
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>24/7 Support</span>
</li>
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>SLA guarantee</span>
</li>
<li class="flex items-center gap-2">
<NIcon name="i-lucide-check" class="text-success" />
<span>Custom integration</span>
</li>
</ul>
<NButton label="Contact Sales" btn="solid-black" class="w-full" />
</template>
</NCard>
</div>
</template>
Slots
| Name | Props | Description |
|---|---|---|
header | - | The header section of the card, typically containing the title and description. |
default | - | The main content area of the card. |
title | - | Custom title content that overrides the title prop. |
description | - | Custom description content that overrides the description prop. |
footer | - | The footer section of the card, typically for actions or additional information. |
Login to your account
Enter your email below to login to your account
Meeting Notes
Transcript from the meeting with the client.
Client requested dashboard redesign with focus on mobile responsiveness.
- New analytics widgets for daily/weekly metrics
- Simplified navigation menu
- Dark mode support
- Timeline: 6 weeks
- Follow-up meeting scheduled for next Tuesday
Is this an image?
This is a card with an image.
Header Only
This is a card with a header and a description.
Header and Content
This is a card with a header and a content.
Header + Footer
This is a card with a header and a footer.
Header + Footer
This is a card with a header and a footer.
<template>
<div class="flex flex-col items-start gap-4">
<NCard class="max-w-sm w-full">
<NCardHeader>
<NCardTitle>Login to your account</NCardTitle>
<NCardDescription>
Enter your email below to login to your account
</NCardDescription>
</NCardHeader>
<NCardContent>
<form>
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<NLabel html-for="email">
Email
</NLabel>
<NInput
id="email"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<NLabel html-for="password">
Password
</NLabel>
<a
href="#"
class="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<NInput id="password" type="password" required />
</div>
</div>
</form>
</NCardContent>
<NCardFooter class="flex-col gap-2">
<NButton type="submit" class="w-full">
Login
</NButton>
<NButton btn="outline-gray" class="w-full">
Login with Google
</NButton>
<div class="mt-4 text-center text-sm">
Don't have an account?
<a href="#" class="underline underline-offset-4">
Sign up
</a>
</div>
</NCardFooter>
</NCard>
<NCard>
<NCardHeader>
<NCardTitle>Meeting Notes</NCardTitle>
<NCardDescription>
Transcript from the meeting with the client.
</NCardDescription>
</NCardHeader>
<NCardContent class="text-sm">
<p>
Client requested dashboard redesign with focus on mobile
responsiveness.
</p>
<ol class="mt-4 flex flex-col list-decimal gap-2 pl-6">
<li>New analytics widgets for daily/weekly metrics</li>
<li>Simplified navigation menu</li>
<li>Dark mode support</li>
<li>Timeline: 6 weeks</li>
<li>Follow-up meeting scheduled for next Tuesday</li>
</ol>
</NCardContent>
<NCardFooter>
<div class="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
<NAvatar>
<NAvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<NAvatarFallback>CN</NAvatarFallback>
</NAvatar>
<NAvatar>
<NAvatarImage src="https://github.com/leerob.png" alt="@leerob" />
<NAvatarFallback>LR</NAvatarFallback>
</NAvatar>
<NAvatar>
<NAvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<NAvatarFallback>ER</NAvatarFallback>
</NAvatar>
</div>
</NCardFooter>
</NCard>
<NCard>
<NCardHeader>
<NCardTitle>Is this an image?</NCardTitle>
<NCardDescription>This is a card with an image.</NCardDescription>
</NCardHeader>
<NCardContent class="px-0">
<img
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
alt="Photo by Drew Beamer"
class="aspect-video object-cover"
width="500"
height="500"
>
</NCardContent>
<NCardFooter class="flex items-center gap-2">
<NBadge badge="outline-gray">
<NIcon name="i-lucide-bed" /> 4
</NBadge>
<NBadge badge="outline-gray">
<NIcon name="i-lucide-bath" /> 2
</NBadge>
<NBadge badge="outline-gray">
<NIcon name="i-lucide-land-plot" /> 350m²
</NBadge>
<div class="ml-auto font-medium tabular-nums">
$135,000
</div>
</NCardFooter>
</NCard>
<div class="w-full flex flex-wrap items-start gap-8 md:*:data-[slot=card]:basis-1/4">
<NCard>
<NCardContent class="text-sm">
Content Only
</NCardContent>
</NCard>
<NCard>
<NCardHeader>
<NCardTitle>Header Only</NCardTitle>
<NCardDescription>
This is a card with a header and a description.
</NCardDescription>
</NCardHeader>
</NCard>
<NCard>
<NCardHeader>
<NCardTitle>Header and Content</NCardTitle>
<NCardDescription>
This is a card with a header and a content.
</NCardDescription>
</NCardHeader>
<NCardContent class="text-sm">
Content
</NCardContent>
</NCard>
<NCard>
<NCardFooter class="text-sm">
Footer Only
</NCardFooter>
</NCard>
<NCard>
<NCardHeader>
<NCardTitle>Header + Footer</NCardTitle>
<NCardDescription>
This is a card with a header and a footer.
</NCardDescription>
</NCardHeader>
<NCardFooter class="text-sm">
Footer
</NCardFooter>
</NCard>
<NCard>
<NCardContent class="text-sm">
Content
</NCardContent>
<NCardFooter class="text-sm">
Footer
</NCardFooter>
</NCard>
<NCard>
<NCardHeader>
<NCardTitle>Header + Footer</NCardTitle>
<NCardDescription>
This is a card with a header and a footer.
</NCardDescription>
</NCardHeader>
<NCardContent class="text-sm">
Content
</NCardContent>
<NCardFooter class="text-sm">
Footer
</NCardFooter>
</NCard>
</div>
</div>
</template>
Presets
type CardPrefix = 'card'
export const staticCard: Record<`${CardPrefix}-${string}` | CardPrefix, string> = {
// base
'card': 'text-card-foreground flex flex-col gap-6 rounded-xl py-6 shadow-sm',
// components
'card-header': '[@container/card-header]:grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-[[data-slot=card-action]]:grid-cols-[1fr_auto] [&.border-b]:pb-6',
'card-title': 'leading-none font-semibold',
'card-description': 'text-muted-foreground text-sm',
'card-content': 'px-6',
'card-footer': 'flex items-center px-6 [&.border-t]:pt-6',
'card-action': 'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
// static variants
'card-solid-gray': 'bg-card border',
'card-solid': 'card-solid-gray',
'card-soft-gray': 'bg-card',
'card-soft': 'card-soft-gray',
'card-outline-gray': 'bg-background border',
'card-outline': 'card-outline-gray',
}
export const dynamicCard = [
[/^card-solid(-(\S+))?$/, ([, , c = 'gray']) => `border bg-background dark:bg-${c}-900 border-${c}-200 dark:border-${c}-800`],
[/^card-soft(-(\S+))?$/, ([, , c = 'gray']) => `bg-${c}-50 dark:bg-${c}-900`],
[/^card-outline(-(\S+))?$/, ([, , c = 'gray']) => `border bg-background dark:bg-${c}-900 border-${c}-200 dark:border-${c}-800`],
]
export const card = [
...dynamicCard,
staticCard,
]
Props
import type { HTMLAttributes } from 'vue'
interface BaseExtensions {
class?: HTMLAttributes['class']
}
export interface NCardProps extends BaseExtensions {
/**
* Allows you to add `UnaUI` card preset properties,
* Think of it as a shortcut for adding options or variants to the preset if available.
*
* @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/card.ts
* @example
* card="outline-green"
*/
card?: string
/**
* Add a title to the card.
*/
title?: string
/**
* Add a description to the card.
*/
description?: string
// sub-components
_cardContent?: Partial<NCardContentProps>
_cardTitle?: Partial<NCardTitleProps>
_cardDescription?: Partial<NCardDescriptionProps>
_cardHeader?: Partial<NCardHeaderProps>
_cardFooter?: Partial<NCardFooterProps>
_cardAction?: Partial<NCardActionProps>
/**
* `UnaUI` preset configuration
*
* @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/card.ts
*/
una?: NCardUnaProps
}
export interface NCardContentProps extends BaseExtensions {
una?: Pick<NCardUnaProps, 'cardContent'>
}
export interface NCardTitleProps extends BaseExtensions {
una?: Pick<NCardUnaProps, 'cardTitle'>
}
export interface NCardDescriptionProps extends BaseExtensions {
una?: Pick<NCardUnaProps, 'cardDescription'>
}
export interface NCardHeaderProps extends BaseExtensions {
una?: Pick<NCardUnaProps, 'cardHeader'>
}
export interface NCardFooterProps extends BaseExtensions {
una?: Pick<NCardUnaProps, 'cardFooter'>
}
export interface NCardActionProps extends BaseExtensions {
una?: Pick<NCardUnaProps, 'cardAction'>
}
export interface NCardUnaProps {
cardDefaultVariant?: HTMLAttributes['class']
cardTitle?: HTMLAttributes['class']
cardDescription?: HTMLAttributes['class']
cardContent?: HTMLAttributes['class']
cardHeader?: HTMLAttributes['class']
cardFooter?: HTMLAttributes['class']
cardAction?: HTMLAttributes['class']
}
Components
<script setup lang="ts">
import type { NCardProps } from '../../../types/card'
import { reactiveOmit } from '@vueuse/core'
import { cn } from '../../../utils'
import CardAction from './CardAction.vue'
import CardContent from './CardContent.vue'
import CardDescription from './CardDescription.vue'
import CardFooter from './CardFooter.vue'
import CardHeader from './CardHeader.vue'
import CardTitle from './CardTitle.vue'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<NCardProps>(), {
card: 'solid-gray',
})
const delegatedProps = reactiveOmit(props, ['class'])
</script>
<template>
<div
data-slot="card"
v-bind="{ ...$attrs, delegatedProps }"
:card="card"
:class="cn(
'card',
props.class,
)"
>
<slot>
<CardHeader
v-if="$slots.header || $slots.title || $slots.description || title || description"
v-bind="delegatedProps._cardHeader"
:una
>
<slot name="header">
<CardTitle
v-if="$slots.title || title"
v-bind="delegatedProps._cardTitle"
:una
>
<slot name="title">
{{ title }}
</slot>
</CardTitle>
<CardDescription
v-if="$slots.description || description"
v-bind="delegatedProps._cardDescription"
:una
>
<slot name="description">
{{ description }}
</slot>
</CardDescription>
<CardAction
v-if="$slots.action"
v-bind="delegatedProps._cardAction"
:una
>
<slot name="action" />
</CardAction>
</slot>
</CardHeader>
<CardContent
v-if="$slots.content"
v-bind="delegatedProps._cardContent"
:una
>
<slot name="content" />
</CardContent>
<CardFooter
v-if="$slots.footer"
v-bind="delegatedProps._cardFooter"
:una
>
<slot name="footer" />
</CardFooter>
</slot>
</div>
</template>
<script setup lang="ts">
import type { NCardContentProps } from '../../../types'
import { cn } from '../../../utils'
const props = defineProps<NCardContentProps>()
</script>
<template>
<div
data-slot="card-content"
:class="cn(
'card-content',
props.una?.cardContent,
props.class,
)"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { NCardTitleProps } from '../../../types'
import { cn } from '../../../utils'
const props = defineProps<NCardTitleProps>()
</script>
<template>
<h3
data-slot="card-title"
:class="
cn(
'card-title',
props.una?.cardTitle,
props.class,
)
"
>
<slot />
</h3>
</template>
<script setup lang="ts">
import type { NCardDescriptionProps } from '../../../types'
import { cn } from '../../../utils'
const props = defineProps<NCardDescriptionProps>()
</script>
<template>
<p
data-slot="card-description"
:class="cn(
'card-description',
props.una?.cardDescription,
props.class,
)"
>
<slot />
</p>
</template>
<script setup lang="ts">
import type { NCardHeaderProps } from '../../../types'
import { cn } from '../../../utils'
const props = defineProps<NCardHeaderProps>()
</script>
<template>
<div
data-slot="card-header"
:class="cn(
'card-header',
props.una?.cardHeader,
props.class,
)"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { NCardFooterProps } from '../../../types'
import { cn } from '../../../utils'
const props = defineProps<NCardFooterProps>()
</script>
<template>
<div
data-slot="card-footer"
:class="cn(
'card-footer',
props.una?.cardFooter,
props.class,
)"
>
<slot />
</div>
</template>