by Martijn Simons, Pixel-Nexus | 09 May 2025
Over the years, numerous frameworks have emerged to streamline web development for front-end developers. Among the most prominent are React, Angular, Vue, Bootstrap, and Tailwind CSS. In this brief article, I will provide an overview of the capabilities and features that make Vue and Tailwind CSS stand out, highlighting their value to developers in building efficient, scalable, and visually appealing web applications.
The setup process for both frameworks will not be covered in this article; however, I encourage you to explore our available tutorial to get started. (How to setup Vue3 and Tailwind CSS | Pixel-Nexus)
In the following sections I will be going over the segments of Vue I find important to get started. Examples provided are based on personal experience and Vue documentation. When working within VS code it is recommended to get the following plugins:
Typescript
Vue - Official
Tailwind CSS IntelliSense
The main features of Vue include, firstly, its declarative rendering
, which extends standard HTML and allows us to manipulate the DOM based on a JavaScript state managed by Vue’s framework. Secondly, reactivity
is a system of observers that tracks specific data properties. Based on the JavaScript state of the properties, Vue automatically updates the DOM elements dependent on them.
Before going into detail about the core Vue features, I will briefly showcase the two available API styles to use Vue as a framework.
The Options API
defines a component's logic using an object that organizes various options. This object typically includes a data
section, which holds the component's reactive properties, a methods
section for functions that manipulate the state and trigger updates, and a mounted
section, which handles the lifecycle hooks when the component is mounted.
Here's an example of the Options API:
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log(`The initial count is ${this.count}.`)
}
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
The primary distinction with the Composition API
is that the component logic is organized using API functions rather than the object-based structure of the Options API. Although the functionalities of the sections remain similar, their names are modified. In this approach, the properties returned in the data
section become reactive state variables defined at the top of the script. The methods
section is transformed into functions located beneath these reactive state variables, which mutate the state and trigger updates. The mounted
section is replaced with an imported API function from Vue that also manages lifecycle hooks.
Here's an example of the Composition API`:
<script setup>
import { ref, onMounted } from 'vue'
// reactive state
const count = ref(0)
function increment() {
count.value++
}
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
Depending on the requirements and goals for your project/production, arguments for both API styles can be made, for further information regarding the API styles and their usage please head over to the [Vue API style documentation.](Composition API FAQ | Vue.js)
From this point onwards code examples provided will be using the Composition API
style.
A data property that is watched by Vue for changes, whenever the value of this property is changed Vue will update the DOM elements that depend on it.
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
As illustrated in the example above, reactive state variables are wrapped in the ref
API object, signaling to Vue that they need to be tracked. To access their values in the script section, you use count.value
, however, within the template, the .value
attribute is not required, as the ref
objects are automatically unwrapped.
Complex logic that includes reactive data can make templates bloated and hard to maintain, when encountering a need for this, often a computed property is the solution. It reduces clutter and repetition of functionality. These properties can be accessed in the same way a ref
object can, with the .value
attribute.
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// a computed ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
By default computed properties only have a getter function, they are read-only
, it is possible however to create a writable computed property. By providing both a getter and a setter function, the .value
attributes can now be used to set a new value.
A vue SFC(Single File component) is a *.vue special file format, it encapsulates a component in a single file. This way each component has its own logic, template, and styles, enabling simplification of applications and ensuring reusability.
External properties can be passed to a component, but they require an explicit declaration to ensure that Vue recognizes them for tracking. A prop is a custom attribute that facilitates data transfer from a parent component to a child component. These properties are reactive and can consist of various data types, including strings, numbers, objects, and functions. This reactivity promotes reusability, as the DOM elements automatically update whenever a prop value changes.
Parent component(partial):
const post = {
id: 1,
title: 'My Journey with Vue'
}
Function setNewId() {
*/.../*
}
<BlogPost :id="post.id" :title="post.title" @clicked_btn="setNewId"/>
Child component:
<script setup lang=ts>
const props = defineProps<{
id: Number;
title: String;
}>();
const emit = defineEmits([
'clicked_btn'])
function btnPressed() {
emit('clicked_btn')
}
</script>
<template>
<button @click="btnPressed()">Press me </button>
<button @click=$"emit('clicked_btn')"></button>
</template>>();
It is advisable that props are never directly reassigned to a new value within the child component, instead, an event should be emitted to the parent, allowing the parent to manage the property update. As seen in the above example with the emission.
Props do not necessarily need to be bound, a static prop can also be passed. A bound property is usually used when passing a reactive property to a component:
// bound
const myTitle = ref<String>("My journey through Vue")
<BlogPost :title="myTitle" />
// static
<BlogPost title="My journey with Vue" />
The v-if
, v-else-if
and v-else
statements are used to conditionally render a component, based on the truthy-ness of an expression value.
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
Rendering a specific component several times based on source data. Allows for efficient rendering of components and removes clutter of duplicate elements within the component.
<div v-for="item in items">
{{ item.text }}
</div>
// or provide ordering hint to force reorder of elements based on given key
<div v-for="item in items" :key="item.id">
{{ item.text }}
</div>
Tailwind CSS 3.4 promotes a utility-first approach, contrasting with the component-first approach of regular CSS. Instead of relying on pre-designed components and styles, Tailwind enables developers to create scalable and reusable designs by combining their utility classes. This eliminates the need for large, hard-to-maintain CSS files.
Seamless integration with Vue and other JavaScript frameworks like React and Angular. While it remains fundamentally CSS, it simplifies development through its intuitive utility classes. Additionally, DaisyUI enhances Tailwind by providing component class names, offering a library of customizable components that can be easily adapted and styled with Tailwind's utility classes.
Optional configuration of color schemes, themes, spacing values, animation, keyframes, etc. through the tailwind.config.js
file or as an inline utility class. Allows for project-specific adjustments and styling.
// inline custom value
<div class="top-[117px]">
<!-- ... -->
</div>
// set custom value for screen sizes and colors in the tailwind config file
module.exports = {
theme: {
screens: {
sm: '480px',
md: '768px',
lg: '976px',
xl: '1440px',
},
colors: {
'blue': '#1fb6ff',
'pink': '#ff49db',
'orange': '#ff7849',
'green': '#13ce66',
'gray-dark': '#273444',
'gray': '#8492a6',
'gray-light': '#d3dce6',
},
}
}
Purge functionality to remove unused CSS from the generated utility classes to optimize performance and ensure smaller file sizes.
export default {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
content: ['./src/**/*.{html,js,vue}'],
theme: {
extend: {},
},
plugins: [],
}
Tailwind allows for responsive design and thus ensures the adaptability of the user interface. By prefixing a utility class with a predefined breakpoint prefix the element will respond when the screen size breakpoint is reached.
<!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->
<img class="w-16 md:w-32 lg:w-48" src="...">
Both frameworks provide the utility to develop scalable and efficient web applications but have a learning curve. Once you get used to the specific syntax of props, emissions, utility classes etc. it becomes quite easy to use and create your desired components and styles.
For further and more detailed information please refer to the links in the resource section below, this was a brief and general overview of my experience getting to learn Vue and Tailwind CSS.
Vue: Quick Start | Vue.js
Tailwind CSS: Installation - Tailwind CSS
DaisyUI: https://daisyui.com/
As a note, this overview gives information for Tailwindcss 3.4
. Some of the examples and syntax provided are changed or removed in Tailwindcss 4.1
!