採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

Vuetify — セットアップとテーマ

テクノロジ系 / L19 vuetify — Material Design コンポーネントライブラリ

Vuetify — セットアップとテーマ

Vuetify とは

  • Vue 3 向けの Material Design コンポーネントライブラリ
  • v-btn / v-text-field / v-card など、見た目が整った部品がすぐ使える
  • 自分で CSS を1から書かなくても、ボタン・フォーム・カードなどの基本 UI が揃う

この教材の前提知識

  • この教材は、component-design(コンポーネント設計)・vue-basic などを

既に学習済みであることを前提とする

  • Vue のコンポーネント、Composition API、<script setup> 構文、

Vue Router(router-view)の基本は、ここでは説明なしに使う

  • composableuseTheme() のような再利用可能な状態ロジック)という

用語も、既知のものとしてそのまま使う

Vuetify を導入する

Vue プロジェクトに Vuetify を組み込むには、大きく3つの手順が必要になる。

  1. npm install でパッケージを追加する
  2. ルートコンポーネント (App.vue) を <v-app> で囲む
  3. main.ts でプラグインとして登録する(後述)
npm install vuetify

なぜ npm 経由で導入するのか

npm install vuetify
  • npm 経由にするのは、Vite などのビルドツールと組み合わせて import

必要な部分だけ読み込むため

  • <script setup> から useTheme() のような composable を呼ぶ今回の構成は、

この import ベースの導入が前提になる

ルートコンポーネントを <v-app> で囲む

App.vue

<template>
  <v-app>
    <router-view />
  </v-app>
</template>
  • Vuetify のコンポーネントは、レイアウトやテーマの基盤を提供する <v-app>

内側で使われることを前提に作られている

  • <v-app> で囲むのを忘れると、v-btn などを置いても配色やレイアウトが

正しく反映されないことがある

  • なぜ専用タグが必要なのか: Vuetify は v-app-barv-navigation-drawer

の高さ・幅を計算し、v-main の余白に自動反映するレイアウト機構を持つ。

この計算の起点となる「箱」が <v-app> であり、ただの見た目のラッパーではない

ここまでで確認できること

npm install vuetifyApp.vue<v-app> で囲む → 後述の

main.ts の設定、まで済ませて開発サーバーを起動すると:

  • ブラウザのコンソールに Vuetify 関連のエラーが出ない
  • 画面に <v-btn>送信</v-btn> のようなタグを1つ置くだけで、

角丸・影・色のついた Material Design 風のボタンが表示される

もしボタンが素の <button> のような見た目のままなら、vuetify/styles

読み込み忘れや app.use(vuetify) の呼び忘れを疑うとよい(詳細は後述)。

セットアップ (main.ts) — 読み込み

import { createApp } from 'vue'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import 'vuetify/styles'

import App from './App.vue'
  • Vuetify 本体・全コンポーネント・全ディレクティブ・スタイルをまとめて読み込む

なぜ全コンポーネント/ディレクティブを読み込むのか

  • Vuetify には v-btn 以外にも数十種類のコンポーネントがあるが、ここでは

使う予定のないものも含めて全部 import している

  • 利点: コンポーネントを使うたびに import { VBtn } from 'vuetify/components'

と個別に書く必要がなく、<v-xxx> タグをそのまま書けば動く。学習中に

「このタグだけ import し忘れた」という事故が起きない

全部読み込みの代償と本番での切り替え

  • 代償: 実際には使わないコンポーネントの分だけ、本番ビルドのバンドルサイズが増える
  • 本番運用では vite-plugin-vuetify 等で使用コンポーネントだけを自動 import

する構成に切り替えるのが一般的

  • まずは全部読み込みで動かし方を覚えるのが先。最適化は後回しでよい

セットアップ (main.ts) — 登録

const vuetify = createVuetify({ components, directives })

createApp(App).use(vuetify).mount('#app')

app.use(vuetify) した時点で、アプリ全体のどのコンポーネントからも

v-btn などのタグや useTheme() などの composable が使えるようになる。

なぜ createVuetify() + app.use() という形なのか

const vuetify = createVuetify({ components, directives })
createApp(App).use(vuetify).mount('#app')
  • createVuetify() は、テーマ設定やコンポーネント一覧をまとめた

「プラグイン」オブジェクトを作る関数

  • app.use(vuetify) はこのプラグインを Vue の provide/inject の仕組みで

アプリ全体に配る。これにより、どんなに深い階層のコンポーネントからでも

useTheme() を呼ぶだけで同じテーマ状態にアクセスできる

  • もしテーマ情報がただのグローバル変数(例: export let theme = 'light'

だったら、provide/inject 越しではなく「別ファイルの変数を直接書き換える」

形になり、値が変わったことを検知して画面を再描画する仕組み

(リアクティビティ)が働かない

基本コンポーネントを使う

<template>
  <v-btn color="primary">送信</v-btn>
  <v-btn color="error" variant="outlined">削除</v-btn>
</template>
  • color prop で配色を指定(自分で class="btn-primary" 等を書かなくてよい)
  • variant prop で見た目の種類(flat / outlined / text 等)を切り替え
  • なぜ prop 指定なのか: color="primary" は「primary = 主役級の操作」という

意味を指定しているだけで、実際の色は次に出てくる theme 設定側が持つ。

CSS クラスを自分で書いてしまうと、後でテーマの色を変えたときにそのボタンだけ

古い色のまま取り残される

テーマ設定 (createVuetify の theme オプション)

const vuetify = createVuetify({
  components,
  directives,
  theme: {
    defaultTheme: 'light',
  },
})
  • Vuetify にはあらかじめ light / dark の2つのテーマが用意されている
  • defaultTheme で起動時のテーマを指定できる
  • なぜプラグイン作成時にまとめて設定するのか: v-btn color="primary" のように

アプリ中のあらゆるコンポーネントが同じ色名(primary 等)を参照する。

その色の実体をここ1箇所で決めておくことで、ブランドカラーを変えたいときの

変更箇所が1箇所で済む

useTheme() でテーマの状態を読み取る

<script setup>
import { useTheme } from 'vuetify'

const theme = useTheme()
</script>
  • useTheme() を呼ぶと、現在のテーマ情報を持つオブジェクトが得られる
  • theme.global.name.value … 現在のテーマ名('light' / 'dark')を

読み取れる

なぜグローバル変数でなく useTheme() なのか

  • useTheme() が返す theme.global.nameリアクティブな値(ref)

これを読んでいるコンポーネントは、値が変わると自動で再描画される

  • もし export let currentTheme = 'light' のようなただのモジュール変数

にしていたら、値を書き換えても Vue はそれを検知できず、画面は

自分で再描画のコードを書くまで古い表示のまま残る

  • useTheme()app.use(vuetify) 時に配られた状態を受け取るだけの

「取得口」。component-design で学んだ他の useXxx() composable と

同じ形なので、Vue のコンポーネントならどこからでも同じ書き方で呼べる

テーマを切り替える

<style scoped>

section code, section pre code { font-variant-ligatures: none; }

</style>

<script setup>
function toggleTheme() {
  theme.change(theme.global.name.value === 'light' ? 'dark' : 'light')
}
</script>
  • theme.change('dark') … テーマを切り替える(推奨される書き換え方法)
  • 現在のテーマ名を見て、逆のテーマ名を渡すことでトグル動作にできる
  • なぜ theme.global.name.value = 'dark' と直接書き換えず change()

経由するのか: change() という名前の関数を挟むことで「テーマを変える」

という意図がコードから読み取れる。Vuetify 側の内部実装が将来変わっても、

change() という窓口さえ変わらなければ呼び出し側のコードは書き換えずに済む

テーマはアプリ全体で共有される

graph LR
  A["app.use(vuetify)<br/>(main.ts で1回)"] --> B[コンポーネントA<br/>useTheme]
  A --> C[コンポーネントB<br/>useTheme]
  A --> D[孫コンポーネント<br/>useTheme]
  B -.同じ theme を参照.- C
  C -.同じ theme を参照.- D
  • useTheme() はアプリ内のどの階層のコンポーネントで呼んでも同じ状態を参照する
  • component-design の ch04 (provide/inject) と同じ仕組みが Vuetify 内部で使われている

まとめ

  • createVuetify({ components, directives })app.use() でプラグイン導入
  • v-btn などのタグはそのまま使える。color / variant prop で見た目を変える
  • useTheme() でテーマの読み取り・切り替えができる
  • テーマの状態はアプリ全体で共有される(provide/inject ベース)

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

Lv1: 基本 — v-btn の color / variant

問題

以下の仕様を満たすボタンを実装せよ。

  1. primary 色のボタン「送信」
  2. error 色で outlined のボタン「削除」
  3. success 色で text のボタン「完了」

合格基準

  • 3つのボタンが色違いで表示される
  • variant を変えたボタンの見た目が異なる(枠だけ / 文字だけ)

Lv2: 応用 — useTheme でテーマ切り替え

問題

useTheme() を使い、クリックするたびにアプリ全体のテーマを

light ⇄ dark で切り替えるボタンを実装せよ。

  1. ボタンのラベルに現在のテーマ名(light または dark)を表示すること
  2. クリックのたびに light ⇄ dark が交互に切り替わること
  3. テーマの切り替えには change() を使うこと(プロパティへの直接代入はしない)

合格基準

  • 初期状態は light と表示される
  • クリックすると dark に変わり、もう一度押すと light に戻る

Lv3: 発展 — 複数コンポーネントで theme を共有

問題

以下の仕様を満たすコンポーネント構成を実装せよ。

  1. 親コンポーネントに ThemeButton(Lv2 のテーマ切り替えボタン)と、

現在のテーマ名だけを表示する ThemeLabel(別コンポーネント)を並べて配置する

  1. ThemeButtonThemeLabel は、それぞれ自分自身で useTheme() を呼び出す
  2. 親コンポーネントから子へ props を渡したり、子から emit したりしてはいけない
  3. ThemeButton でテーマを切り替えたときに、ThemeLabel の表示も自動的に追従すること

合格基準

  • 別コンポーネントなのに、片方の操作がもう片方の表示に反映される
  • なぜ props/emit なしで反映されるのか、自分の言葉で説明できる

Lv4: 発展 — createVuetify でカスタムカラーテーマを定義する

問題

以下の仕様を満たすボタンを実装せよ。

  1. createVuetify() のテーマ設定に、好きな名前でカスタムカラー

(組み込みにない独自の配色)を1つ定義する

  1. v-btncolor prop にそのカスタムカラー名を指定し、ボタンに反映させる

合格基準

  • ボタンの背景色に、定義したカスタムカラーが適用されている
  • primaryerror など組み込みの色名ではなく、独自に定義した色名を使っている

提出

submission.md に4問の回答を。

ヒント

まずは第1段階だけを見て考えてみる。それでも分からなければ第2段階、

それでも分からなければ最後の手段として第3段階を見る。

第1段階: 考え方

  • Vuetify の「テーマ」(配色・ライト/ダークの設定)は、コンポーネントの外(main.ts)で

一度だけ定義されている。それを画面側の <script setup> から読み書きするには、

Vuetify が提供している「テーマにアクセスするための composable」を呼び出す必要がある。

  • その composable の戻り値を、テンプレートやクリック処理から使えるように

変数へ代入しておく。

  • 「切り替える」処理は、今の状態を見て逆の状態を選ぶ、という単純な分岐でしかない。

今の状態を読み取る方法と、状態を変更する方法さえ分かれば、toggleTheme() の中身は

自分で組み立てられる。

第2段階: 使う API 名

  • composable の名前は useTheme()vuetify パッケージからインポートする。
  • 現在のテーマ名は、戻り値の global.name.value というプロパティで読み取れる。
  • テーマを切り替えるときは、戻り値が持つ change() というメソッドを使う

(プロパティへの直接代入は非推奨)。

  • toggleTheme() は「今の名前が 'light' かどうかを見て、'dark''light' かを

change() に渡す」という三項演算子 1 行で書ける。

第3段階: コード断片

  • Vuetify のテーマ(配色設定)を JS から読み書きするには useTheme() を呼び出す。import { useTheme } from 'vuetify'
  • useTheme() の戻り値を theme という変数に代入すればよい。
  • 現在のテーマ名は theme.global.name.value'light' または 'dark')で読み取れる。
  • テーマを切り替えるときは theme.global.name.value = ... と直接代入せず、theme.change('dark') のように change() メソッドを使う(直接代入は非推奨警告が出る)。
  • toggleTheme() の中身はこう書ける: theme.change(theme.global.name.value === 'light' ? 'dark' : 'light')

解説

Vuetify の「テーマ」はアプリ全体で共有される設定

Vuetify を導入すると、色・ダークモードなどの見た目の設定は createVuetify({ theme: {...} })

アプリ起動時に一度だけ定義し、あとはどのコンポーネントからでも useTheme() で参照・変更できる。

import { useTheme } from 'vuetify'

const theme = useTheme()
console.log(theme.global.name.value)   // 'light' | 'dark'
<script setup>
import { useTheme } from 'vuetify'

const theme = useTheme()

function toggleTheme() {
  theme.change(theme.global.name.value === 'light' ? 'dark' : 'light')
}
</script>

<template>
  <v-btn @click="toggleTheme">
    テーマ: {{ theme.global.name.value }}
  </v-btn>
</template>
  • theme.global.name はアプリ全体で 1 つだけ存在する グローバルな状態

どのコンポーネントで useTheme() を呼んでも同じ値を参照する(Pinia の store に近い性質)。

  • 値を直接 theme.global.name.value = 'dark' と書き換えることもできるが、Vuetify は

theme.change('dark') を推奨している(将来的な内部処理の変更に対応するため)。

なぜコンポーネントごとに v-theme-provider を書かなくてよいか

Vue の provide/inject(component-design の ch04 で学んだ仕組み)を Vuetify がプラグイン内部で

使っており、app.use(vuetify) した時点でアプリ全体にテーマ情報が provide 済みになっている。

だからこそ、深い階層のどのコンポーネントからでも useTheme() するだけで同じテーマにアクセスできる。

「ライブラリが用意する composable(useXxx())は、内部で provide/inject や store を

使っていることが多い」と知っておくと、他のライブラリを読むときにも見通しが良くなる。

ヒント

まずは第1段階だけを見て考えてみる。それでも分からなければ第2段階、

それでも分からなければ最後の手段として第3段階を見る。

第1段階: 考え方

  • Vuetify にはあらかじめ primary / error / success などの色名が

用意されているが、それとは別に、自分だけの色名(カスタムカラー)を

追加で定義することもできる。

  • カスタムカラーは createVuetify() を呼び出すときの設定(テーマ設定)の中に、

「好きな名前」と「16進カラーコード」のペアとして書く。

  • 定義した色名は、組み込みの primary などと全く同じように、v-btn

color prop にそのまま指定して使える。

第2段階: 使う API 名

  • createVuetify({ theme: { themes: { light: { colors: { ... } } } } }) という

構造の中の colors オブジェクトに、キー名: '#16進カラーコード' の形で

カスタムカラーを追加する。

  • 例えば brand という名前で色を定義したら、v-btn には color="brand" と書く。

第3段階: コード断片

import { createVuetify } from 'vuetify'

const vuetify = createVuetify({
  theme: {
    themes: {
      light: {
        colors: {
          brand: '#8E24AA', // 好きな16進カラーコードでOK
        },
      },
    },
  },
})
<template>
  <v-btn color="brand">ブランドボタン</v-btn>
</template>

解説

Vuetify は組み込みの色以外に「自分だけの色」を追加定義できる

primaryerror のような色名は、Vuetify が最初から用意している「テーマの色」の

一部でしかない。createVuetify()theme.themes.<テーマ名>.colors

自分の好きなキー名を追加すれば、そのままアプリ独自の色として使えるようになる。

import { createVuetify } from 'vuetify'

const vuetify = createVuetify({
  theme: {
    themes: {
      light: {
        colors: {
          brand: '#8E24AA', // 独自に追加したカスタムカラー
        },
      },
    },
  },
})
<template>
  <v-btn color="brand">ブランドボタン</v-btn>
</template>
  • color prop は「組み込みの色名」と「自分で定義したカスタム色名」を区別しない。

theme.themes.light.colors に登録されている名前であれば、そのまま使える。

  • キー名は自由に付けられる(brand である必要はない。プロジェクトの

ブランドカラーなどに合わせて名前を決めるとよい)。

内部でどう反映されているか

Vuetify は createVuetify() に渡されたテーマ設定から、色ごとに

--v-theme-<色名> という CSS カスタムプロパティ(CSS 変数)を生成し、

<style id="vuetify-theme-stylesheet"> として <head> に差し込む。

v-btn などの color prop は、その値に応じて bg-<色名> というクラスを

コンポーネントに付与するだけで、実際の色は background-color: rgb(var(--v-theme-<色名>))

という形でこの CSS 変数から解決される。

graph LR
  A["createVuetify({ theme: { colors: { brand: '#8E24AA' } } })"] --> B["--v-theme-brand が\nCSS変数として生成される"]
  B --> C["v-btn color='brand' が\nbg-brand クラスを付与"]
  C --> D["background-color: rgb(var(--v-theme-brand))\nとして描画される"]

つまり「色名を書いただけ」と「テーマにその色を定義した」は別物で、

colors に登録していない名前を color prop に渡しても、対応する CSS 変数が

存在しないため色は反映されない。

この演習でのファイル構成について

通常のアプリでは createVuetify()main.ts で1回だけ呼び出し、

各コンポーネントは app.use(vuetify) 済みの状態からその恩恵を受けるだけでよい

(ch01 の「セットアップ」スライドの通り)。

この演習では、テストで動作を確認しやすくするため、createVuetify() の呼び出しを

answer.vue / starter.vue の中に書いているが、書いているテーマ設定の内容自体は

main.ts に書く内容と何も変わらない。