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:
- Duplicated code
- Oversized, multipurpose components
- Difficult-to-test code
Atomic Design offers a solution. Let’s examine how to apply it to a Nuxt project.
What is Atomic Design
Brad Frost developed Atomic Design as a methodology for creating design systems. It is structured into five levels inspired by chemistry:
- Atoms: Basic building blocks (e.g. form labels, inputs, buttons)
- Molecules: Simple groups of UI elements (e.g. search forms)
- Organisms: Complex components made of molecules/atoms (e.g. headers)
- Templates: Page-level layouts
- Pages: Specific instances of templates with content
For Nuxt, we can adapt these definitions:
- Atoms: Pure, single-purpose components
- Molecules: Combinations of atoms with minimal logic
- Organisms: Larger, self-contained, reusable components
- Templates: Nuxt layouts defining page structure
- Pages: Components handling data and API calls
Code Example: Before and After
Consider this non-Atomic Design todo app component:
<template>
<div: HTMLAttributes & ReservedProps
div HTMLAttributes.class?: any
class="container mx-auto p-4">
<h1: HTMLAttributes & ReservedProps
h1 HTMLAttributes.class?: any
class="text-2xl font-bold mb-4 text-gray-800 dark:text-gray-200">Todo App</h1: HTMLAttributes & ReservedProps
h1>
<!-- Add Todo Form -->
<form: FormHTMLAttributes & ReservedProps
form @onSubmit?: ((payload: Event) => void) | undefined
submit.prevent="addTodo: () => Promise<void>
addTodo" HTMLAttributes.class?: any
class="mb-4">
<input: InputHTMLAttributes & ReservedProps
input
InputHTMLAttributes.value?: any
v-model="newTodo: string
newTodo"
InputHTMLAttributes.type?: InputTypeHTMLAttribute | undefined
type="text"
InputHTMLAttributes.placeholder?: string | undefined
placeholder="Enter a new todo"
HTMLAttributes.class?: any
class="border p-2 mr-2 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded"
/>
<button: ButtonHTMLAttributes & ReservedProps
button ButtonHTMLAttributes.type?: "reset" | "submit" | "button" | undefined
type="submit" HTMLAttributes.class?: any
class="bg-blue-500 hover:bg-blue-600 text-white p-2 rounded transition duration-300">
Add Todo
</button: ButtonHTMLAttributes & ReservedProps
button>
</form: FormHTMLAttributes & ReservedProps
form>
<!-- Todo List -->
<ul: HTMLAttributes & ReservedProps
ul HTMLAttributes.class?: any
class="space-y-2">
<li: LiHTMLAttributes & ReservedProps
li
v-for="const todo: Todo
todo in todos: Todo[]
todos"
key?: PropertyKey | undefined
:key?: PropertyKey | undefined
key="const todo: Todo
todo.Todo.id: number
id"
HTMLAttributes.class?: any
class="flex justify-between items-center p-3 bg-gray-100 dark:bg-gray-700 rounded shadow-sm"
>
<span: HTMLAttributes & ReservedProps
span HTMLAttributes.class?: any
class="text-gray-800 dark:text-gray-200">{{ const todo: Todo
todo.Todo.text: string
text }}</span: HTMLAttributes & ReservedProps
span>
<button: ButtonHTMLAttributes & ReservedProps
button
@onClick?: ((payload: MouseEvent) => void) | undefined
click="deleteTodo: (id: number) => Promise<void>
deleteTodo(const todo: Todo
todo.Todo.id: number
id)"
HTMLAttributes.class?: any
class="bg-red-500 hover:bg-red-600 text-white p-1 rounded transition duration-300"
>
Delete
</button: ButtonHTMLAttributes & ReservedProps
button>
</li: LiHTMLAttributes & ReservedProps
li>
</ul: HTMLAttributes & ReservedProps
ul>
</div: HTMLAttributes & ReservedProps
div>
</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.ref, const onMounted: CreateHook<any>
onMounted } from 'vue'
interface Todo {
Todo.id: number
id: number
Todo.text: string
text: 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.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.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: number
id: 1, text: string
text: 'Learn Vue.js' },
{ id: number
id: 2, text: string
text: 'Build a Todo App' },
{ id: number
id: 3, text: string
text: 'Study Atomic Design' }
]
}
const const addTodo: () => Promise<void>
addTodo = async () => {
if (const newTodo: Ref<string, string>
newTodo.Ref<string, string>.value: string
value.String.trim(): string
Removes the leading and trailing white space and line terminator characters from a string.trim()) {
// Simulating API call
const const newTodoItem: Todo
newTodoItem: Todo = {
Todo.id: number
id: 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: string
text: const newTodo: Ref<string, string>
newTodo.Ref<string, string>.value: 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; }>.push(...items: {
id: number;
text: string;
}[]): number
Appends new elements to the end of an array, and returns the new length of the array.push(const newTodoItem: Todo
newTodoItem)
const newTodo: Ref<string, string>
newTodo.Ref<string, string>.value: string
value = ''
}
}
const const deleteTodo: (id: number) => Promise<void>
deleteTodo = async (id: number
id: 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.filter(todo: {
id: number;
text: string;
}
todo => todo: {
id: number;
text: string;
}
todo.id: number
id !== id: number
id)
}
function onMounted(hook: any, target?: ComponentInternalInstance | null): void
onMounted(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 & ReservedProps
div HTMLAttributes.class?: any
class="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-300">
<header: HTMLAttributes & ReservedProps
header HTMLAttributes.class?: any
class="bg-white dark:bg-gray-800 shadow">
<nav: HTMLAttributes & ReservedProps
nav HTMLAttributes.class?: any
class="container mx-auto px-4 py-4 flex justify-between items-center">
<type NuxtLink: unknown
NuxtLink to: string
to="/" class: string
class="text-xl font-bold">Todo App</NuxtLink>
<import ThemeToggle
ThemeToggle />
</nav: HTMLAttributes & ReservedProps
nav>
</header: HTMLAttributes & ReservedProps
header>
<main: HTMLAttributes & ReservedProps
main HTMLAttributes.class?: any
class="container mx-auto px-4 py-8">
<default?(_: {}): any
slot />
</main: HTMLAttributes & ReservedProps
main>
</div: HTMLAttributes & ReservedProps
div>
</template>
<script setup lang="ts">
import import ThemeToggle
ThemeToggle 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.ref, const onMounted: CreateHook<any>
onMounted } from 'vue'
import import TodoList
TodoList from '../components/organisms/TodoList'
interface Todo {
Todo.id: number
id: number
Todo.text: string
text: 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.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: number
id: 1, text: string
text: 'Learn Vue.js' },
{ id: number
id: 2, text: string
text: 'Build a Todo App' },
{ id: number
id: 3, text: string
text: 'Study Atomic Design' }
]
}
const const addTodo: (text: string) => Promise<void>
addTodo = async (text: string
text: string) => {
// Simulating API call
const const newTodoItem: Todo
newTodoItem: Todo = {
Todo.id: number
id: 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: string
text
}
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.push(const newTodoItem: Todo
newTodoItem)
}
const const deleteTodo: (id: number) => Promise<void>
deleteTodo = async (id: number
id: 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.filter(todo: {
id: number;
text: string;
}
todo => todo: {
id: number;
text: string;
}
todo.id: number
id !== id: number
id)
}
function onMounted(hook: any, target?: ComponentInternalInstance | null): void
onMounted(const fetchTodos: () => Promise<void>
fetchTodos)
</script>
<template>
<div: HTMLAttributes & ReservedProps
div HTMLAttributes.class?: any
class="container mx-auto p-4">
<h1: HTMLAttributes & ReservedProps
h1 HTMLAttributes.class?: any
class="text-2xl font-bold mb-4 text-gray-800 dark:text-gray-200">Todo App</h1: HTMLAttributes & ReservedProps
h1>
<import TodoList
TodoList
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 & ReservedProps
div>
</template>
Organism (TodoList)
<script setup lang="ts">
import import TodoForm
TodoForm from '../molecules/TodoForm.vue'
import import TodoItem
TodoItem from '../molecules/TodoItem.vue'
interface Todo {
Todo.id: number
id: number
Todo.text: string
text: string
}
const defineProps: <{
todos: Todo[];
}>() => DefineProps<LooseRequired<{
todos: Todo[];
}>, 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
}>()
```defineProps<{
todos: Todo[]
todos: Todo[]
}>()
const defineEmits: <{
(e: "add-todo", value: string): void;
(e: "delete-todo", id: number): void;
}>() => {
(e: "add-todo", value: string): void;
(e: "delete-todo", id: number): void;
} (+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.defineEmits<{
(e: "add-todo"
e: 'add-todo', value: string
value: string): void
(e: "delete-todo"
e: 'delete-todo', id: number
id: number): void
}>()
</script>
<template>
<div: HTMLAttributes & ReservedProps
div>
<import TodoForm
TodoForm @onAddTodo: ($event: any) => void
add-todo="$emit: (event: "add-todo", value: string) => void (+1 overload)
$emit('add-todo', $event: any
$event)" />
<ul: HTMLAttributes & ReservedProps
ul HTMLAttributes.class?: any
class="space-y-2">
<import TodoItem
TodoItem
v-for="const todo: Todo
todo in todos: Todo[]
todos"
key: number
:key: number
key="const todo: Todo
todo.Todo.id: number
id"
todo: Todo
:todo: Todo
todo="const todo: Todo
todo"
@onDeleteTodo: ($event: any) => void
delete-todo="$emit: (event: "delete-todo", id: number) => void (+1 overload)
$emit('delete-todo', $event: any
$event)"
/>
</ul: HTMLAttributes & ReservedProps
ul>
</div: HTMLAttributes & ReservedProps
div>
</template>
Molecules (TodoForm and TodoItem)
TodoForm.vue:
<script setup lang="ts">
import import TodoForm
TodoForm from '../molecules/TodoForm.vue'
import import TodoItem
TodoItem from '../molecules/TodoItem.vue'
interface Todo {
Todo.id: number
id: number
Todo.text: string
text: string
}
const defineProps: <{
todos: Todo[];
}>() => DefineProps<LooseRequired<{
todos: Todo[];
}>, 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
}>()
```defineProps<{
todos: Todo[]
todos: Todo[]
}>()
const defineEmits: <{
(e: "add-todo", value: string): void;
(e: "delete-todo", id: number): void;
}>() => {
(e: "add-todo", value: string): void;
(e: "delete-todo", id: number): void;
} (+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.defineEmits<{
(e: "add-todo"
e: 'add-todo', value: string
value: string): void
(e: "delete-todo"
e: 'delete-todo', id: number
id: number): void
}>()
</script>
<template>
<div: HTMLAttributes & ReservedProps
div>
<import TodoForm
TodoForm @onAddTodo: ($event: any) => void
add-todo="$emit: (event: "add-todo", value: string) => void (+1 overload)
$emit('add-todo', $event: any
$event)" />
<ul: HTMLAttributes & ReservedProps
ul HTMLAttributes.class?: any
class="space-y-2">
<import TodoItem
TodoItem
v-for="const todo: Todo
todo in todos: Todo[]
todos"
key: number
:key: number
key="const todo: Todo
todo.Todo.id: number
id"
todo: Todo
:todo: Todo
todo="const todo: Todo
todo"
@onDeleteTodo: ($event: any) => void
delete-todo="$emit: (event: "delete-todo", id: number) => void (+1 overload)
$emit('delete-todo', $event: any
$event)"
/>
</ul: HTMLAttributes & ReservedProps
ul>
</div: HTMLAttributes & ReservedProps
div>
</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.ref } from 'vue'
import import BaseInput
BaseInput from '../atoms/BaseInput.vue'
import import BaseButton
BaseButton 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.ref('')
const const emit: (e: "add-todo", value: string) => void
emit = const defineEmits: <(e: "add-todo", value: string) => void>() => (e: "add-todo", value: string) => void (+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.defineEmits<{
(e: "add-todo"
e: 'add-todo', value: string
value: string): void
}>()
const const addTodo: () => void
addTodo = () => {
if (const newTodo: Ref<string, string>
newTodo.Ref<string, string>.value: string
value.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) => void
emit('add-todo', const newTodo: Ref<string, string>
newTodo.Ref<string, string>.value: string
value)
const newTodo: Ref<string, string>
newTodo.Ref<string, string>.value: string
value = ''
}
}
</script>
<template>
<form: FormHTMLAttributes & ReservedProps
form @onSubmit?: ((payload: Event) => void) | undefined
submit.prevent="const addTodo: () => void
addTodo" HTMLAttributes.class?: any
class="mb-4">
<import BaseInput
BaseInput modelValue: string
v-model="const newTodo: Ref<string, string>
newTodo" placeholder: string
placeholder="Enter a new todo" />
<import BaseButton
BaseButton type: string
type="submit">Add Todo</import BaseButton
BaseButton>
</form: FormHTMLAttributes & ReservedProps
form>
</template>
Atoms (BaseButton, BaseInput, BaseText)
BaseButton.vue:
<script setup lang="ts">
const defineProps: <{
variant?: "primary" | "danger";
}>() => DefineProps<LooseRequired<{
variant?: "primary" | "danger";
}>, 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
}>()
```defineProps<{
variant?: "primary" | "danger" | undefined
variant?: 'primary' | 'danger'
}>()
</script>
<template>
<button: ButtonHTMLAttributes & ReservedProps
button
HTMLAttributes.class?: any
:HTMLAttributes.class?: any
class="[
'p-2 rounded transition duration-300',
variant?: "primary" | "danger" | undefined
variant === 'danger'
? 'bg-red-500 hover:bg-red-600 text-white'
: 'bg-blue-500 hover:bg-blue-600 text-white'
]"
>
<default?(_: {}): any
slot></slot>
</button: ButtonHTMLAttributes & ReservedProps
button>
</template>
BaseInput.vue:
<script setup lang="ts">
const defineProps: <{
modelValue: string;
placeholder?: string;
}>() => DefineProps<LooseRequired<{
modelValue: string;
placeholder?: string;
}>, 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
}>()
```defineProps<{
modelValue: string
modelValue: string
placeholder?: string | undefined
placeholder?: string
}>()
const defineEmits: <(e: "update:modelValue", value: string) => void>() => (e: "update:modelValue", value: string) => void (+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.defineEmits<{
(e: "update:modelValue"
e: 'update:modelValue', value: string
value: string): void
}>()
</script>
<template>
<input: InputHTMLAttributes & ReservedProps
input
InputHTMLAttributes.value?: any
:InputHTMLAttributes.value?: any
value="modelValue: string
modelValue"
@onInput?: ((payload: Event) => void) | undefined
input="$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 | undefined
type="text"
InputHTMLAttributes.placeholder?: string | undefined
:InputHTMLAttributes.placeholder?: string | undefined
placeholder="placeholder?: string | undefined
placeholder"
HTMLAttributes.class?: any
class="border p-2 mr-2 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded"
/>
</template>
Component Level | Job | Examples |
---|---|---|
Atoms | Pure, single-purpose components | BaseButton BaseInput BaseIcon BaseText |
Molecules | Combinations of atoms with minimal logic | SearchBar LoginForm StarRating Tooltip |
Organisms | Larger, self-contained, reusable components. Can perform side effects and complex operations. | TheHeader ProductCard CommentSection NavigationMenu |
Templates | Nuxt layouts defining page structure | DefaultLayout BlogLayout DashboardLayout AuthLayout |
Pages | Components handling data and API calls | HomePage 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.