Files
agent-skills/skills/vue-best-practices/reference/sfc-separation-of-concerns-colocate.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

5.5 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Colocate Template, Script, and Style in SFCs for Maintainability MEDIUM Separating template, logic, and styles into different files reduces component cohesion and maintainability best-practice
vue3
sfc
architecture
separation-of-concerns
maintainability

Colocate Template, Script, and Style in SFCs for Maintainability

Impact: MEDIUM - Vue SFCs are designed to colocate template, logic, and styles because these concerns are inherently coupled within a component. Separating them into different files based on file type (not concern) makes components harder to understand and maintain.

Task Checklist

  • Keep template, script, and style in the same .vue file by default
  • Only use src imports when files become extremely large (500+ lines)
  • Group related components in folders rather than separating by file type
  • Use component composition to manage complexity instead of file splitting

Not Recommended:

components/
├── UserCard.vue          # Just template
├── UserCard.js           # Logic
├── UserCard.css          # Styles

Recommended:

<!-- components/UserCard.vue - Everything in one file -->
<script setup>
import { computed } from 'vue'
import { useUserStatus } from '@/composables/useUserStatus'

const props = defineProps({
  user: { type: Object, required: true }
})

const { isOnline } = useUserStatus(props.user.id)
const displayName = computed(() =>
  `${props.user.firstName} ${props.user.lastName}`
)
</script>

<template>
  <div class="user-card">
    <img :src="user.avatar" :alt="displayName" class="avatar" />
    <div class="info">
      <h3 class="name">{{ displayName }}</h3>
      <span :class="['status', { online: isOnline }]">
        {{ isOnline ? 'Online' : 'Offline' }}
      </span>
    </div>
  </div>
</template>

<style scoped>
.user-card {
  display: flex;
  padding: 1rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
}

.avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
}

.info {
  margin-left: 1rem;
}

.name {
  margin: 0 0 0.25rem;
}

.status {
  font-size: 0.875rem;
  color: #666;
}

.status.online {
  color: #22c55e;
}
</style>

Why Colocation is Preferred

1. Coupled Concerns Should Stay Together

Template, logic, and styles within a component are inherently coupled:

  • Template references reactive data from the script
  • Styles target classes used in the template
  • Changes in one often require changes in others
<!-- Everything references each other -->
<script setup>
const isExpanded = ref(false)  // Used by template and affects styling
</script>

<template>
  <!-- Uses isExpanded from script, applies class for styling -->
  <div :class="{ expanded: isExpanded }">...</div>
</template>

<style scoped>
/* Targets class from template, shows visual state from script */
.expanded { max-height: 500px; }
</style>

2. Easier Navigation and Understanding

Single file = single place to look:

<!-- Open one file, understand the whole component -->
<script setup>
// All logic here
</script>

<template>
  <!-- All markup here -->
</template>

<style scoped>
/* All styles here */
</style>

3. Better Refactoring Experience

Renaming a class? Everything is in one file:

<!-- Before: .card-title used in both template and style -->
<!-- After: Rename to .user-title in one file, one commit -->

When Src Imports Are Acceptable

For genuinely large components (rare), use src imports:

<!-- LargeDataTable.vue -->
<script src="./LargeDataTable.ts" lang="ts"></script>
<template src="./LargeDataTable.html"></template>
<style src="./LargeDataTable.scss" scoped lang="scss"></style>

But first, consider if the component should be split into smaller components.

Managing Complexity Without File Splitting

Extract Logic to Composables

// composables/useDataTable.ts
export function useDataTable(initialData: Ref<Item[]>) {
  const sortColumn = ref<string | null>(null)
  const sortDirection = ref<'asc' | 'desc'>('asc')
  const currentPage = ref(1)

  const sortedData = computed(() => { ... })
  const paginatedData = computed(() => { ... })

  function sort(column: string) { ... }
  function goToPage(page: number) { ... }

  return {
    sortColumn,
    sortDirection,
    currentPage,
    sortedData,
    paginatedData,
    sort,
    goToPage
  }
}
<!-- DataTable.vue - Component stays focused -->
<script setup>
import { useDataTable } from '@/composables/useDataTable'

const props = defineProps(['items'])
const { sortedData, sort, goToPage } = useDataTable(toRef(props, 'items'))
</script>

Break Into Smaller Components

<!-- Instead of one 500-line component -->
<template>
  <div class="data-table">
    <DataTableHeader @sort="handleSort" />
    <DataTableBody :rows="visibleRows" />
    <DataTablePagination
      :total="total"
      :current="currentPage"
      @change="handlePageChange"
    />
  </div>
</template>

The Real Separation of Concerns

True separation of concerns in Vue means:

  • Components handle their own template/logic/style (coupled by nature)
  • Composables handle reusable stateful logic
  • Utilities handle pure functions
  • Stores handle global state

This is more maintainable than separating HTML/CSS/JS into different files.

Reference