1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue 3の名前付きスロットで、複数カードとフォールバック表示を作ってみる

1
Last updated at Posted at 2026-08-11

Vue 3でカードを複数並べる画面を考えたときに、カードの見た目は共通にしつつ、データがあるカードは内容を表示し、データがないカードは案内を表示したくなりました。
今回は、名前付きスロットとフォールバックを組み合わせて、その形を小さく試したメモです。

まだ途中段階なので、勘違いなどあれば教えていただけるとうれしいです。

今日やったこと

同じBaseCardを複数表示し、カードごとのデータ有無で表示内容を切り替えました。

カードの枠・余白・背景色はBaseCard.vueにまとめます。一方で、タイトル・本文・一覧はApp.vueから名前付きスロットとして渡し、データがない場合はBaseCard側に書いたフォールバックを表示する形にしました。

キャラクター 「同じカードの枠を並べて、中身がある場所だけ情報を入れるんだね。データがない場所には案内札を出す感じになりそう」

Vueでは、<slot>の内側にフォールバックコンテンツを書けます。親コンポーネントからスロットの内容が渡されなかった場合にだけ、そのフォールバックが表示されます。 Vue.js公式のスロットガイド ja.vuejs

まず、カード本体を作ります。titlebodylistの3つに名前を付け、それぞれにフォールバックを用意しました。

<!-- src/components/BaseCard.vue -->

<template>
  <!--
    カード全体の入れ物。
    枠線・背景色・内側の余白は、このコンポーネントで共通化する。
  -->
  <article class="base-card">
    <!--
      タイトルを表示する領域。
      親側から title スロットが渡されない場合は、
      「ご案内」という見出しをフォールバックとして表示する。
    -->
    <header class="base-card__header">
      <slot name="title">
        <h2>ご案内</h2>
      </slot>
    </header>

    <!--
      本文を表示する領域。
      親側から body スロットが渡されない場合は、
      準備中の案内文をフォールバックとして表示する。
    -->
    <div class="base-card__body">
      <slot name="body">
        <p>※フォールバック表示: 現在、この項目の詳細は準備中です。更新までしばらくお待ちください。</p>
      </slot>
    </div>

    <!--
      一覧を表示する領域。
      親側から list スロットが渡されない場合は、
      一覧がないことを示す文章をフォールバックとして表示する。
    -->
    <div class="base-card__list">
      <slot name="list">
        <p>現在、表示する項目はありません。</p>
      </slot>
    </div>
  </article>
</template>

<style scoped>
/*
  BaseCard 共通の見た目を定義する。
  scoped にして、このコンポーネント用のスタイルとして管理する。
*/
.base-card {
  /* 親要素の横幅に合わせてカードを広げる */
  width: 100%;

  /* 内容が少なくてもカードの高さをある程度そろえる */
  min-height: 170px;

  /* カード内の文字が端に寄りすぎないようにする */
  padding: 1rem;

  /* カードの境界線を表示する */
  border: 1px solid #b7c2cc;

  /* 角を丸くして、ひとまとまりのカードとして見せる */
  border-radius: 10px;

  /* 背景色を白にする */
  background: #fff;
}

/*
  タイトル・本文・一覧の各領域に間隔を作る。
  スロットから渡されるタグそのものではなく、
  BaseCard 側で用意した領域に余白を付ける。
*/
.base-card__body,
.base-card__list {
  margin-top: 0.75rem;
}
</style>

このBaseCardは、親側から内容が渡されればその内容を表示し、渡されなければカード内のフォールバックを表示します。データの有無にかかわらず、カード内に何らかの案内が残る形を目指しました。

つまずいたところ

フォールバックを表示するには、空の内容を渡すのではなく、スロット自体を渡さない必要がありました。

たとえば、データがないカードにも#titleを書いてしまうと、titleスロットは渡された扱いになります。そのため今回は、データがある場合だけ#title#body#listを出すように、v-ifを付けています。

キャラクター 「データが空だからといって、空のスロットを渡すわけではないんだね。スロットを渡さないときに、カード側の案内が出ると考えると整理しやすそう」

v-iffalseのとき、そのtemplateブロックはレンダリングされません。今回はそれを利用して、データがないカードではスロットを渡さず、BaseCardのフォールバックを使う形にしています。 Vue.js公式の組み込みディレクティブ vuejs

次に、複数のカードを表示するApp.vueです。1枚目はすべてのデータがあり、2枚目は本文と一覧がなく、3枚目はデータを持たないカードにしています。

<!-- src/App.vue -->

<script setup>
// BaseCard コンポーネントを読み込む。
// Vue 3 + Vite では、利用するコンポーネントを明示的に import する。
import BaseCard from './components/BaseCard.vue'

// カードごとの元データ。
// 1件目は title・body・list をすべて持つ。
// 2件目は title だけを持つ。
// 3件目はデータがない状態を確認するため、id だけを持たせている。
const cards = [
  {
    id: 1,
    title: '今月のお知らせ',
    body: '更新内容と確認してほしい項目をまとめています。',
    list: ['表示内容を確認する', '必要な設定を見直す']
  },
  {
    id: 2,
    title: 'プロフィール'
  },
  {
    id: 3
  }
]
</script>

<template>
  <!--
    cards 配列の件数分だけ BaseCard を表示する。
    :key には、各カードを識別できる id を指定する。
  -->
  <section class="card-list">
    <BaseCard
      v-for="card in cards"
      :key="card.id"
    >
      <!--
        title がある場合だけ、title スロットを渡す。
        title がないカードでは、この template は表示されないため、
        BaseCard 側の「ご案内」がフォールバックとして表示される。
      -->
      <template
        v-if="card.title"
        #title
      >
        <h2>{{ card.title }}</h2>
      </template>

      <!--
        body がある場合だけ、body スロットを渡す。
        body がないカードでは、BaseCard 側の準備中文が表示される。
      -->
      <template
        v-if="card.body"
        #body
      >
        <p>{{ card.body }}</p>
      </template>

      <!--
        list に1件以上の項目がある場合だけ、list スロットを渡す。
        list がないカードでは、BaseCard 側の一覧用フォールバックが表示される。
      -->
      <template
        v-if="card.list && card.list.length > 0"
        #list
      >
        <!--
          list 内の文字列を1件ずつ li として表示する。
          :key には、ここでは一覧の文字列そのものを指定している。
        -->
        <ul>
          <li
            v-for="item in card.list"
            :key="item"
          >
            {{ item }}
          </li>
        </ul>
      </template>
    </BaseCard>
  </section>
</template>

<style scoped>
/*
  複数のカードを縦方向へ並べるための設定。
  gap でカード同士の間隔を作る。
*/
.card-list {
  display: grid;
  gap: 1rem;
}
</style>

このサンプルを表示すると、カードは3枚並びます。

  • 1枚目は、タイトル・本文・2件の一覧が元データから表示される
  • 2枚目は、「プロフィール」は元データから表示され、本文と一覧はフォールバックになる
  • 3枚目は、タイトル・本文・一覧のデータがないため、すべてBaseCard側のフォールバックが表示される

image.png

名前付きスロットは、親側からどの場所へ内容を渡すかを分けられる仕組みです。#title#body#listのように名前を合わせて書くことで、それぞれ対応する<slot name="...">へコンテンツを渡せます。 Vue.js公式のスロットガイド handson.vuejs-jp

キャラクター 「カードによって持っているデータが違っても、枠は同じままで並べられるんだね。情報がある場所だけ中身を置けるから、一覧画面にも使えそう」

今の理解(メモ)

今の自分には、名前付きスロットとフォールバックは「データの有無に合わせてカードの中身を見せるための組み合わせ」に見えています。

  • BaseCard.vueは、カードの枠と、タイトル・本文・一覧を置く場所を持つ
  • App.vueは、カードごとの元データを持ち、データがある場合だけ名前付きスロットを渡す
  • v-forを使うと、配列の件数に合わせて同じBaseCardを複数表示できる
  • v-ifでスロットを渡すかどうかを切り替えると、データがない場合はフォールバックを表示できる
  • フォールバックを置いておくと、元データがないカードでも空白にならず、利用者へ状態を伝えられそう

複数カードの表示は、持っているアイテムの数に合わせて一覧へ並べる仕組みに少し近いと感じました。今回はカードの件数に合わせてBaseCardを並べ、各カードのデータ有無によって中身を変えています。

次にやりたいこと

カードの見た目とデータの状態を、もう少し細かく分けて試したいです。

  • 元データが空配列の場合に、「カードがありません」と画面全体へ表示する
  • loadingの状態を追加し、データがない状態と読み込み中の状態を分ける
  • listが空配列の場合だけ、「表示する項目はありません」と出す
  • カードごとにボタンを渡せるactionsスロットを追加する
  • propsを使って、カードの種類や表示状態を渡す形も試す

今日の整理

複数カードとフォールバック表示の役割を、今の理解で整理します。

要素 役割 このサンプルでの動き 次に確認したいこと
BaseCard.vue 共通のカード枠とスロットを定義する 3枚のカードで同じ枠を使う 横並びのレイアウト
cards カードごとの元データを持つ 3件のデータで3枚表示する APIなどから取得する場合
v-for 配列の件数分だけカードを表示する cardsの3件分を繰り返す 並び順や絞り込み
#title#body#list 名前付きスロットへ内容を渡す データがある項目だけ表示する propsとの使い分け
v-if スロットを渡すかどうかを切り替える データなしならフォールバックを使う 読み込み中の表示
フォールバック スロット未指定時の標準表示 データのないカードに案内を出す 文言の分かりやすさ

おわりに

今回は、同じBaseCardを複数並べ、元データがあるカードには名前付きスロットで内容を渡し、元データがないカードにはフォールバックを表示する形を試しました。カードを共通化するだけでなく、データが欠けている状態でも画面に何を表示するかを考える必要があると感じました。

次は、データなし・読み込み中・エラーといった状態も分けながら、利用者が今どの状態なのか分かる画面を作ってみたいです。フロント側の画面づくりを少しずつ覚えて、見やすく操作しやすいUIにつなげていければと思います。

学習中の内容なので、勘違いや理解不足もあると思います。もし気になる点があれば教えてもらえるとうれしいです。

 注意事項
 本ブログに掲載している内容は、私個人の見解であり、
 所属する組織の立場や戦略、意見を代表するものではありません。
 あくまでエンジニアとしての経験や考えを発信していますのでご了承ください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?