Skip to content

Single-File Components

Vue’s most distinctive feature is the Single-File Component (SFC) — a .vue file that contains a component’s logic, template, and styles in one place. This is how all Vue components in this course are written.

A .vue file has up to three top-level blocks:

<script setup lang="ts">
// Component logic: imports, reactive state, functions
</script>
<template>
<!-- Component HTML template -->
</template>
<style scoped>
/* Component CSS — scoped to this component only */
</style>

Each block is optional, but in practice almost every component has all three.

<script setup> is the Composition API’s shorthand. Everything declared inside it is automatically available in the template — no return statement needed.

<script setup lang="ts">
import { ref } from 'vue'
import PersonCard from './PersonCard.vue'
const name = ref('Alice')
const greeting = `Hello, ${name.value}!`
</script>

The lang="ts" attribute enables TypeScript. Without it, the script block is plain JavaScript.

Anything imported in <script setup> — components, functions, types — is available directly in the template. Vue registers PersonCard automatically when you import it here.

The template is standard HTML extended with Vue-specific syntax. It can only have one root element in Vue 2, but Vue 3 supports multiple root elements (fragments):

<template>
<h1>{{ name }}</h1>
<p>Welcome to FamilyTree</p>
</template>

Vue compiles this template into a render function — a JavaScript function that describes what the DOM should look like. When reactive data changes, Vue re-runs the render function and efficiently patches the DOM.

The scoped attribute on <style> makes CSS rules apply only to elements in this component’s template:

<style scoped>
h1 {
color: var(--accent);
font-size: 1.5rem;
}
</style>

Under the hood, Vue adds a unique data attribute (like data-v-3f8a2c) to your component’s elements and rewrites the CSS to target that attribute. A .card rule in one component won’t affect .card elements in other components.

If you need global styles, use a separate CSS file imported in main.ts, or remove the scoped attribute.

Here’s a PersonCard component that shows a name and birth year:

<script setup lang="ts">
defineProps<{
name: string
birthYear?: number
}>()
</script>
<template>
<div class="card">
<span class="name">{{ name }}</span>
<span v-if="birthYear" class="year">b. {{ birthYear }}</span>
</div>
</template>
<style scoped>
.card {
display: flex;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
border-radius: 6px;
}
.name { font-weight: 600; }
.year { color: var(--text-muted); font-size: 0.85rem; }
</style>

This is a complete, self-contained component. Its styles won’t leak. Its logic is co-located with its template. This is what makes SFCs such an effective authoring format.

  1. In your project, create src/components/GreetingCard.vue.
  2. Give it a name prop (string) and a message prop (string).
  3. Display both in the template inside a styled card div.
  4. Import and use <GreetingCard> in src/App.vue, passing values for both props.
  5. Add scoped styles that give the card a border and padding.
  • A .vue SFC has three blocks: <script setup>, <template>, and <style scoped>.
  • <script setup lang="ts"> is the Composition API shorthand — everything declared is available in the template.
  • <template> is standard HTML + Vue syntax; Vue 3 supports multiple root elements.
  • scoped styles only apply to the current component — no style leakage between components.
  • Imported components are automatically registered in <script setup> — no components: {} option needed.