# Components and Single-File Components

Course: [Vue.js](https://www.devtutorials.ai/learn/vue) · Section: Components
Source: https://www.devtutorials.ai/learn/vue/components/components-and-single-file-components

> What components are, component trees, parent and child, the blocks of a Single-File Component, independent instances, local and global registration, naming rules, scoped styles, :deep(), CSS modules, v-bind() in CSS and defineOptions(), and Tailwind CSS and UI libraries.

## Components

- A **component** = a reusable piece of the page with its own HTML, code and style.
- A big page is built from many small components.
- A component used inside another is its **child**. The outer one is the **parent**.
- All the components together form a **component tree**.

**Simple lesson:** a component is a reusable piece of the page.

### A Real-World Example

- Meera's page has a header, a menu and a footer.
- Each part lives in its own file, so it is easy to find and fix.
- `App.vue` puts them together.

</script>

<template>
</template>
`,
    "components/SiteHeader.vue": `<template>
    <header class="head">🍛 Meera's Kitchen</header>
</template>

<style scoped>
.head { font-size: 22px; font-weight: bold; color: #2f9e6e; }
</style>
`,
    "components/MenuList.vue": `<script setup lang="ts">
const dishes: string[] = ["Masala Dosa", "Idli", "Vada"];
</script>

<template>
    <ul>
      <li v-for="d in dishes" :key="d">{{ d }}</li>
    </ul>
</template>
`,
    "components/SiteFooter.vue": `<template>
    <footer>Open 7 am to 10 pm</footer>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

- **What happened:** `App.vue` imported three components and used them like HTML tags.
- Open the `components` folder in the Explorer to see each file.

### What You Will Learn

1. How components form a tree.
2. The three blocks of a `.vue` file.
3. Why each use of a component has its own state.
4. How to register components locally and globally.
5. How to name components and files.
6. How scoped styles and `:deep()` work, and where Tailwind and UI libraries fit.
7. How to use CSS modules.
8. How to use state inside CSS with `v-bind()`.
9. How to set component options with `defineOptions()`.

## The Component Tree

<figure className="vue-fig ngc-fig">
<svg viewBox="0 0 640 210" role="img" aria-labelledby="fig-vue-tree" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-vue-tree">Animation: App sits at the top of the tree. SiteHeader, MenuList and SiteFooter are its children. MenuList has DishCard children. Each level lights up in turn</title>
  <g style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }}>
    <path d="M320 58 L130 100" /><path d="M320 58 L320 100" /><path d="M320 58 L510 100" />
    <path d="M320 138 L230 168" /><path d="M320 138 L410 168" />
  </g>
  <g style={{ fontFamily: "var(--mono)", fontSize: 13, fontWeight: 700 }}>
    <rect x="260" y="20" width="120" height="38" rx="9" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 2 }}><animate attributeName="stroke-width" values="4;4;2;2" keyTimes="0;0.3;0.31;1" dur="6s" repeatCount="indefinite" /></rect>
    <text x="320" y="44" textAnchor="middle" style={{ fill: "var(--ngc-green)" }}>App</text>
    <g><animate attributeName="opacity" values="0.35;0.35;1;1;0.35" keyTimes="0;0.3;0.33;0.95;1" dur="6s" repeatCount="indefinite" /> <rect x="70" y="100" width="120" height="38" rx="9" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 2 }} /><text x="130" y="124" textAnchor="middle" style={{ fill: "var(--ngc-blue)" }}>SiteHeader</text> <rect x="260" y="100" width="120" height="38" rx="9" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 2 }} /><text x="320" y="124" textAnchor="middle" style={{ fill: "var(--ngc-blue)" }}>MenuList</text> <rect x="450" y="100" width="120" height="38" rx="9" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 2 }} /><text x="510" y="124" textAnchor="middle" style={{ fill: "var(--ngc-blue)" }}>SiteFooter</text></g>
    <g><animate attributeName="opacity" values="0.35;0.35;1;1;0.35" keyTimes="0;0.6;0.63;0.95;1" dur="6s" repeatCount="indefinite" /> <rect x="175" y="168" width="110" height="34" rx="9" style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)", strokeWidth: 2 }} /><text x="230" y="190" textAnchor="middle" style={{ fill: "var(--ngc-orange)" }}>DishCard</text> <rect x="355" y="168" width="110" height="34" rx="9" style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)", strokeWidth: 2 }} /><text x="410" y="190" textAnchor="middle" style={{ fill: "var(--ngc-orange)" }}>DishCard</text></g>
  </g>
</svg>
<figcaption>App is the root. Each component can have children of its own.</figcaption>
</figure>

- `App` is the **root component**. You met it in **Introduction to Vue**.
- A parent passes data down to its children. You will learn how in **Props and Events**.

**Simple lesson:** components nest inside each other like a family tree.

## Single-File Components

- A **Single-File Component (SFC)** = one `.vue` file that holds one component.
- It has up to three blocks:
  - `<template>`: what it shows
  - `<script setup>`: its state and functions
  - `<style>`: how it looks
- Vite compiles each `.vue` file into plain JavaScript and CSS.
- In Vue 3 a template may have **several** root elements. Vue 2 allowed only one.

```vue
<script setup lang="ts">
import { ref } from "vue";
const likes = ref(0);
</script>

<template>
  <button class="like" @click="likes++">❤ {{ likes }}</button>
</template>

<style scoped>
.like { color: crimson; }
</style>
```

**Simple lesson:** one `.vue` file = one component = template + script + style.

## Independent Instances

- Each time you use a component, Vue makes a new **instance** of it.
- **Instance** = one live copy, with its own state.
- Three `<LikeButton />` tags = three separate counts.

</script>

<template>
    <p>Dosa <LikeButton /></p>
    <p>Idli <LikeButton /></p>
    <p>Vada <LikeButton /></p>
</template>
`,
    "LikeButton.vue": `<script setup lang="ts">

const likes = ref<number>(0);
</script>

<template>
    <button @click="likes++">❤ {{ likes }}</button>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

State created at the top of a separate `.ts` file is shared by every instance that imports it. Create per-instance state inside the component's own `<script setup>`.

**Simple lesson:** every use of a component gets its own copy of the state.

In Vue DevTools, the Components tab shows this tree. Click a component to see its own state. In this lab, each box's Explorer shows the files instead.

## Registering Components

### Local Registration

- With `<script setup>`, importing a component is enough. It is ready in the template.
- This is **local registration**: only this file can use it.
- Local is the default choice. Unused components are left out of the build.

### Global Registration

- `app.component("Name", Component)` in `main.ts` makes a component usable **everywhere**, with no import.
- Use it only for small parts used on almost every page, such as an icon or a price tag.
- Too many global components make it hard to see where a tag comes from.

    <p>Dosa <PriceTag :amount="90" /></p>
    <p>Lassi <PriceTag :amount="70" /></p>
</template>
`,
    "PriceTag.vue": `<script setup lang="ts">
defineProps({ amount: { type: Number, required: true } });
</script>

<template>
    <span class="price">₹{{ amount }}</span>
</template>
`,
    "main.ts": `import { createApp } from "vue";

const app = createApp(App);
app.component("PriceTag", PriceTag);
app.mount("#app");
`,
  }}
/>

**Simple lesson:** import to use a component in one file. Register globally only for tiny, everywhere parts.

## Naming Components

- Use **PascalCase** for names: `DishCard`, `SiteHeader`.
- Use **two or more words**, so names never clash with HTML tags like `<header>`.
- Name the file the same: `DishCard.vue`.
- In templates you can write `<DishCard />` or `<dish-card />`. PascalCase is clearer.
- Self-close a component with no content: `<DishCard />`.

<div className="tscroll">

| Good | Avoid | Why |
|---|---|---|
| `DishCard.vue` | `Card.vue` | One word may clash with HTML |
| `SiteHeader` | `Header` | `<header>` is already an HTML tag |
| `<DishCard />` | `<DishCard></DishCard>` | Shorter when it has no content |

</div>

## Styling Components

### Scoped Styles

- `<style scoped>` applies only to this component's own elements.
- Vue adds a unique attribute like `data-v-1a2b3c` to the elements and the CSS.
- So `.title` in one component cannot change `.title` in another.
- A child's **root element** gets the parent's scoped style too.

</script>

<template>
    <h3 class="title">App title (green, scoped to App)</h3>
    <div class="menu">
    </div>
</template>

<style scoped>
.title { color: #2f9e6e; }
.menu :deep(.price) { background: gold; }
</style>
`,
    "DishCard.vue": `<template>
    <div>
      <h3 class="title">Card title (not green)</h3>
      <span class="price">₹90 (gold, from App's :deep)</span>
    </div>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

- **What happened:** App's `.title` rule did not reach the card's title.
- `:deep(.price)` reached inside the child on purpose.

### CSS Modules

- `<style module>` turns each class into a unique name.
- Use the classes in the template through `$style`: `:class="$style.badge"`.
- Good for big apps where class names might clash.

### v-bind() in CSS

- `v-bind(name)` inside `<style>` uses a value from the script.
- When the value changes, the style changes.

const accent = ref<string>("crimson");
</script>

<template>
    <p :class="$style.badge">Chef's special</p>
    <button @click="accent = accent === 'crimson' ? 'teal' : 'crimson'">Change colour</button>
</template>

<style module>
.badge {
    color: v-bind(accent);
    font-weight: bold;
    border: 2px solid v-bind(accent);
    padding: 4px 8px;
    display: inline-block;
}
</style>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

Pick one style approach per project. Most Vue projects use `<style scoped>` for almost everything.

### Tailwind CSS and UI Libraries

- **Tailwind CSS** = small utility classes such as `p-4` and `text-green-700`, written in the template. No separate CSS file per component.
- Tailwind v4 needs one Vite plugin and one CSS line:

```ts
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({ plugins: [vue(), tailwindcss()] });
```

```css
/* src/style.css, imported in main.ts */
@import "tailwindcss";
```

```vue
<template>
  <button class="rounded-lg bg-green-700 px-4 py-2 text-white hover:bg-green-800">Order</button>
</template>
```

- **UI libraries** give ready-made, accessible components: buttons, tables, dialogs, date pickers.

<div className="tscroll">

| Library | Style | Good for |
|---|---|---|
| Vuetify | Material Design | Admin apps, dashboards |
| PrimeVue | Many themes, or unstyled | Business apps with big tables and forms |
| Element Plus | Clean desktop look | Internal tools |
| Nuxt UI / shadcn-vue | Tailwind-based | Custom-looking sites |

</div>

- Install one with npm, then `app.use(Library)` or import components one by one (smaller bundles).

The preview boxes have no internet, so Tailwind and UI libraries cannot load here. Scoped CSS, CSS modules and `v-bind()` above work the same way in every project.

## defineOptions()

- `defineOptions()` (Vue 3.3) sets options that have no other place in `<script setup>`.
- The common ones are `name` (shown in Vue DevTools) and `inheritAttrs`.
- You will use `inheritAttrs` in **Slots, Attributes and Provide/Inject**.

</script>

<template>
</template>
`,
    "OrderBadge.vue": `<script setup lang="ts">

defineOptions({ name: "KitchenOrderBadge" });

console.log("This component is named:", getCurrentInstance()?.type.name);
</script>

<template>
    <span>Order #501</span>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

**Simple lesson:** scoped CSS by default, `:deep()` to reach a child, `$style` for modules, `v-bind()` for live values.

## TL;DR

- **Component** = a reusable piece of the page.
- **Component tree** = the root `App` with children nested inside.
- **SFC** = a `.vue` file with `<template>`, `<script setup>` and `<style>`.
- **Instance** = each use of a component, with its own state.
- **Local registration** = import it in `<script setup>`. The default.
- **Global registration** = `app.component("Name", C)` in `main.ts`.
- **Naming** = PascalCase, two or more words, same file name.
- **`<style scoped>`** = styles only this component. `:deep()` reaches children.
- **`<style module>` / `v-bind()`** = unique class names via `$style` / script values in CSS.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| One-word names | `Header.vue` is fine | `SiteHeader.vue` | Clashes with the HTML `<header>` tag |
| Forgetting the import | Vue finds components by itself | Import it, or register globally | "Failed to resolve component" warning |
| Shared state in a module | Each instance gets its own copy | Create state inside the component | All instances change together |
| Styling a child's inner element from the parent | Scoped CSS reaches children | Use `:deep(.class)` | The style is ignored |
| Everything global | Global is easier | Import locally by default | Bigger builds, unclear origins |
| `class="$style.badge"` | Plain class reads `$style` | Write `:class="$style.badge"` | The class is the literal text |

</div>

## Check Yourself

## Exercises

## What to Remember

- A page is a tree of components, with `App` at the root.
- A `.vue` file holds one component: template, script setup and style.
- Each use of a component has its own state.
- Import components locally. Register globally only tiny, everywhere parts.
- Use `<style scoped>`. Reach into children with `:deep()`.

Next: **Props and Events**
