Skip to content
En esta página

CSS en Vue.js

Una de las ventajas de Vue.js es la facilidad con la que se pueden aplicar clases de CSS a elementos de HTML en forma dinámica, es decir, de acuerdo a cómo van cambiando los datos de la aplicación.

Por ejemplo, tenemos una tabla con una lista de alumnos y queremos que al cliquear sobre el nombre de un alumno la propiedad alumno.presente pase de false a true y que además el color de fondo de esa fila pase a ser verde:

AlumnoFechaPresente
Elon Musk24/03/21
Mark Zuckerberg24/03/21
Jack Dorsey24/03/21
Evan You24/03/21
Dan Abramov24/03/21

El color de fondo cambia porque le estamos indicando a Vue que, cuando esa propiedad cambia, que le agregue a esa fila la clase de CSS 'fondo-verde'.

Y, ¿cómo cambiamos las clases de CSS de acuerdo a los datos? De varias maneras. Vue es tan flexible en este punto que hasta resulta confuso por la variedad de formas en que se puede hacer.

Veamos una por una:

Clase simple

Esta es la forma más fácil de hacerlo, pero sólo puede ser usada cuando el dato y la clase de CSS tienen el mismo nombre.

Por ejemplo, en data tengo una propiedad booleana llamada azul y en <style> tengo una clase de CSS también llamada azul.

Entoces, al elmento de HTML que quiero que cambie de color le agrego el atributo v-bind:class (o sea, una clase dinámica, que cambia de acuerdo al dato que se le pase), o en forma abreviada: :class.

html
<template>

  <h2 :class="{ azul }" @click="cambiarColor"> CLICK </h2>

</template>

<script>

export default {

  data: () => ({
    azul: false
  }),
  methods: {
    cambiarColor() {
      this.azul = !this.azul
    }
  }
}
</script>

<style scoped>

.azul {
  background-color: blue;
  color: white;
}
</style>

Cuando azul es true Vue le aplica al elemento la clase azul. Y al cliquear sobre el elemento cambia el color:

CLICK

Funciona, pero hay un problema: en CSS los nombres de las clases suelen tener más de una palabra, que suelen ser separadas por un guión medio, por ejemplo: btn-primary, o sea, en formato kebab-case. O a veces también se usa el guión bajo: grid_column.

Pero en JavaScript las variables o propiedades deben declararse en formato camelCase, o sea: btnPrimary.

Cuando se trata de nombres de componentes Vue hace la conversión de kebab-case a camelCase en forma automática:

html
<template>
  <mi-componente/>
</template>

<script>
import MiComponente from './compontes/MiComponente.vue'

export default {
  components: {
    MiComponente
  }
}
</script>

Pero no cuando se trata de clases de CSS 😕️ Entonces, cuando tenemos clases de más de una palabra esto no va a funcionar.

Clase + booleano en data

La solución es diferenciar el nombre de la clase de CSS del valor booleano en data del cual la clase depende. En este caso, el nombre de la clase debe estar entre comillas: { 'fondo-azul': azul }

html
<template>

  <h2 :class="{ 'fondo-azul': azul }" @click="cambiarColor"> CLICK </h2>

</template>

<script>

export default {

  data: () => ({
    azul: false
  }),
  methods: {
    cambiarColor() {
      this.azul = !this.azul
    }
  }
}
</script>

<style scoped>

.fondo-azul {
  background-color: blue;
  color: white;
}
</style>

Entonces, según azul sea true o false Vue aplica al elemento la clase 'fondo-azul'. Y al cliquear sobre el elemento cambia el color:

CLICK

Clase + String en data

Lo mismo se puede hacer usando una propiedad de data que contenga un string vacío: fondoAzul: ''.

Cuando cliqueamos en el elemento el método cambiarColor() le asigna a this.fondoAzul el string 'fondo-azul' y ahí Vue aplica a ese elemento de HTML el nombre de esa clase:

html
<template>

  <h2 :class="fondoAzul" @click="cambiarColor"> CLICK </h2>

</template>

<script>

export default {

  data: () => ({
    fondoAzul: ''
  }),
  methods: {
    cambiarColor() {
      this.fondoAzul = this.fondoAzul ? 'fondo-azul' : ''
    }
  }
}
</script>

<style scoped>

.fondo-azul {
  background-color: blue;
  color: white;
}

</style>

Alternar entre 2 clases con un booleano

Para alternar entre dos clases ('fondo-verde' o 'fondo-azul') podemos declarar un booleano en data (azulVerde) y una computed (color()) que retorna un string con el nombre de la clase según el valor de ese booleano. Al clickear sobre el elemento h2 el method cambiarColor invierte el valor del booleano y la computed retorna el otro string, lo cual hace que cambie el valor de color con la otra clase de CSS:

html
<template>

  <h2 :class="color" @click="cambiarColor"> CLICK </h2>

</template>

<script>

export default {

  data: () => ({
    azulVerde: false
  }),
  computed: {
    color() {
      return this.azulVerde ? 'fondo-verde' : 'fondo-azul'
    }
  },
  methods: {
    cambiarColor() {
      this.azulVerde = !this.azulVerde
    }
  }
}
</script>

<style scoped>

.fondo-azul {
  background-color: blue;
  color: white;
}

.fondo-verde {
  background-color: green;
  color: white;
}
</style>

Ahora al cliquear el booleano se invierte y el fondo alterna entre los dos colores:

CLICK

Alternar entre 2 clases invirtiendo un Array

Otra forma de alternar entre dos clases es usando un array e invertiendo su orden con el método array.reverse():

html
<template>
  <!-- Con [0] estamos indicando que se trata del primer valor del Array: -->
  <h2 :class="classArray[0]" @click="cambiarColor"> CLICK </h2>

</template>

<script>

export default {
  
  data: () => ({
    classArray: ['fondo-azul', 'fondo-verde']
  }),

  methods: {
    cambiarColor() {
      // Invertir el orden del array
      this.classArray.reverse()
    }
  }
}
</script>

<style scoped>

.fondo-azul {
  background-color: blue;
  color: white;
}

.fondo-verde {
  background-color: green;
  color: white;
}
</style>

Ahora al cliquear el array de colores se invierte y el fondo pasa de un color al otro:

CLICK

Varias clases + Objeto en data

Si queremos que varias clases de CSS cambien al mismo tiempo podemos usar un objeto en data que las contenga:

html
<template>

  <h2 :class="variasClases" @click="cambiarColor"> CLICK </h2>

</template>

<script>

export default {
  data: () => ({
    variasClases: {
      'fondo-azul': true,
      'fondo-verde': false,
      'font-italicas': true,
      'font-bold': false
    }
  }),

  methods: {
    cambiarColor() {
      // Iterar sobre todas las keys del objeto e invertir el valor
      for (let key in this.variasClases) {
        this.variasClases[key] = !this.variasClases[key]
      }
    }
  }
}
</script>

<style scoped>

.fondo-azul {
  background-color: blue;
  color: white;
}

.fondo-verde {
  background-color: green;
  color: white;
}

.font-italicas {
  font-style: italic;
}

.font-bold {
  font-weight: bold;
}

</style>

Y así las cuatro clases cambian según el valor booleano:

CLICK

Varias Clases + Props + Computed

Pero, ¿y si los booleanos de los que dependen las clases llegan al componente mediante props? Entonces no es posible usar un objeto en data con los nombres de las clases. Esto se puede solucionar usando una computed:

html
<template>

  <h2 :class="variasClases"> Clases + Props + Computed </h2>

</template>

<script>

export default {

  props: {
    fondoAzul: Boolean,
    fondoVerde: Boolean,
    fontItalicas: Boolean,
    fontBold: Boolean
  },

  data: () => ({
  }),

  computed: {
    variasClases() {
      return {
        'fondo-azul': this.fondoAzul,
        'fondo-verde': this.fondoVerde,
        'font-italicas': this.fontItalicas,
        'font-bold': this.fontBold
      }
    }
  }
}
</script>

<style scoped>

.fondo-azul {
  background-color: blue;
  color: white;
}

.fondo-verde {
  background-color: green;
  color: white;
}

.font-italicas {
  font-style: italic;
}

.font-bold {
  font-weight: bold;
}
</style>

En este caso sería el componente padre el que determina qué clases se van a usar en el hijo:

Clases + Props + Computed

Clases según params enviados en métodos

Volviendo al primer ejemplo de la lista de alumnos, ¿cómo funciona por dentro?

El color de fondo tiene que cambiar según el estado de la propiedad alumno.presente, que puede ser true o false:

AlumnoFechaPresente
Elon Musk24/03/21
Mark Zuckerberg24/03/21
Jack Dorsey24/03/21
Evan You24/03/21
Dan Abramov24/03/21

Para eso hay que enviar esta propiedad desde el ciclo v-for que renderiza la lista de alumnos al método colorFondo():

html
<template>
  <table>

    <thead>
      <tr>
        <th>Alumno</th><th>Fecha</th><th>Presente</th>
      </tr>
    </thead>

    <tbody>
      <tr v-for="alumno in alumnos" :key="alumno.id"
        @click="presenteAusente(alumno.id)"
        :class="colorFondo(alumno.presente)"
      >
        <td>{{ alumno.nombre }}</td>
        <td>{{ alumno.fecha }}</td>
        <td>{{ alumno.presente ? '✅️' : '' }}</td>
      </tr>
    </tbody>
  </table>
</template>

<script>

const { VITE_API_URL: url } = import.meta.env

export default {

  data: () => ({
    alumnos: []
  }),

  created() {
    this.getData(url)
  },

  methods: {
    presenteAusente(id) {
      const alumno = this.alumnos.find(alumno => alumno.id === id)
      alumno.presente = !alumno.presente
    },
    colorFondo(presente) {
      return presente && 'fondo-verde'
    },
    async getData(url) {
      try {
        this.alumnos = await (await fetch(url)).json()
      } catch(err) {
        console.log(err)
      }
    }
  }
}
</script>

<style scoped>

.fondo-verde {
  background-color: green;
  color: white;
}

</style>

Usando el atributo style

Todas estas formas de usar clases dinámicas pueden ser igualmente aplicadas al atributo style en los casos en que necesiten usar estilos inline dentro de un elemento html, usando v-bind:style o, en forma abreviada :style.

Las propiedades de CSS dentro de style puede ser escritas tanto en camelCase (tal como son usadas en JavaScript) como en kebab-case (como son usadas en CSS nativo). Si las usan con camelCase no es necesario usar comillas:

html
<button 
  @click="addToCart"
  :style="{ backgroundColor }"
> {{ product.inCart ? 'Agregado' : 'Agregar al carrito' }}
</button>

Y en el script:

js
computed: {
  // Si la computed se llama igual que la propiedad de CSS
  // no es necesario repetir el nombre:
  // :style={ backgroundColor: backgroundColor }
  backgroundColor() {
    return this.product.inCart ? 'darkgreen' : 'darkblue'
  }
},

Pero si las usan con kebab-case es necesario ponerlas entre comillas con lo cual ya no pueden corresponderse en forma directa con las computed y el código es más largo (aunque más fácil de entender):

html
<button 
  @click="addToCart"
  :style="{ 'background-color': backgroundColor }"
> {{ product.inCart ? 'Agregado' : 'Agregar al carrito' }}
</button>

Usando v-bind dentro de CSS

Una de las formas más útiles de manipular los estilos de CSS con Vue es usando la directiva v-bind dentro de las clases mismas.

Por ejemplo, podemos hacer que el atributo font-size dentro de la clase 'element-size' dependa del valor de size en data:

html
<template>

  <p class="element-size" @click="decrement"> CLICK </p>

</template>

<script>

export default {

  data: () => ({
    size: 3,
  }),

  methods: {
    decrement() {
      this.size = this.size - 0.5
    }
  }
}
</script>

<style scoped>

/* v-bind dentro de una clase de CSS */
.element-size {
  font-size: v-bind(size + 'rem');
}

</style>

De esta forma, al cliquear el tamaño del elemento se va achicando:

CLICK

Esto es muy útil para hacer animaciones que dependan de la interacción del usuario, o de cálculos matemáticos complicados.

Pero recuerden que esta funcionalidad sólo está disponible en Vue 3 🤷‍♂️️

De todas formas, como Vue 3 permite usar la misma sintaxis de Vue 2, podemos instalar Vue 3 pero usarlo como si fuese Vue 2, y solamente usar las funcionalidades de Vue 3 que nos puedan ser útiles, como ésta 🙂️