Component Basics and Single-File Components
Breaking a UI into reusable components, and how they're imported and composed in practice.
2 min read
A component is a self-contained, reusable piece of UI — its own template, its own logic, optionally its own styles. Once you have more than a trivial page, breaking it into components isn't a nicety, it's what keeps the codebase navigable: instead of one enormous App.vue, you get a NavBar, a ProductCard, a SearchInput, each understandable on its own.
A simple component
<!-- src/components/ProductCard.vue -->
<script setup>
defineProps(["name", "price"]);
</script>
<template>
<div class="card">
<h3>{{ name }}</h3>
<p>${{ price }}</p>
</div>
</template>
<style scoped>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
}
</style>(defineProps is covered in full in the next lesson — for now, just note that it's how a component declares what data it accepts from whoever uses it.)
Using it
<!-- src/App.vue -->
<script setup>
import ProductCard from "./components/ProductCard.vue";
</script>
<template>
<main>
<ProductCard name="Keyboard" price="60" />
<ProductCard name="Mouse" price="25" />
</main>
</template>With <script setup>, importing a component is all it takes to make it available in the template — no separate registration step, and no need to add it to a components: {} option the way the Options API requires. Vue's compiler sees the import and the matching tag name and wires them together automatically.
Naming conventions
Component files and their tags are conventionally PascalCase (ProductCard.vue, <ProductCard />), which visually distinguishes a custom component from a native HTML element like <div> or <button> at a glance. Vue's template compiler also accepts kebab-case tags (<product-card />) referring to the same PascalCase-imported component, but consistency within a codebase matters more than which convention you pick.
When to split into a new component
There's no fixed rule, but a few signals are reliable:
- You're repeating the same chunk of template in more than one place — extract it once, use it everywhere.
- A section of the template has its own independent state that doesn't need to be visible to the rest of the page — giving it its own component keeps that state properly encapsulated.
- The file is getting hard to scan — if you have to scroll past hundreds of lines to see how two sections of the UI relate, that's a sign the boundaries between them deserve to be explicit files, not just visual sections of one giant template.
Small, focused components with clear boundaries — described formally by props coming in and events going out, both covered in the next two lessons — are what make a Vue codebase scale past a handful of files.