Styling Vue components with Aphrodite

Nicholas Baroni
5 min readApr 20, 2018

--

If you search on Google for “aphrodite vue.js”, not a lot comes up. Perhaps it’s because using Aphrodite with Vue is somewhat straightforward, or maybe it’s because of Vue has excellent support for using CSS and CSS preprocessors inside of your component file. So why use something like Aphrodite to create your CSS with JS? Why not use a CSS preprocessor, since Vue allows you to co-locate your styles anyway? The reasons are as follows:

  1. The CSS (SASS, Stylus, etc.) in your Vue components gets compiled into one large CSS file. A large CSS file will increase your initial page load time, because the application can’t render until the whole file is downloaded and parsed. Now that dynamically loading JS is common, CSS needs to be more modular. Dynamically loading CSS will become more common with increasing support from Webpack and babel plugins, but you can avoid the accompanying complexity by generating CSS with JS.
  2. Any CSS preprocessor requires you to learn special syntax. You likely already know JS, so avoid the effort of learning and relearning extra syntax.
  3. Using CSS requires associating application state with CSS selectors, usually class names. When you generate CSS with JS, you can avoid that.

There are only two hard things in Computer Science: cache invalidation and naming things.

— Phil Karlton

To be fair, we should list the advantages of using a CSS preprocessor.

  1. There’s no need for a JS style library, like Aphrodite.
  2. You can use child selectors.
  3. CSS preprocessors have functions to manipulate color, so you can create many different shades of your theme colors, making it easier to change your theme by just changing a few values. The JS libary for doing this, color, is somewhat large and you might not want it in your bundle.
  4. The ecosystem around CSS preprocessors is huge, and there are libraries for typography, transitions, grids, etc. There doesn’t seem to be much of an ecosystem around CSS in JS libraries because those libraries are mainly used with component-based UI libraries. Component-based UI libraries can lead to less-semantic HTML that’s crowded with wrapper divs.

Honestly, there are good reasons for using either styling method. Using component-based UI libraries with Aphrodite will probably be easier and faster, but may result is less semantic source code. I would recommend it for POCs and projects with time constraints. Dynamic CSS loading used with CSS preprocessors will be a harder workflow and more difficult for on-boarding developers, but could result in better, more efficient production code.

If you’re still game for using Aphrodite, read on!

Basic Integration

This example uses Aphrodite as it might be used in React. We’ll refactor this, but let’s use it as a starting point.

<template>
<p :class="css(styles.p, active && styles.active)">
Text
</p>
</template>
<script>
import { css, StyleSheet } from 'aphrodite'
export default {
props: ['active'],
data () {
return {
css,
styles
}
}
}
const styles = StyleSheet.create({
p: {
color: 'grey'
},
active: {
color: 'blue'
}
})
</script>

This is a basic example of using Aphrodite with Vue, and if you are new to either library, you might end up with something like this, since that’s how Aphrodite’s documents advise you to use it in React. There are 2 problems that I can see with this approach, and for each we can use Vue’s features to correct the problem.

  1. Aphrodite needs to be imported into each component. It would be better to use a Vue plugin.
  2. We still have to associate application state with style names, which can become more complicated than in the example above. We can avoid doing this by declaring our styles in a computed function.

If we make a simple Vue mixin that puts the Aphrodite functions onto the Vue component prototype, we can avoid having to import Aphrodite.

// lib/aphrodite-plugin.jsimport { css, StyleSheet } from 'aphrodite'export default {
install (Vue, options = {}) {
Vue.prototype.$css = css
Vue.prototype.$style = StyleSheet.create.bind(StyleSheet)
}
}
// main.jsimport Vue from 'vue'
import Aphrodite from './lib/aphrodite-plugin'
Vue.use(Aphrodite)

Now here is the example refactored to use the plugin.

<template>
<p :class="$css(styles.p, active && styles.active)">
Text
</p>
</template>
<script>
export default {
props: ['active'],
data () {
return {
styles: this.$style(styles)
}
}
}
const styles = {
p: {
color: 'grey'
},
active: {
color: 'blue'
}
})
</script>

Now we don’t have to import Aphrodite each time. We’re still using a data property. Since styles are a function of state, we could arguably move styles from data to a computed property.

<template>
<p :class="$css(styles.p, active && styles.active)">
Text
</p>
</template>
<script>
export default {
props: ['active'],
computed: {
styles () {
return this.$style(styles)
}
}
}
const styles = {
p: {
color: 'grey'
},
active: {
color: 'blue'
}
})
</script>

Since we’re still associating class names with state, there’s not yet any difference between using data or a computed property. Let’s refactor again and create the style objects inside of the computed function.

<template>
<p :class="$css(styles.p)">
Text
</p>
</template>
<script>
export default {
props: ['active'],
computed: {
styles () {
return this.$style({
p: {
color: this.active ? 'blue' : 'grey'
}
})
}
}
}
</script>

We did 2 things. We moved the style objects into the computed function, and we scrapped the ‘active’ style in favor of using JS to determine the color. Now we can avoid having to think, “What do I call this state within my styles?” The style object’s keys are simply identifiers of elements in the template. Now we have a new problem though, in that the Vue JS object can become bloated with styles, making it difficult to read non-style related code. The fix is simple.

<template>
<p :class="$css(styles.p)">
Text
</p>
</template>
<script>
export default {
props: ['active'],
computed: {
styles
}
}
// you cannot use an arrow function for this
// or else 'this' won't refer to the component instance
function styles () {
return this.$style({
p: {
color: this.active ? 'blue' : 'grey'
}
})
}
</script>

Inherited Styles

We can account for inherited styles by using Vue’s $attrs object.

//  parent.vue
<template>
<div>
<h1 :class="$css(styles.h1)">
Heading
</h1>
<Child styles="styles.child" />
</div>
</template>
// child.vue
<template>
<p :class="$css(styles.p, $attrs.styles)">
Text
</p>
</template>

What Next?

  1. Mixins — Import JavaScript functions that return style objects, and create an ecosystem of style mixins in JS.
  2. Theming — Even though we can implement a theme in JS, I still like using CSS custom properties for theming.
  3. CSS Polyfills — How can you theme with the CSS custom properties when you need to support IE11? You can polyfill some CSS features by adding the logic inside of your Vue Aphrodite plugin. For instance, you could pass an object of theme colors to your plugin, inside of the options object. Then, add the logic to scan your style objects and replace the var(--color) value with the value of color or --color from the theme object.
  4. Change the syntax. Want to use style strings instead of style objects? No problem. Just convert the strings to objects inside of the plugin.

--

--