UIPackage
Menu

Framework

Change language

Boilerplate repo

Saved Cards List

blockfinance

List of stored payment cards using compact 3D card visuals. Marks one as the default, allows setting a different one as default, removing with a confirmation dialog, and adding a new card via an inline PaymentForm that collapses open. Emits `add`, `remove`, and `set-default` — consumer owns the persistence.

Also available for React ->

Installation

$npx shadcn-vue@latest add https://uipkge.dev/r/vue/saved-cards-list.json
Named registry:npx shadcn-vue@latest add @uipkge/saved-cards-listInstalls to:app/components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
titlestringoptional
descriptionstringoptional
onAdd(payload: AddPayload)optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

AddPayload
interface AddPayload {
  number: string
  name: string
  expiry: string
  cvc: string
  brand: CardBrand
}

Files installed (3)

  • app/components/blocks/SavedCardsList.vue2 kB
    <script setup lang="ts">
    import { computed, ref, useSlots } from 'vue'
    import type { HTMLAttributes } from 'vue'
    import { Plus } from 'lucide-vue-next'
    import { Button } from '@/components/ui/button'
    import AddCardDialog, { type AddPayload } from './AddCardDialog.vue'
    
    const props = defineProps<{
      title?: string
      description?: string
      onAdd?: (payload: AddPayload) => void
      class?: HTMLAttributes['class']
    }>()
    
    const emit = defineEmits<{ add: [payload: AddPayload] }>()
    
    const slots = useSlots()
    const showAdd = ref(false)
    
    const hasRows = computed(() => !!slots.default)
    
    function handleAdd(p: AddPayload) {
      props.onAdd?.(p)
      emit('add', p)
    }
    </script>
    
    <template>
      <div
        data-slot="saved-cards-list"
        :class="['bg-card text-card-foreground mx-auto w-full max-w-2xl rounded-xl border shadow-sm', props.class]"
      >
        <div class="flex items-center justify-between border-b px-5 py-4">
          <div>
            <h3 class="text-base font-semibold">{{ title ?? 'Saved cards' }}</h3>
            <p class="text-muted-foreground text-xs">{{ description ?? 'Manage the cards used for billing.' }}</p>
          </div>
          <slot v-if="$slots['header-action']" name="header-action" />
          <Button v-else-if="onAdd" size="sm" @click="showAdd = !showAdd"><Plus class="size-4" /> Add card</Button>
        </div>
    
        <!-- Add form (collapses inline) -->
        <AddCardDialog v-model:open="showAdd" @add="handleAdd" />
    
        <!-- Empty state -->
        <div v-if="!hasRows && !showAdd" class="px-6 py-14 text-center">
          <div class="bg-muted text-muted-foreground mx-auto grid size-12 place-items-center rounded-full">
            <Plus class="size-5" />
          </div>
          <h4 class="mt-3 text-sm font-medium">No cards saved</h4>
          <p class="text-muted-foreground text-xs">Add a card to use it for future payments.</p>
          <Button v-if="onAdd" size="sm" class="mt-4" @click="showAdd = true">Add your first card</Button>
        </div>
    
        <!-- List -->
        <ul v-else class="divide-border divide-y">
          <slot />
        </ul>
      </div>
    </template>
    
  • app/components/blocks/SavedCardRow.vue2.7 kB
  • app/components/blocks/AddCardDialog.vue0.7 kB

Raw manifest:https://uipkge.dev/r/vue/saved-cards-list.json