Card

Displays a card with header, content, and footer.

Examples

Basic

PropDefaultTypeDescription
title-stringThe card title.
description-stringThe card description.
Preview
Code

Create account now

Fill in the form below to create an account.

Variant

PropDefaultTypeDescription
cardoutline{variant}The variant of the card.
VariantDescription
outlineThe default variant.
softThe soft variant.
~The unstyle or base variant
Preview
Code

Outline variant

Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum.

Soft variant

Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum.

Base variant

Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum.

Color

PropDefaultTypeDescription
card{variant}-primary{variant}-{color}The color of the card.
Preview
Code

outline-purple

Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum.

soft-info

Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum.

Slots

NamePropsDescription
header-The header slot.
default-The default slot.
title-The title slot.
description-The description slot.
footer-The footer slot.
Preview
Code
Card image

Advanced Card

This example shows how you can use named templates for customization

NewBeta

John Doe
San Francisco, CA
Joined on 2021-01-01

Presets

shortcuts/card.ts
type CardPrefix = 'card'

export const staticCard: Record<`${CardPrefix}-${string}` | CardPrefix, string> = {
  // base
  'card': 'relative flex flex-col rounded-lg overflow-hidden shadow-sm',
  'card-default-variant': 'card-outline-gray',

  // components
  'card-header': '',
  'card-title': 'text-2xl font-semibold leading-none tracking-tight',
  'card-description': 'text-sm text-muted',
  'card-content': 'px-4 py-5 pt-0 sm:p-6 sm:pt-0',
  'card-footer': 'flex items-center p-6 pt-0',
  'card-about': 'flex flex-col gap-y-1.5 px-4 py-5 sm:p-6',

  // static variants
  'card-soft-gray': 'bg-muted border border-base',
  'card-outline-gray': 'bg-base border border-base',
}

export const dynamicCard = [
  // dynamic variants
  [/^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

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>
  _cardAbout?: Partial<NCardAboutProps>
  _cardFooter?: Partial<NCardFooterProps>
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/card.ts
   */
  una?: {
    cardDefaultVariant: HTMLAttributes['class']
    card?: HTMLAttributes['class']
  }
}

export interface NCardContentProps extends BaseExtensions {
  una?: {
    cardContent?: HTMLAttributes['class']
  }
}

export interface NCardTitleProps extends BaseExtensions {
  una?: {
    cardTitle?: HTMLAttributes['class']
  }
}

export interface NCardDescriptionProps extends BaseExtensions {
  una?: {
    cardSubtitle?: HTMLAttributes['class']
  }
}

export interface NCardHeaderProps extends BaseExtensions {
  una?: {
    cardHeader?: HTMLAttributes['class']
  }
}

export interface NCardAboutProps extends BaseExtensions {
  una?: {
    cardAbout?: HTMLAttributes['class']
  }
}

export interface NCardFooterProps extends BaseExtensions {
  una?: {
    cardFooter?: HTMLAttributes['class']
  }
}

Components

Card.vue
CardContent.vue
CardAbout.vue
CardTitle.vue
CardDescription.vue
CardHeader.vue
CardFooter.vue
<script setup lang="ts">
import type { NCardProps } from '../../../types/card'
import { computed } from 'vue'
import { cn } from '../../../utils'
import CardAbout from './CardAbout.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>(), {
  una: () => ({
    cardDefaultVariant: 'card-default-variant',
  }),
})

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const cardVariants = ['soft', 'outline'] as const
const hasVariant = computed(() => cardVariants.some(cardVariant => props.card?.includes(cardVariant)))
const isBaseVariant = computed(() => props.card?.includes('~'))
</script>

<template>
  <div
    v-bind="{ ...$attrs, delegatedProps }"
    :card="card"
    :class="cn(
      'card',
      !hasVariant && !isBaseVariant ? una?.cardDefaultVariant : '',
      props.class,
      una?.card,
    )"
  >
    <slot name="root">
      <CardHeader
        v-bind="delegatedProps._cardHeader"
      >
        <slot name="header" />
      </CardHeader>

      <CardAbout
        v-if="$slots.about || title || description || $slots.title || $slots.description"
        v-bind="delegatedProps._cardAbout"
      >
        <slot name="about">
          <CardTitle
            v-if="$slots.title || title"
            v-bind="delegatedProps._cardTitle"
          >
            <slot name="title">
              {{ title }}
            </slot>
          </CardTitle>
          <CardDescription
            v-if="$slots.description || description"
            v-bind="delegatedProps._cardDescription"
          >
            <slot name="description">
              {{ description }}
            </slot>
          </CardDescription>
        </slot>
      </CardAbout>

      <CardContent
        v-if="$slots.default"
        v-bind="delegatedProps._cardContent"
      >
        <slot />
      </CardContent>

      <CardFooter
        v-if="$slots.footer"
        v-bind="delegatedProps._cardFooter"
      >
        <slot name="footer" />
      </CardFooter>
    </slot>
  </div>
</template>