Card

Displays a card with header, content, and footer.

Examples

Basic

PropDefaultTypeDescription
title-stringSets the main heading text displayed in the card header
description-stringProvides secondary text shown below the title
Preview
Code

Login to your account

Enter your email below to login to your account

Variant

PropDefaultTypeDescription
cardsolid{variant}Controls the visual style of the card.
VariantDescription
solidAdds a border while maintaining a solid background.
outlineAdds a subtle border while maintaining a clean background.
softApplies a light background color without a border.
~Removes all variant styling, keeping only core card structure.
Preview
Code

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.

Color

PropDefaultTypeDescription
card{variant}-primary{variant}-{color}Combines variant and color to define the card's appearance (e.g. soft-blue)
Preview
Code

Free Plan

Perfect for getting started

$0/month
  • Up to 3 projects
  • Community support
  • Basic analytics

Pro Plan

Best for professionals

$29/month
  • Unlimited projects
  • Priority support
  • Advanced analytics
  • Custom domains

Enterprise

For large organizations

$99/month
  • Everything in Pro
  • 24/7 Support
  • SLA guarantee
  • Custom integration

Slots

NamePropsDescription
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.
Preview
Code

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.

  1. New analytics widgets for daily/weekly metrics
  2. Simplified navigation menu
  3. Dark mode support
  4. Timeline: 6 weeks
  5. Follow-up meeting scheduled for next Tuesday

Is this an image?

This is a card with an image.

Photo by Drew Beamer
Content Only

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.

Content

Header + Footer

This is a card with a header and a footer.

Content

Header + Footer

This is a card with a header and a footer.

Content

Presets

shortcuts/card.ts
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

types/card.ts
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

Card.vue
CardContent.vue
CardTitle.vue
CardDescription.vue
CardHeader.vue
CardFooter.vue
<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>