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 |
Notifications
You have 3 unread messages.
Push Notifications
Send notifications to device.
Your call has been confirmed.
1 hour ago
You have a new message!
1 hour ago
Your subscription is expiring soon!
2 hours ago
<script setup lang="ts">
const notifications = [
{
title: 'Your call has been confirmed.',
description: '1 hour ago',
},
{
title: 'You have a new message!',
description: '1 hour ago',
},
{
title: 'Your subscription is expiring soon!',
description: '2 hours ago',
},
]
</script>
<template>
<div class="grid w-full place-items-center">
<NCard
title="Notifications"
description="You have 3 unread messages."
class="max-w-380px"
:_card-content="{
class: 'grid gap-4',
}"
>
<div class="flex items-center border rounded-md p-4 space-x-4">
<NIcon name="i-lucide-bell" square="6" />
<div class="flex-1 space-y-1">
<p class="text-sm font-medium leading-none">
Push Notifications
</p>
<p class="text-muted-foreground text-sm">
Send notifications to device.
</p>
</div>
<NSwitch />
</div>
<div>
<div
v-for="(notification, index) in notifications" :key="index"
class="grid grid-cols-[25px_minmax(0,1fr)] mb-4 items-start pb-4 last:mb-0 last:pb-0"
>
<span class="h-2 w-2 flex translate-y-1 rounded-full bg-sky-500" />
<div class="space-y-1">
<p class="text-sm font-medium leading-none">
{{ notification.title }}
</p>
<p class="text-muted-foreground text-sm">
{{ notification.description }}
</p>
</div>
</div>
</div>
<template #footer>
<div class="mt-2 w-full flex justify-end gap-4">
<NButton
leading="i-lucide-check"
label="Mark all as read"
class="w-full"
/>
</div>
</template>
</NCard>
</div>
</template>
Variant
Prop | Default | Type | Description |
---|---|---|---|
card | outline | {variant} | Controls the visual style of the card. |
Variant | Description |
---|---|
outline | Adds a subtle border while maintaining a clean background. |
soft | Applies a light background color with matching border. |
~ | Removes all variant styling, keeping only core card structure. |
Outline variant
A simple outline variant card with 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="Outline variant"
description="A simple outline variant card with 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',
}"
>
<div class="flex items-center justify-between">
<span class="text-3xl font-bold">$0</span>
<span class="text-sm text-muted">/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" />
</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',
}"
>
<div class="flex items-center justify-between">
<span class="text-3xl font-bold">$29</span>
<span class="text-sm text-accent">/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" />
</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',
}"
>
<div class="flex items-center justify-between">
<span class="text-3xl font-bold">$99</span>
<span class="text-sm text-muted">/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" />
</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. |
John Doe
@johndoe
Full-stack developer passionate about building beautiful user interfaces and scalable applications. Always learning and sharing knowledge with the community.
<template>
<div
class="grid w-full place-items-center"
>
<NCard
class="max-w-380px overflow-hidden"
:_card-header="{
class: 'p-0',
}"
:_card-content="{
class: 'mt-4',
}"
>
<template #header>
<div class="relative">
<img
src="https://images.unsplash.com/photo-1700527736181-67795948c0b1?q=80&w=2352&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="Card image"
class="h-56 w-full object-cover"
>
<div class="absolute inset-0 from-black/60 to-transparent bg-gradient-to-t" />
<div class="absolute bottom-0 left-0 p-4">
<div class="flex items-center gap-2">
<NAvatar
src="https://i.pravatar.cc/300"
alt="John Doe"
size="sm"
class="ring-2 ring-white"
/>
<div class="text-white">
<p class="font-medium leading-none">
John Doe
</p>
<p class="text-sm text-white/80">
@johndoe
</p>
</div>
</div>
</div>
</div>
</template>
<!-- content -->
<div class="flex flex-col gap-4">
<div class="flex items-center gap-4 text-sm text-muted">
<div class="flex items-center gap-1">
<NIcon name="i-lucide-users" class="text-primary" />
<span>2.4k followers</span>
</div>
<div class="flex items-center gap-1">
<NIcon name="i-lucide-map-pin" class="text-error" />
<span>San Francisco, CA</span>
</div>
</div>
<p class="text-sm">
Full-stack developer passionate about building beautiful user interfaces and scalable applications. Always learning and sharing knowledge with the community.
</p>
</div>
<template #footer>
<div class="w-full flex flex-col">
<div class="mt-2 w-full flex items-center justify-between gap-4">
<NButton
btn="solid"
leading="i-lucide-user-plus"
label="Follow"
class="flex-1"
/>
<NButton
btn="outline-gray"
leading="i-lucide-mail"
label="Message"
class="flex-1"
/>
</div>
</div>
</template>
</NCard>
</div>
</template>
Presets
type CardPrefix = 'card'
export const staticCard: Record<`${CardPrefix}-${string}` | CardPrefix, string> = {
// base
'card': 'rounded-xl shadow text-base',
// components
'card-header': 'flex flex-col gap-y-1.5 p-6',
'card-title': 'font-semibold leading-none tracking-tight',
'card-description': 'text-sm text-muted',
'card-content': 'p-6 pt-0',
'card-footer': 'flex items-center p-6 pt-0',
// static variants
'card-soft-gray': 'bg-muted border border-base',
'card-outline-gray': 'bg-base border border-base',
}
export const dynamicCard = [
[/^card-soft(-(\S+))?$/, ([, , c = 'gray']) => `bg-${c}-50 dark:bg-${c}-900 border-${c}-200 dark:border-${c}-700/58`],
[/^card-outline(-(\S+))?$/, ([, , c = 'gray']) => `border border-${c}-200 dark:border-${c}-700/58`],
]
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>
/**
* `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 NCardUnaProps {
cardDefaultVariant?: HTMLAttributes['class']
cardTitle?: HTMLAttributes['class']
cardDescription?: HTMLAttributes['class']
cardContent?: HTMLAttributes['class']
cardHeader?: HTMLAttributes['class']
cardFooter?: HTMLAttributes['class']
}
Components
<script setup lang="ts">
import type { NCardProps } from '../../../types/card'
import { computed } from 'vue'
import { cn } from '../../../utils'
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: 'outline-gray',
})
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<div
v-bind="{ ...$attrs, delegatedProps }"
:card="card"
:class="cn(
'card',
props.class,
)"
>
<slot name="root">
<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>
</slot>
</CardHeader>
<CardContent
v-if="$slots.default"
v-bind="delegatedProps._cardContent"
:una
>
<slot />
</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
: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
: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
: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
: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
:class="cn(
'card-footer',
props.una?.cardFooter,
props.class,
)"
>
<slot />
</div>
</template>