Single File Components

vue.js logo

Creating a single file component

In the earlier What Are Components lesson we had a preview of what we are going to build now, and also the component structure:

countdown project image

Using this Vite setup we can create components in their own file to keep them organised, using the single file components method. We can create components anywhere in the src folder, but generally we group them into a components folder for organisation (already created).

Let's create our Event.vue component:

vue-countdown/
├── src/
│   ├── assets/
│   ├── components/
│   │   ├── Event.vue

All components use the .vue extension.

This is the component we will re-use for each event listed.

Single file component structure

Add the following structure to the Event.vue component:

<script setup></script>

<template>
    <p>Event component</p>
</template>

<style scoped></style>

This structure is the same for all components, including our App.vue. It also works in a similar way to the local and global components we looked at earlier:

app.component("app-header", {
  template: "<h1>app header component</h1>",
});

These used a template property, but when using single file components the HTML is placed inside the <template> wrapper:

<template>
    <p>Event component</p>
</template>

This is much more convenient as we can write regular HTML, rather than using a string. Also the <script> and <style> sections provide a convenient location for JavaScript and CSS. We can write regular JavaScript code, and we will also soon discover how to make use of Vue.js features for reactivity.

In the <style> section can write CSS just like a regular stylesheet. If we want to restrict the CSS to apply to only this component, we have the scoped attribute:

<style scoped></style>

This is optional, and if omitted, they styling will apply to all other elements in the app which match the CSS selector, even if located in other components.

Displaying a component

At the moment, our Event component is just a file placed into a folder. To see it in the browser, we need to add it to the location where we want it to render.

Remember from earlier, the App.vue component is the main .vue file which is mounted to the DOM, meaning everything starts from here. This is a place where we can register our component to display. First, import the Event component in the script section of the App.vue:

<script setup>
import Event from "./components/Event.vue";
// const eventData = [
// ...
// ];
</script>

Then render the component in the template section:

<template>
    <Event />
</template>

Save the file and you will see the browser update due to Vite's hot reloading feature, you should see the Event component text in the browser.

Event component content

In the Event.vue the text of Event component can now be replaced with the event markup:

<template>
    <article>
        <div class="event_data">
            <h3 class="event_name">Graduation</h3>
            <p class="event_details">Party time!!!</p>
        </div>
        <div class="event_countdown">
            <div class="remove_btn_wrapper">
                <button class="remove_btn">&#10060;</button>
            </div>
            <div>
                <p class="days_left">56</p>
                <small>days left</small>
            </div>
        </div>
    </article>
</template>

And update the CSS styling:

<style scoped>
article {
    display: flex;
    align-items: center;
    background: linear-gradient(135deg, #2c3e50, #3498db);
    border-radius: 1.2rem;
    margin: 1.2rem 0;
    padding: 1.5rem 2rem;
    color: #ffffff;
    font-weight: 300;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.event_data {
    flex: 3;
    padding-right: 2rem;
}

.event_name {
    font-size: 1.8rem;
    margin: 0 0 0.5rem 0;
    font-weight: 600;
    letter-spacing: 0.5px;
}

.event_details {
    font-size: 1.4rem;
    margin: 0;
    opacity: 0.9;
    line-height: 1.4;
}

.event_countdown {
    flex: 1;
    text-align: center;
    font-size: 1.6rem;
    border-left: 2px solid rgba(255, 255, 255, 0.2);
    padding-left: 2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.days_left {
    margin: 0;
    font-size: 2.4rem;
    font-weight: 700;
}

small {
    font-size: 1rem;
    opacity: 0.8;
    text-transform: uppercase;
    letter-spacing: 1px;
}

.remove_btn_wrapper {
    width: 100%;
    text-align: right;
    margin-bottom: 0.5rem;
}

.remove_btn {
    background: transparent;
    border: none;
    font-size: 1rem;
    cursor: pointer;
    color: rgba(255, 255, 255, 0.7);
    padding: 0.5rem;
    border-radius: 50%;
    transition: all 0.2s ease;
}

.remove_btn:hover {
    color: #ffffff;
    background: rgba(255, 255, 255, 0.1);
    transform: scale(1.1);
}
</style>

This event is just static HTML for now, but will soon be used to display the event data dynamically.