Defining and Using a Component
A component is a .vue file; importing it makes it a tag in your template, with naming rules that avoid clashing with HTML.
import MyThing from './MyThing.vue' → <my-thing />A component is one reusable piece of interface: its markup, its logic and its styles in a single file. In a Vue project that file ends in .vue and is called a single-file component. It holds a <script setup> block for logic, a <template> block for markup, and an optional <style scoped> block whose rules apply to that component only.
Using one
Import the file inside another component's <script setup>. The import is the registration: the name you import under becomes a tag in that component's template, with no separate registration step. Vue resolves <counter-button /> and <CounterButton /> to the same import, so the two tag forms are the same thing. Self-closing tags work here because the Vue compiler reads the template, not the browser's HTML parser.
Names
Name the file in PascalCase and use at least two words: CounterButton.vue, not Button.vue. A one-word lowercase tag can collide with a current or future HTML element.
What trips people up
An import is local to the file that wrote it. A component imported in App.vue is not usable in a child's template; that file needs its own import. The alternative is app.component('CounterButton', CounterButton) in main.ts, which makes the tag available in every template but keeps the component in the bundle whether anything uses it or not.
Example
<!-- src/components/CounterButton.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
function add(): void {
count.value += 1
}
</script>
<template>
<button type="button" @click="add">
Clicked {{ count }} times
</button>
</template>
<style scoped>
button {
padding: 0.5rem 1rem;
}
</style>When to use it
- Pull a repeated card or button out of a long page into its own file.
- Give a piece of interface private state, so two copies on one screen do not interfere.
- Register a base button globally so you stop importing it in thirty files.
More examples
Importing it and using it twice
The import line is all the registration there is. Both tags refer to that same import: kebab-case and PascalCase are interchangeable in a .vue template. Each tag creates its own instance with its own count, so clicking one button does not change the other. Reach for this shape whenever the same piece of interface appears more than once on a screen.
<!-- src/App.vue -->
<script setup lang="ts">
import CounterButton from './components/CounterButton.vue'
</script>
<template>
<main>
<h3>Two independent counters</h3>
<counter-button />
<CounterButton />
</main>
</template>
<style scoped>
main {
display: grid;
gap: 0.5rem;
justify-items: start;
}
</style>Registering a component globally
app.component(name, component) registers a component for the whole application, which is the longhand alternative to importing it in each file that needs it. Use it for the two or three components that genuinely appear everywhere, such as a base button. Global registration costs you tree-shaking: the component ships in the bundle even if no template uses it, and a reader of a template cannot tell where the tag came from.
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import CounterButton from './components/CounterButton.vue'
const app = createApp(App)
// Every template can now use <counter-button /> without importing it.
app.component('CounterButton', CounterButton)
app.mount('#app')The older Options API form
Worth recognising rather than writing. Before <script setup>, a component exported an object and listed its children in a components option. You will meet this in older projects and in tutorials written for Vue 2. This course uses <script setup> throughout, where the import alone does the same job.
<!-- src/App.vue, written in the older style -->
<script lang="ts">
import { defineComponent } from 'vue'
import CounterButton from './components/CounterButton.vue'
export default defineComponent({
components: {
CounterButton
}
})
</script>
<template>
<counter-button />
</template>
Discussion