DDD Component Architecture
Domain-driven folder structure and conventions for Vue/Nuxt component organisation. Apply when creating, moving, or renaming components and translation keys.
Structure
Path: components/[Domain]/[Feature]/[Function]/
File: [Name].vue (short names, Nuxt auto-generates full path prefix)
Kebab folders — Nuxt handles PascalCase resolution.
Hierarchy
- Domain - business vertical (
patient-care, records, inventory, catalog, calendar, reporting, settings)
- Feature - logical module (
plans, laboratory)
- Function - UI pattern (
form, list, card, chart, dialog, menu)
- Component - the Vue file
Function Types
| Function |
Purpose |
form/, form/fields |
inputs, fieldsets, wizards |
list/ |
tables, rows, grids |
card/ |
summaries, widgets |
chart/ |
visualisations, graphs |
dialog/ |
modals, drawers |
menu/ |
context menus, dropdowns |
Examples
| Path |
Filename |
Nuxt Component Name |
patient-care/plans/list/ |
Active.vue |
PatientCarePlansListActive |
patient-care/laboratory/card/ |
Result.vue |
PatientCareLaboratoryCardResult |
inventory/stock/actions/ |
Adjust.vue |
InventoryStockActionsAdjust |
Rules
- Simple forms can be contained directly within dialog components
- Extract forms to
form/ only when reused across multiple dialogs or contexts
- Generic components (
StatusBadge) — base/ or ui/
- Domain-specific shared components stay in primary domain, imported elsewhere
- No barrel files (
index.ts) in domain folders — direct imports only
Creating Components
Before creating, verify:
- Path provides context — folder structure determines the full component name
- Short descriptive filename —
Active.vue, Result.vue, Adjust.vue
- No generic names — never
Main.vue, Default.vue, Component.vue
- Correct function folder — form logic in
form/, not dialog/
- Single responsibility — one component = one purpose
- Import direction — lower domains can import from higher, not reverse
Translations
Translation keys follow the same structure as file paths. Use dots instead of hyphens.
Path: pages.[domain].[feature].[function].*
Folder path to translation path:
components/inventory/stock-locations/ — pages.inventory.stock.locations
components/patient-care/plans/ — pages.patientCare.plans
Structure:
{
"pages": {
"inventory": {
"stock": {
"locations": {
"list": {
"header": "Stock Locations",
"createButton": "Add Location",
"filters": { "searchPlaceholder": "Search locations..." }
},
"form": {
"createTitle": "Create Stock Location",
"editTitle": "Edit Stock Location",
"nameLabel": "Name",
"cancel": "Cancel",
"validation": { "nameRequired": "Name is required" }
},
"toast": {
"create": {
"success": "Stock location created",
"error": "Failed to create"
},
"edit": {
"success": "Stock location updated",
"error": "Failed to update"
},
"delete": {
"success": "Stock location deleted",
"error": "Failed to delete"
}
}
}
}
}
}
}
Usage in components:
// Single translation variable scoped to feature
const t = useTranslation('pages.inventory.stock.locations')
// Access nested keys via dot notation
t('list.header') // "Stock Locations"
t('list.createButton') // "Add Location"
t('form.nameLabel') // "Name"
t('form.validation.nameRequired') // "Name is required"
t('toast.create.success') // "Stock location created"
t('toast.edit.error') // "Failed to update"
Function categories match component folders:
| Function |
Purpose |
list |
Headers, buttons, filters, column labels for list views |
form |
Labels, titles, buttons, validation for forms |
toast |
Success/error notification messages (nested by action: create, edit, delete) |
dialog |
Dialog-specific text (if needed beyond form titles) |
Edge Cases
| Scenario |
Resolution |
| Component used by 2+ domains equally |
shared/[feature]/ |
| Form reused in multiple dialogs |
Extract to [...]Form.vue, dialog imports form |
| Simple form in single dialog |
Keep form inline within dialog component |
| Nested feature |
patient-care/laboratory/results/ — PatientCareLaboratoryResults[Function].vue |
| Page-level component |
pages/, not components/ |
| Composable |
composables/[domain]/use[Domain][Feature].ts |
Common Translations Reference
Use const tCommon = useTranslation('common') for reusable strings:
| Key |
Values |
generalActions.* |
save, cancel, delete, edit, add, update, archive, retry, search, confirm, restore, back, continue, apply, clear, goBack, share, print |
generalStatuses.* |
active, archived, draft, sent, sending, open, created, creating, ready, rejected, updated, ongoing, all, loading |
boolean.* |
yes, no |
dateAndTime.* |
date, time, hour, startDate, endDate, years_one/other, months_one/other, days_one/other |
dataTable.columns.* |
id, date, name, email, phone, status, actions, firstName, lastName, client, veterinarian, clinic |
dataTable.emptyState.* |
header, description, descriptionNoMatches, buttonClearFilters |
generalFormFields.* |
email, phone, tags, remarks, reasonType, reason, veterinarian, department |
enums.* |
invoiceStatus, invoicePaymentStatus, invoiceType, paymentMethod, patientSex, consultationTypes, consultationStatus |
Example usage:
const tCommon = useTranslation('common')
tCommon('generalActions.save') // "Save"
tCommon('generalActions.cancel') // "Cancel"
tCommon('boolean.yes') // "Yes"
tCommon('dataTable.columns.name') // "Name"
1---2name: ddd-architecture3description: DDD component architecture for Vue/Nuxt projects — folder structure, naming conventions, translation keys, and component organisation. Use when creating components, choosing file paths, naming files, structuring translations, or organising domain folders.4---56# DDD Component Architecture78Domain-driven folder structure and conventions for Vue/Nuxt component organisation. Apply when creating, moving, or renaming components and translation keys.910## Structure1112Path: `components/[Domain]/[Feature]/[Function]/`13File: `[Name].vue` (short names, Nuxt auto-generates full path prefix)1415Kebab folders — Nuxt handles PascalCase resolution.1617## Hierarchy18191. **Domain** - business vertical (`patient-care`, `records`, `inventory`, `catalog`, `calendar`, `reporting`, `settings`)202. **Feature** - logical module (`plans`, `laboratory`)213. **Function** - UI pattern (`form`, `list`, `card`, `chart`, `dialog`, `menu`)224. **Component** - the Vue file2324## Function Types2526| Function | Purpose |27| ---------------------- | -------------------------- |28| `form/`, `form/fields` | inputs, fieldsets, wizards |29| `list/` | tables, rows, grids |30| `card/` | summaries, widgets |31| `chart/` | visualisations, graphs |32| `dialog/` | modals, drawers |33| `menu/` | context menus, dropdowns |3435## Examples3637| Path | Filename | Nuxt Component Name |38| ------------------------------- | ------------ | ---------------------------------- |39| `patient-care/plans/list/` | `Active.vue` | `PatientCarePlansListActive` |40| `patient-care/laboratory/card/` | `Result.vue` | `PatientCareLaboratoryCardResult` |41| `inventory/stock/actions/` | `Adjust.vue` | `InventoryStockActionsAdjust` |4243## Rules4445- Simple forms can be contained directly within dialog components46- Extract forms to `form/` only when reused across multiple dialogs or contexts47- Generic components (`StatusBadge`) — `base/` or `ui/`48- Domain-specific shared components stay in primary domain, imported elsewhere49- No barrel files (`index.ts`) in domain folders — direct imports only5051## Creating Components5253Before creating, verify:54551. **Path provides context** — folder structure determines the full component name562. **Short descriptive filename** — `Active.vue`, `Result.vue`, `Adjust.vue`573. **No generic names** — never `Main.vue`, `Default.vue`, `Component.vue`584. **Correct function folder** — form logic in `form/`, not `dialog/`595. **Single responsibility** — one component = one purpose606. **Import direction** — lower domains can import from higher, not reverse6162## Translations6364Translation keys follow the same structure as file paths. Use dots instead of hyphens.6566**Path:** `pages.[domain].[feature].[function].*`6768**Folder path to translation path:**6970- `components/inventory/stock-locations/` — `pages.inventory.stock.locations`71- `components/patient-care/plans/` — `pages.patientCare.plans`7273**Structure:**7475```json76{77 "pages": {78 "inventory": {79 "stock": {80 "locations": {81 "list": {82 "header": "Stock Locations",83 "createButton": "Add Location",84 "filters": { "searchPlaceholder": "Search locations..." }85 },86 "form": {87 "createTitle": "Create Stock Location",88 "editTitle": "Edit Stock Location",89 "nameLabel": "Name",90 "cancel": "Cancel",91 "validation": { "nameRequired": "Name is required" }92 },93 "toast": {94 "create": {95 "success": "Stock location created",96 "error": "Failed to create"97 },98 "edit": {99 "success": "Stock location updated",100 "error": "Failed to update"101 },102 "delete": {103 "success": "Stock location deleted",104 "error": "Failed to delete"105 }106 }107 }108 }109 }110 }111}112```113114**Usage in components:**115116```typescript117// Single translation variable scoped to feature118const t = useTranslation('pages.inventory.stock.locations')119120// Access nested keys via dot notation121t('list.header') // "Stock Locations"122t('list.createButton') // "Add Location"123t('form.nameLabel') // "Name"124t('form.validation.nameRequired') // "Name is required"125t('toast.create.success') // "Stock location created"126t('toast.edit.error') // "Failed to update"127```128129**Function categories match component folders:**130131| Function | Purpose |132| -------- | ----------------------------------------------------------------------------- |133| `list` | Headers, buttons, filters, column labels for list views |134| `form` | Labels, titles, buttons, validation for forms |135| `toast` | Success/error notification messages (nested by action: create, edit, delete) |136| `dialog` | Dialog-specific text (if needed beyond form titles) |137138## Edge Cases139140| Scenario | Resolution |141| ------------------------------------ | --------------------------------------------------------------------------------- |142| Component used by 2+ domains equally | `shared/[feature]/` |143| Form reused in multiple dialogs | Extract to `[...]Form.vue`, dialog imports form |144| Simple form in single dialog | Keep form inline within dialog component |145| Nested feature | `patient-care/laboratory/results/` — `PatientCareLaboratoryResults[Function].vue` |146| Page-level component | `pages/`, not `components/` |147| Composable | `composables/[domain]/use[Domain][Feature].ts` |148149## Common Translations Reference150151Use `const tCommon = useTranslation('common')` for reusable strings:152153| Key | Values |154| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |155| `generalActions.*` | save, cancel, delete, edit, add, update, archive, retry, search, confirm, restore, back, continue, apply, clear, goBack, share, print |156| `generalStatuses.*` | active, archived, draft, sent, sending, open, created, creating, ready, rejected, updated, ongoing, all, loading |157| `boolean.*` | yes, no |158| `dateAndTime.*` | date, time, hour, startDate, endDate, years_one/other, months_one/other, days_one/other |159| `dataTable.columns.*` | id, date, name, email, phone, status, actions, firstName, lastName, client, veterinarian, clinic |160| `dataTable.emptyState.*` | header, description, descriptionNoMatches, buttonClearFilters |161| `generalFormFields.*` | email, phone, tags, remarks, reasonType, reason, veterinarian, department |162| `enums.*` | invoiceStatus, invoicePaymentStatus, invoiceType, paymentMethod, patientSex, consultationTypes, consultationStatus |163164**Example usage:**165166```ts167const tCommon = useTranslation('common')168tCommon('generalActions.save') // "Save"169tCommon('generalActions.cancel') // "Cancel"170tCommon('boolean.yes') // "Yes"171tCommon('dataTable.columns.name') // "Name"172```