๐ŸŸข Card


Basic

NCard - use to display content in a flexible container component.

PropDescription
titleTitle of the card
descriptionDescription of the card

Create account now

Fill in the form below to create an account.

<template>
  <div class="grid w-full place-items-center">
    <NCard
      title="Create account now"
      description="Fill in the form below to create an account."
      class="sm:w-100"
    >
      <div flex="~ col" gap-4>
        <NFormGroup
          label="Email"
        >
          <NInput
            placeholder="phojrengel@gmail.com"
            leading="i-heroicons-envelope-20-solid"
          />
        </NFormGroup>

        <NFormGroup
          label="Password"
        >
          <NInput
            type="password"
            placeholder="*********"
            leading="i-heroicons-key-20-solid"
          />
        </NFormGroup>
      </div>
      <template #footer>
        <div mt-2 w-full gap-4 flex="~ justify-end">
          <NButton btn="soft-gray block">
            Login
          </NButton>
          <NButton btn="solid-black block">
            Create an account
          </NButton>
        </div>
      </template>
    </NCard>
  </div>
</template>

Variants

card="{variant}" - change the card variant

VariantDescription
outlineThe default variant.
softThe soft variant.
~The unstyle or base variant
If you want to change the default variant or add new variant, you can do so through the Configuration section.

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.

<template>
  <div flex="~ col" gap-4>
    <NCard
      title="Outline variant"
      description="Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum."
      card="outline"
    />
    <NCard
      title="Soft variant"
      description="Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum."
      card="soft"
    />
    <NCard
      title="Base variant"
      description="Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum."
      card="~"
    />
  </div>
</template>

Color

card="{variant}-{color}" - change the color of the card.

You can use any color provided by the Tailwind CSS color palette, the default is primary. You can also add your own colors to the palette through the Configuration section.

outline-orange

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

soft-error

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

<template>
  <div flex="~ col" gap-4>
    <NCard
      title="outline-orange"
      description="Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum."
      card="outline-purple"
    />
    <NCard
      title="soft-error"
      description="Lorem ipsum dolor sit amet consectetur adipisicing elit. aliquid pariatur, ipsum similique veniam quo totam eius aperiam dolorum."
      card="soft-error"
    />
  </div>
</template>

Slots

You can use the following slots to customize the card.

NameDescription
headerThe header slot.
defaultThe default slot.
titleThe title slot.
descriptionThe description slot.
footerThe footer slot.
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
<template>
  <div
    class="grid w-full place-items-center"
  >
    <NCard class="sm:w-100">
      <template #header>
        <div h-40>
          <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-full w-full object-cover"
          >
        </div>
      </template>

      <template #title>
        <div class="flex items-center justify-between">
          Advanced Card
          <NButton
            size="sm"
            icon
            class="rounded-full"
            btn="ghost-gray square"
            label="i-heroicons-ellipsis-vertical"
          />
        </div>
      </template>

      <template #description>
        This example shows how you can use named templates for customization

        <div class="mt-2 flex items-center space-x-2">
          <NBadge
            label="New"
            badge="soft-success"
          />

          <NBadge
            label="Beta"
          />
        </div>
      </template>

      <!-- content -->
      <div class="flex flex-col gap-4">
        <div class="flex items-center gap-2">
          <NIcon name="i-heroicons-user-circle-solid" />
          <span>John Doe</span>
        </div>
        <div class="flex items-baseline gap-2">
          <NIcon name="i-heroicons-map-pin-solid" />
          <span>San Francisco, CA</span>
        </div>
        <div class="flex items-center gap-2">
          <NIcon name="i-heroicons-calendar-solid" />
          <span>Joined on 2021-01-01</span>
        </div>
      </div>

      <template #footer>
        <div class="w-full flex flex-col">
          <div mt-2 w-full gap-4 flex="~ justify-between items-center">
            <NButton
              size="md"
              btn="ghost-warning"
              class="-mx-2"
            >
              Explore
            </NButton>
            <NButton
              size="md"
              icon
              label="i-heroicons-chevron-down-solid"
              btn="text-gray"
              class="rounded-full"
            />
          </div>
        </div>
      </template>
    </NCard>
  </div>
</template>

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>
  _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']
  }
}

Presets

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,
]

Component

<script setup lang="ts">
import { computed } from 'vue'
import type { NCardProps } from '../../../types/card'
import { cn } from '../../../utils'
import CardContent from './CardContent.vue'
import CardHeader from './CardHeader.vue'
import CardAbout from './CardAbout.vue'
import CardFooter from './CardFooter.vue'
import CardTitle from './CardTitle.vue'
import CardDescription from './CardDescription.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>
<script setup lang="ts">
import type { NCardContentProps } from '../../../types/card'
import { cn } from '../../../utils'

const props = defineProps<NCardContentProps>()
</script>

<template>
  <div
    :class="cn(
      'card-content',
      props.class,
      props.una?.cardContent,
    )"
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { NCardTitleProps } from '../../../types/card'
import { cn } from '../../../utils'

const props = defineProps<NCardTitleProps>()
</script>

<template>
  <h3
    :class="
      cn('card-title',
         props.class,
         props.una?.cardTitle,
      )
    "
  >
    <slot />
  </h3>
</template>
<script setup lang="ts">
import type { NCardDescriptionProps } from '../../../types/card'
import { cn } from '../../../utils'

const props = defineProps<NCardDescriptionProps>()
</script>

<template>
  <p
    :class="cn(
      'card-description',
      props.class,
      props.una?.cardSubtitle,
    )"
  >
    <slot />
  </p>
</template>
<script setup lang="ts">
import type { NCardHeaderProps } from '../../../types/card'
import { cn } from '../../../utils'

const props = defineProps<NCardHeaderProps>()
</script>

<template>
  <div
    :class="cn(
      'card-header',
      props.class,
      props.una?.cardHeader,
    )"
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { NCardAboutProps } from '../../../types'
import { cn } from '../../../utils'

const props = defineProps<NCardAboutProps>()
</script>

<template>
  <div
    :class="cn(
      'card-about',
      props.class,
      props.una?.cardAbout,
    )"
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { NCardFooterProps } from '../../../types/card'
import { cn } from '../../../utils'

const props = defineProps<NCardFooterProps>()
</script>

<template>
  <div
    :class="cn(
      'card-footer',
      props.class,
      props.una?.cardFooter,
    )"
  >
    <slot />
  </div>
</template>