A Vue application is built out of .vue files called Single-File Components (SFCs). Each file bundles the markup, logic and styles of one component in a single place, which is the format you will use for every real project. In this lesson you will learn the anatomy of an SFC, how <script setup> works, what each folder in a scaffolded Vite project is for, and how the application is mounted onto the page.
An SFC has up to three top-level blocks. Only <template> is required, but almost every component uses the first two.
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button class="counter" @click="count++">
Clicked {{ count }} times
</button>
</template>
<style scoped>
.counter {
padding: 0.5rem 1rem;
border-radius: 6px;
}
</style><script setup> holds the component logic. Every top-level binding (variables, functions, imported components) is automatically available in the template.<template> contains the HTML with Vue directives and {{ }} interpolation.<style scoped> restricts the CSS to this component only. Vue adds a data-v-xxxxxxx attribute to the component's elements and rewrites the selectors accordingly, so .counter here cannot leak into other components.The browser cannot load .vue files directly. Vite (via the @vitejs/plugin-vue plugin) compiles each SFC into a plain JavaScript module during development and at build time.
Vue 3 supports two ways to write the logic block. The older <script> with export default { ... } is the Options API. <script setup> is compile-time syntactic sugar for the Composition API and is the recommended default:
| Feature | <script> + export default | <script setup> |
| --- | --- | --- |
| Boilerplate | Must return values from setup() | Top-level bindings exposed automatically |
| Imported components | Registered in components | Usable directly in the template |
| TypeScript | Works | Best inference, defineProps<T>() |
The two styles can coexist in one project; the Options API is explained in a later lesson. New code should use <script setup>.
npm create vue@latest generates a Vite project. The important files:
my-app/
├── index.html # the single HTML page; contains <div id="app">
├── package.json
├── vite.config.js # Vite and plugin configuration
├── public/ # static files copied as-is (favicon, robots.txt)
└── src/
├── main.js # entry point: creates and mounts the app
├── App.vue # root component
├── assets/ # images and CSS processed by Vite
└── components/ # your reusable componentsindex.html lives at the project root because Vite treats it as the entry point and rewrites its <script type="module" src="/src/main.js"> tag. Files in public/ are served at the site root without processing, while files in src/assets/ are imported and hashed for cache busting.
src/main.js is where the component tree becomes a running application:
import { createApp } from 'vue'
import App from './App.vue'
import './assets/main.css'
const app = createApp(App)
app.mount('#app')createApp receives the root component and returns an application instance. Plugins (Vue Router, Pinia), global components and configuration are registered on this instance before mount. mount('#app') renders the tree inside the element matched by the selector in index.html. The <div id="app"> element itself stays in the DOM; Vue renders into it.
Import a child component in <script setup> and use it as a tag: import HelloWorld from './components/HelloWorld.vue' followed by <HelloWorld msg="Welcome" /> in the template. No registration step is needed. Give components multi-word PascalCase names (TodoItem, not Todo) so they never clash with existing or future HTML elements, and name the file after the component.
npm run dev for the dev server with hot module replacement; edits to a .vue file update the page without a full reload..vue files.<style scoped> by default. Reserve unscoped <style> for global rules such as CSS resets, and keep those in assets/main.css.What does `<style scoped>` do?
.vue file combines <script setup>, <template> and <style> for one component.<script setup> exposes every top-level binding to the template with no boilerplate.scoped styles are rewritten with a data attribute so they cannot leak.main.js calls createApp(App).mount('#app'); plugins are registered on the app instance before mounting.public/ is copied verbatim, src/assets/ is processed and hashed by Vite.Next lesson: Template Syntax and Reactivity — interpolation, expressions in templates and how Vue keeps the DOM in sync with your data.