状態によって見た目を変えたい場面は、とても多くあります。選択中のタブを強調する、エラーの入力欄を赤くする、進み具合に合わせてバーの幅を変える、などです。Vueでは、:classと:styleが、そのための専用の仕組みです。この記事では、Vue 3(<script setup>)を前提に、基本の書き方から、使い分けの判断、アクセシビリティまでを整理します。
クラスの切り替え:オブジェクト構文
オブジェクトのキーがクラス名、値が条件です。値が真のキーだけが、クラスとして付きます。静的なclassとも併用でき、両方が合わさって出力されます。クラス名にハイフンを含むときは、'text-danger'のように、引用符で囲みます。
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
</script>
<template>
<!-- 値が真のキーだけが、クラスになる -->
<div :class="{ active: isActive, error: hasError }">状態によってクラスが変わる</div>
<!-- 静的な class と併用できる → class="box active" -->
<div class="box" :class="{ active: isActive, 'text-danger': hasError }">併用する例</div>
</template>
クラスの切り替え:配列構文
複数のクラスを並べたいときは、配列構文を使います。配列の中の偽の値(空文字、false、null)は、無視されるので、hasError && 'has-error'のような書き方ができます。配列の中に、オブジェクトを混ぜることもできます。
<script setup>
import { ref } from 'vue'
const baseClass = ref('card')
const isActive = ref(true)
const hasError = ref(false)
</script>
<template>
<!-- 配列の中では、偽の値(''・false・null)は無視される -->
<div :class="[baseClass, isActive ? 'is-active' : '', hasError && 'has-error']">配列構文</div>
<!-- 配列の中に、オブジェクトを混ぜる -->
<div :class="[baseClass, { 'is-active': isActive, 'has-error': hasError }]">混在</div>
</template>
条件が複雑なら computed にまとめる
テンプレートに条件を書きすぎると、読みにくくなります。条件が複雑なときは、computedでオブジェクトを作り、:classに渡します。computedの基本はcomputedプロパティで動的なデータ処理を行う方法をご覧ください。
<script setup>
import { ref, computed } from 'vue'
const isActive = ref(true)
const error = ref(null)
// 条件が複雑なときは、computed にまとめる
const classObject = computed(() => ({
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal'
}))
</script>
<template>
<div :class="classObject">computed で作ったクラス</div>
</template>
コンポーネントにクラスを渡す
ルート要素が1つのコンポーネントにclassを渡すと、子のルート要素のクラスと、自動でマージされます。ルート要素が複数あるコンポーネントでは、どの要素にクラスを付けるかが決められないので、$attrs.classで明示的に結びつけます。
<!-- 子コンポーネント MyButton.vue(ルート要素が1つ) -->
<template>
<button class="btn">ボタン</button>
</template>
<!-- 親:渡した class は、子のルート要素のクラスとマージされる -->
<MyButton class="primary" :class="{ large: isLarge }" />
<!-- → <button class="btn primary large"> -->
<!-- ルート要素が複数あるときは、どこに付けるかを $attrs.class で明示する -->
<!-- <p :class="$attrs.class">...</p><span>...</span> -->
インラインスタイルの切り替え::style
:styleは、CSSのプロパティをオブジェクトで渡します。プロパティ名は、camelCase(fontSize)が推奨ですが、ハイフンのままでも、引用符で囲めば書けます。配列で複数のスタイルを渡すと、マージされて、同じ要素に適用されます。ベンダープレフィックスは、Vueが実行時に、ブラウザの対応に応じて自動で付けます。
<script setup>
import { ref } from 'vue'
const progress = ref(40)
const fontSize = ref(16)
</script>
<template>
<!-- 実行時にしか決まらない数値だけを、:style にする -->
<div class="bar" :style="{ width: progress + '%' }"></div>
<!-- camelCase が推奨。ハイフンを使うなら、引用符で囲む -->
<p :style="{ fontSize: fontSize + 'px' }">camelCase</p>
<p :style="{ 'font-size': fontSize + 'px' }">kebab-case</p>
<!-- 配列で、複数のスタイルをマージできる(あとのものが優先) -->
<div :style="[baseStyles, overridingStyles]"></div>
</template>
CSSで書けることは、CSSに任せる
状態に応じた見た目の切り替えは、基本は、クラスの付け外しで行います。:styleは、実行時にしか決まらない数値(バーの幅の%など)に限ると、整理しやすくなります。ホバーのような、CSSで書ける状態を、JavaScriptで管理する必要はありません。固定の値(transitionなど)も、CSSのクラスに書きます。
<!-- 避けたい書き方:JavaScript でホバーを管理し、固定の値まで :style に書く -->
<div
:class="{ highlight: isHovered }"
:style="{ width: progress + '%', transition: 'width 0.3s' }"
@mouseenter="isHovered = true"
@mouseleave="isHovered = false"
>
</div>
<!-- 良い書き方:ホバーは CSS の :hover、固定の値は CSS、動的な数値だけを :style に -->
<div class="bar" :style="{ width: progress + '%' }"></div>
<style scoped>
.bar { transition: width 0.3s; }
.bar:hover { background: #42b983; }
</style>
styleの中でv-bind()を使う
Vue 3の単一ファイルコンポーネントでは、<style>の中でv-bind()を使い、状態をCSSの値に直接結びつけられます。JavaScriptの式を使う場合は、引用符で囲みます。公式の説明によると、実際の値は、ハッシュ化したCSSカスタムプロパティとしてコンパイルされ、コンポーネントのルート要素にインラインで適用されます。状態が変われば、リアクティブに更新されます。CSSの本体は静的なままなので、見通しがよくなります。