Skip to content

Atomic Architecture: Revolutionizing Vue and Nuxt Project Structure

Published: at 

Introduction

Clear writing requires clear thinking. The same is valid for coding. Throwing all components into one folder may work when starting a personal project. But as projects grow, especially with larger teams, this approach leads to problems:

Atomic Design offers a solution. Let’s examine how to apply it to a Nuxt project.

What is Atomic Design

atomic design diagram brad Frost

Brad Frost developed Atomic Design as a methodology for creating design systems. It is structured into five levels inspired by chemistry:

  1. Atoms: Basic building blocks (e.g. form labels, inputs, buttons)
  2. Molecules: Simple groups of UI elements (e.g. search forms)
  3. Organisms: Complex components made of molecules/atoms (e.g. headers)
  4. Templates: Page-level layouts
  5. Pages: Specific instances of templates with content

For Nuxt, we can adapt these definitions:

Code Example: Before and After

Consider this non-Atomic Design todo app component:

Screenshot of ToDo App

<template>
  <div: HTMLAttributes & ReservedPropsdiv HTMLAttributes.class?: anyclass="container mx-auto p-4">
    <h1: HTMLAttributes & ReservedPropsh1 HTMLAttributes.class?: anyclass="text-2xl font-bold mb-4 text-gray-800 dark:text-gray-200">Todo App</h1: HTMLAttributes & ReservedPropsh1>
    
    <!-- Add Todo Form -->
    <form: FormHTMLAttributes & ReservedPropsform @onSubmit?: ((payload: Event) => void) | undefinedsubmit.prevent="addTodo: () => Promise<void>addTodo" HTMLAttributes.class?: anyclass="mb-4">
      <input: InputHTMLAttributes & ReservedPropsinput
        InputHTMLAttributes.value?: anyv-model="newTodo: stringnewTodo"
        InputHTMLAttributes.type?: InputTypeHTMLAttribute | undefinedtype="text"
        InputHTMLAttributes.placeholder?: string | undefinedplaceholder="Enter a new todo"
        HTMLAttributes.class?: anyclass="border p-2 mr-2 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded"
      />
      <button: ButtonHTMLAttributes & ReservedPropsbutton ButtonHTMLAttributes.type?: "reset" | "submit" | "button" | undefinedtype="submit" HTMLAttributes.class?: anyclass="bg-blue-500 hover:bg-blue-600 text-white p-2 rounded transition duration-300">
        Add Todo
      </button: ButtonHTMLAttributes & ReservedPropsbutton>
    </form: FormHTMLAttributes & ReservedPropsform>
    
    <!-- Todo List -->
    <ul: HTMLAttributes & ReservedPropsul HTMLAttributes.class?: anyclass="space-y-2">
      <li: LiHTMLAttributes & ReservedPropsli
        v-for="const todo: Todotodo in todos: Todo[]todos"
        key?: PropertyKey | undefined:key?: PropertyKey | undefinedkey="const todo: Todotodo.Todo.id: numberid"
        HTMLAttributes.class?: anyclass="flex justify-between items-center p-3 bg-gray-100 dark:bg-gray-700 rounded shadow-sm"
      >
        <span: HTMLAttributes & ReservedPropsspan HTMLAttributes.class?: anyclass="text-gray-800 dark:text-gray-200">{{ const todo: Todotodo.Todo.text: stringtext }}</span: HTMLAttributes & ReservedPropsspan>
        <button: ButtonHTMLAttributes & ReservedPropsbutton
          @onClick?: ((payload: MouseEvent) => void) | undefinedclick="deleteTodo: (id: number) => Promise<void>deleteTodo(const todo: Todotodo.Todo.id: numberid)"
          HTMLAttributes.class?: anyclass="bg-red-500 hover:bg-red-600 text-white p-1 rounded transition duration-300"
        >
          Delete
        </button: ButtonHTMLAttributes & ReservedPropsbutton>
      </li: LiHTMLAttributes & ReservedPropsli>
    </ul: HTMLAttributes & ReservedPropsul>
  </div: HTMLAttributes & ReservedPropsdiv>
</template>

<script setup lang="ts">
import { function ref<T>(value: T): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
, const onMounted: CreateHook<any>onMounted } from 'vue'
interface Todo { Todo.id: numberid: number Todo.text: stringtext: string } const const newTodo: Ref<string, string>newTodo = ref<string>(value: string): Ref<string, string> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
('')
const
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
=
ref<Todo[]>(value: Todo[]): Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
<Todo[]>([])
const const fetchTodos: () => Promise<void>fetchTodos = async () => { // Simulating API call
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
.
Ref<{ id: number; text: string; }[], Todo[] | { id: number; text: string; }[]>.value: Todo[] | {
    id: number;
    text: string;
}[]
value
= [
{ id: numberid: 1, text: stringtext: 'Learn Vue.js' }, { id: numberid: 2, text: stringtext: 'Build a Todo App' }, { id: numberid: 3, text: stringtext: 'Study Atomic Design' } ] } const const addTodo: () => Promise<void>addTodo = async () => { if (const newTodo: Ref<string, string>newTodo.Ref<string, string>.value: stringvalue.String.trim(): string
Removes the leading and trailing white space and line terminator characters from a string.
trim
()) {
// Simulating API call const const newTodoItem: TodonewTodoItem: Todo = { Todo.id: numberid: var Date: DateConstructor
Enables basic storage and retrieval of dates and times.
Date
.DateConstructor.now(): number
Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now
(),
Todo.text: stringtext: const newTodo: Ref<string, string>newTodo.Ref<string, string>.value: stringvalue }
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
.
Ref<{ id: number; text: string; }[], Todo[] | { id: number; text: string; }[]>.value: {
    id: number;
    text: string;
}[]
value
.
Array<{ id: number; text: string; }>.push(...items: {
    id: number;
    text: string;
}[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
(const newTodoItem: TodonewTodoItem)
const newTodo: Ref<string, string>newTodo.Ref<string, string>.value: stringvalue = '' } } const const deleteTodo: (id: number) => Promise<void>deleteTodo = async (id: numberid: number) => { // Simulating API call
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
.
Ref<{ id: number; text: string; }[], Todo[] | { id: number; text: string; }[]>.value: Todo[] | {
    id: number;
    text: string;
}[]
value
=
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
.
Ref<{ id: number; text: string; }[], Todo[] | { id: number; text: string; }[]>.value: {
    id: number;
    text: string;
}[]
value
.
Array<{ id: number; text: string; }>.filter(predicate: (value: {
    id: number;
    text: string;
}, index: number, array: {
    id: number;
    text: string;
}[]) => unknown, thisArg?: any): {
    id: number;
    text: string;
}[] (+1 overload)
Returns the elements of an array that meet the condition specified in a callback function.
@parampredicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
filter
(
todo: {
    id: number;
    text: string;
}
todo
=>
todo: {
    id: number;
    text: string;
}
todo
.id: numberid !== id: numberid)
} function onMounted(hook: any, target?: ComponentInternalInstance | null): voidonMounted(const fetchTodos: () => Promise<void>fetchTodos) </script>

This approach leads to large, difficult-to-maintain components. Let’s refactor using Atomic Design:

This will be the refactored structure

📐 Template (Layout)

   └─── 📄 Page (TodoApp)

        └─── 📦 Organism (TodoList)

             ├─── 🧪 Molecule (TodoForm)

    ├─── ⚛️ Atom (BaseInput)
    └─── ⚛️ Atom (BaseButton)

             └─── 🧪 Molecule (TodoItems)

                  └─── 🧪 Molecule (TodoItem) [multiple instances]

                       ├─── ⚛️ Atom (BaseText)
                       └─── ⚛️ Atom (BaseButton)

Refactored Components

Tempalte Default

<template>
  <div: HTMLAttributes & ReservedPropsdiv HTMLAttributes.class?: anyclass="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-300">
    <header: HTMLAttributes & ReservedPropsheader HTMLAttributes.class?: anyclass="bg-white dark:bg-gray-800 shadow">
      <nav: HTMLAttributes & ReservedPropsnav HTMLAttributes.class?: anyclass="container mx-auto px-4 py-4 flex justify-between items-center">
        <type NuxtLink: unknownNuxtLink to: stringto="/" class: stringclass="text-xl font-bold">Todo App</NuxtLink>
        <import ThemeToggleThemeToggle />
      </nav: HTMLAttributes & ReservedPropsnav>
    </header: HTMLAttributes & ReservedPropsheader>
    <main: HTMLAttributes & ReservedPropsmain HTMLAttributes.class?: anyclass="container mx-auto px-4 py-8">
      <default?(_: {}): anyslot />
    </main: HTMLAttributes & ReservedPropsmain>
  </div: HTMLAttributes & ReservedPropsdiv>
</template>

<script setup lang="ts">
import import ThemeToggleThemeToggle from '~/components/ThemeToggle.vue'
</script>

Pages

<script setup lang="ts">
import { function ref<T>(value: T): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
, const onMounted: CreateHook<any>onMounted } from 'vue'
import import TodoListTodoList from '../components/organisms/TodoList' interface Todo { Todo.id: numberid: number Todo.text: stringtext: string } const
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
=
ref<Todo[]>(value: Todo[]): Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
<Todo[]>([])
const const fetchTodos: () => Promise<void>fetchTodos = async () => { // Simulating API call
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
.
Ref<{ id: number; text: string; }[], Todo[] | { id: number; text: string; }[]>.value: Todo[] | {
    id: number;
    text: string;
}[]
value
= [
{ id: numberid: 1, text: stringtext: 'Learn Vue.js' }, { id: numberid: 2, text: stringtext: 'Build a Todo App' }, { id: numberid: 3, text: stringtext: 'Study Atomic Design' } ] } const const addTodo: (text: string) => Promise<void>addTodo = async (text: stringtext: string) => { // Simulating API call const const newTodoItem: TodonewTodoItem: Todo = { Todo.id: numberid: var Date: DateConstructor
Enables basic storage and retrieval of dates and times.
Date
.DateConstructor.now(): number
Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now
(),
Todo.text: stringtext }
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
.
Ref<{ id: number; text: string; }[], Todo[] | { id: number; text: string; }[]>.value: {
    id: number;
    text: string;
}[]
value
.
Array<{ id: number; text: string; }>.push(...items: {
    id: number;
    text: string;
}[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
(const newTodoItem: TodonewTodoItem)
} const const deleteTodo: (id: number) => Promise<void>deleteTodo = async (id: numberid: number) => { // Simulating API call
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
.
Ref<{ id: number; text: string; }[], Todo[] | { id: number; text: string; }[]>.value: Todo[] | {
    id: number;
    text: string;
}[]
value
=
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
.
Ref<{ id: number; text: string; }[], Todo[] | { id: number; text: string; }[]>.value: {
    id: number;
    text: string;
}[]
value
.
Array<{ id: number; text: string; }>.filter(predicate: (value: {
    id: number;
    text: string;
}, index: number, array: {
    id: number;
    text: string;
}[]) => unknown, thisArg?: any): {
    id: number;
    text: string;
}[] (+1 overload)
Returns the elements of an array that meet the condition specified in a callback function.
@parampredicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
filter
(
todo: {
    id: number;
    text: string;
}
todo
=>
todo: {
    id: number;
    text: string;
}
todo
.id: numberid !== id: numberid)
} function onMounted(hook: any, target?: ComponentInternalInstance | null): voidonMounted(const fetchTodos: () => Promise<void>fetchTodos) </script> <template> <div: HTMLAttributes & ReservedPropsdiv HTMLAttributes.class?: anyclass="container mx-auto p-4"> <h1: HTMLAttributes & ReservedPropsh1 HTMLAttributes.class?: anyclass="text-2xl font-bold mb-4 text-gray-800 dark:text-gray-200">Todo App</h1: HTMLAttributes & ReservedPropsh1> <import TodoListTodoList todos: Todo[]:todos: Todo[]todos="
const todos: Ref<{
    id: number;
    text: string;
}[], Todo[] | {
    id: number;
    text: string;
}[]>
todos
"
@onAddTodo: (text: string) => Promise<void>add-todo="const addTodo: (text: string) => Promise<void>addTodo" @onDeleteTodo: (id: number) => Promise<void>delete-todo="const deleteTodo: (id: number) => Promise<void>deleteTodo" /> </div: HTMLAttributes & ReservedPropsdiv> </template>

Organism (TodoList)

<script setup lang="ts">
import import TodoFormTodoForm from '../molecules/TodoForm.vue'
import import TodoItemTodoItem from '../molecules/TodoItem.vue'

interface Todo {
  Todo.id: numberid: number
  Todo.text: stringtext: string
}

const defineProps: <__VLS_Props>() => DefineProps<LooseRequired<__VLS_Props>, never> (+2 overloads)
Vue `<script setup>` compiler macro for declaring component props. The expected argument is the same as the component `props` option. Example runtime declaration: ```js // using Array syntax const props = defineProps(['foo', 'bar']) // using Object syntax const props = defineProps({ foo: String, bar: { type: Number, required: true } }) ``` Equivalent type-based declaration: ```ts // will be compiled into equivalent runtime declarations const props = defineProps<{ foo?: string bar: number }>() ```
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
defineProps
<{
todos: Todo[]todos: Todo[] }>() const defineEmits: <__VLS_Emit>() => __VLS_Emit (+2 overloads)
Vue `<script setup>` compiler macro for declaring a component's emitted events. The expected argument is the same as the component `emits` option. Example runtime declaration: ```js const emit = defineEmits(['change', 'update']) ``` Example type-based declaration: ```ts const emit = defineEmits<{ // <eventName>: <expected arguments> change: [] update: [value: number] // named tuple syntax }>() emit('change') emit('update', 1) ``` This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
defineEmits
<{
(e: "add-todo"e: 'add-todo', value: stringvalue: string): void (e: "delete-todo"e: 'delete-todo', id: numberid: number): void }>() </script> <template> <div: HTMLAttributes & ReservedPropsdiv> <import TodoFormTodoForm @onAddTodo: ($event: any) => voidadd-todo="$emit: (event: "add-todo", value: string) => void (+1 overload)$emit('add-todo', $event: any$event)" /> <ul: HTMLAttributes & ReservedPropsul HTMLAttributes.class?: anyclass="space-y-2"> <import TodoItemTodoItem v-for="const todo: Todotodo in todos: Todo[]todos" key: number:key: numberkey="const todo: Todotodo.Todo.id: numberid" todo: Todo:todo: Todotodo="const todo: Todotodo" @onDeleteTodo: ($event: any) => voiddelete-todo="$emit: (event: "delete-todo", id: number) => void (+1 overload)$emit('delete-todo', $event: any$event)" /> </ul: HTMLAttributes & ReservedPropsul> </div: HTMLAttributes & ReservedPropsdiv> </template>

Molecules (TodoForm and TodoItem)

TodoForm.vue:
<script setup lang="ts">
import import TodoFormTodoForm from '../molecules/TodoForm.vue'
import import TodoItemTodoItem from '../molecules/TodoItem.vue'

interface Todo {
  Todo.id: numberid: number
  Todo.text: stringtext: string
}

const defineProps: <__VLS_Props>() => DefineProps<LooseRequired<__VLS_Props>, never> (+2 overloads)
Vue `<script setup>` compiler macro for declaring component props. The expected argument is the same as the component `props` option. Example runtime declaration: ```js // using Array syntax const props = defineProps(['foo', 'bar']) // using Object syntax const props = defineProps({ foo: String, bar: { type: Number, required: true } }) ``` Equivalent type-based declaration: ```ts // will be compiled into equivalent runtime declarations const props = defineProps<{ foo?: string bar: number }>() ```
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
defineProps
<{
todos: Todo[]todos: Todo[] }>() const defineEmits: <__VLS_Emit>() => __VLS_Emit (+2 overloads)
Vue `<script setup>` compiler macro for declaring a component's emitted events. The expected argument is the same as the component `emits` option. Example runtime declaration: ```js const emit = defineEmits(['change', 'update']) ``` Example type-based declaration: ```ts const emit = defineEmits<{ // <eventName>: <expected arguments> change: [] update: [value: number] // named tuple syntax }>() emit('change') emit('update', 1) ``` This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
defineEmits
<{
(e: "add-todo"e: 'add-todo', value: stringvalue: string): void (e: "delete-todo"e: 'delete-todo', id: numberid: number): void }>() </script> <template> <div: HTMLAttributes & ReservedPropsdiv> <import TodoFormTodoForm @onAddTodo: ($event: any) => voidadd-todo="$emit: (event: "add-todo", value: string) => void (+1 overload)$emit('add-todo', $event: any$event)" /> <ul: HTMLAttributes & ReservedPropsul HTMLAttributes.class?: anyclass="space-y-2"> <import TodoItemTodoItem v-for="const todo: Todotodo in todos: Todo[]todos" key: number:key: numberkey="const todo: Todotodo.Todo.id: numberid" todo: Todo:todo: Todotodo="const todo: Todotodo" @onDeleteTodo: ($event: any) => voiddelete-todo="$emit: (event: "delete-todo", id: number) => void (+1 overload)$emit('delete-todo', $event: any$event)" /> </ul: HTMLAttributes & ReservedPropsul> </div: HTMLAttributes & ReservedPropsdiv> </template>

TodoItem.vue:

<script setup lang="ts">
import { function ref<T>(value: T): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
} from 'vue'
import import BaseInputBaseInput from '../atoms/BaseInput.vue' import import BaseButtonBaseButton from '../atoms/BaseButton.vue' const const newTodo: Ref<string, string>newTodo = ref<string>(value: string): Ref<string, string> (+1 overload)
Takes an inner value and returns a reactive and mutable ref object, which has a single property `.value` that points to the inner value.
@paramvalue - The object to wrap in the ref.@see{@link https://vuejs.org/api/reactivity-core.html#ref}
ref
('')
const const emit: __VLS_Emitemit = const defineEmits: <__VLS_Emit>() => __VLS_Emit (+2 overloads)
Vue `<script setup>` compiler macro for declaring a component's emitted events. The expected argument is the same as the component `emits` option. Example runtime declaration: ```js const emit = defineEmits(['change', 'update']) ``` Example type-based declaration: ```ts const emit = defineEmits<{ // <eventName>: <expected arguments> change: [] update: [value: number] // named tuple syntax }>() emit('change') emit('update', 1) ``` This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
defineEmits
<{
(e: "add-todo"e: 'add-todo', value: stringvalue: string): void }>() const const addTodo: () => voidaddTodo = () => { if (const newTodo: Ref<string, string>newTodo.Ref<string, string>.value: stringvalue.String.trim(): string
Removes the leading and trailing white space and line terminator characters from a string.
trim
()) {
const emit: (e: "add-todo", value: string) => voidemit('add-todo', const newTodo: Ref<string, string>newTodo.Ref<string, string>.value: stringvalue) const newTodo: Ref<string, string>newTodo.Ref<string, string>.value: stringvalue = '' } } </script> <template> <form: FormHTMLAttributes & ReservedPropsform @onSubmit?: ((payload: Event) => void) | undefinedsubmit.prevent="const addTodo: () => voidaddTodo" HTMLAttributes.class?: anyclass="mb-4"> <import BaseInputBaseInput modelValue: stringv-model="const newTodo: Ref<string, string>newTodo" placeholder: stringplaceholder="Enter a new todo" /> <import BaseButtonBaseButton type: stringtype="submit">Add Todo</import BaseButtonBaseButton> </form: FormHTMLAttributes & ReservedPropsform> </template>

Atoms (BaseButton, BaseInput, BaseText)

BaseButton.vue:
<script setup lang="ts">
const defineProps: <__VLS_Props>() => DefineProps<LooseRequired<__VLS_Props>, never> (+2 overloads)
Vue `<script setup>` compiler macro for declaring component props. The expected argument is the same as the component `props` option. Example runtime declaration: ```js // using Array syntax const props = defineProps(['foo', 'bar']) // using Object syntax const props = defineProps({ foo: String, bar: { type: Number, required: true } }) ``` Equivalent type-based declaration: ```ts // will be compiled into equivalent runtime declarations const props = defineProps<{ foo?: string bar: number }>() ```
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
defineProps
<{
variant?: "primary" | "danger" | undefinedvariant?: 'primary' | 'danger' }>() </script> <template> <button: ButtonHTMLAttributes & ReservedPropsbutton HTMLAttributes.class?: any:HTMLAttributes.class?: anyclass="[ 'p-2 rounded transition duration-300', variant?: "primary" | "danger" | undefinedvariant === 'danger' ? 'bg-red-500 hover:bg-red-600 text-white' : 'bg-blue-500 hover:bg-blue-600 text-white' ]" > <default?(_: {}): anyslot></slot> </button: ButtonHTMLAttributes & ReservedPropsbutton> </template>

BaseInput.vue:

<script setup lang="ts">
const defineProps: <__VLS_Props>() => DefineProps<LooseRequired<__VLS_Props>, never> (+2 overloads)
Vue `<script setup>` compiler macro for declaring component props. The expected argument is the same as the component `props` option. Example runtime declaration: ```js // using Array syntax const props = defineProps(['foo', 'bar']) // using Object syntax const props = defineProps({ foo: String, bar: { type: Number, required: true } }) ``` Equivalent type-based declaration: ```ts // will be compiled into equivalent runtime declarations const props = defineProps<{ foo?: string bar: number }>() ```
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
defineProps
<{
modelValue: stringmodelValue: string placeholder?: string | undefinedplaceholder?: string }>() const defineEmits: <__VLS_Emit>() => __VLS_Emit (+2 overloads)
Vue `<script setup>` compiler macro for declaring a component's emitted events. The expected argument is the same as the component `emits` option. Example runtime declaration: ```js const emit = defineEmits(['change', 'update']) ``` Example type-based declaration: ```ts const emit = defineEmits<{ // <eventName>: <expected arguments> change: [] update: [value: number] // named tuple syntax }>() emit('change') emit('update', 1) ``` This is only usable inside `<script setup>`, is compiled away in the output and should **not** be actually called at runtime.
@see{@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
defineEmits
<{
(e: "update:modelValue"e: 'update:modelValue', value: stringvalue: string): void }>() </script> <template> <input: InputHTMLAttributes & ReservedPropsinput InputHTMLAttributes.value?: any:InputHTMLAttributes.value?: anyvalue="modelValue: stringmodelValue" @onInput?: ((payload: Event) => void) | undefinedinput="$emit: (event: "update:modelValue", value: string) => void$emit('update:modelValue', ($event: Event$event.Event.target: EventTarget | null
Returns the object to which event is dispatched (its target). [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target)
target
as HTMLInputElement).HTMLInputElement.value: string
Returns the value of the data at the cursor's current position.
value
)"
InputHTMLAttributes.type?: InputTypeHTMLAttribute | undefinedtype="text" InputHTMLAttributes.placeholder?: string | undefined:InputHTMLAttributes.placeholder?: string | undefinedplaceholder="placeholder?: string | undefinedplaceholder" HTMLAttributes.class?: anyclass="border p-2 mr-2 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded" /> </template>
Component LevelJobExamples
AtomsPure, single-purpose componentsBaseButton BaseInput BaseIcon BaseText
MoleculesCombinations of atoms with minimal logicSearchBar LoginForm StarRating Tooltip
OrganismsLarger, self-contained, reusable components. Can perform side effects and complex operations.TheHeader ProductCard CommentSection NavigationMenu
TemplatesNuxt layouts defining page structureDefaultLayout BlogLayout DashboardLayout AuthLayout
PagesComponents handling data and API callsHomePage UserProfile ProductList CheckoutPage

Summary

Atomic Design offers one path to a more apparent code structure. It works well as a starting point for many projects. But as complexity grows, other architectures may serve you better. Want to explore more options? Read my post on How to structure vue Projects. It covers approaches beyond Atomic Design when your project outgrows its initial structure.

Related Posts