Nuxt UI & TailwindCSS
MStore Dashboard menggunakan Nuxt UI 4.x sebagai component library dengan TailwindCSS 4 untuk styling.Overview
Nuxt UI adalah component library berbasis headless yang terintegrasi dengan TailwindCSS, menyediakan komponen yang accessible dan customizable.Installation & Setup
Nuxt UI sudah dikonfigurasi dinuxt.config.ts:
Copy
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
ui: {
theme: {
colors: [
'primary',
'secondary',
'tertiary',
'info',
'success',
'warning',
'error',
'neutral'
]
}
},
colorMode: {
preference: 'light',
fallback: 'light',
classSuffix: '',
storageKey: 'nuxt-color-mode'
},
icon: {
provider: 'iconify',
serverBundle: 'auto'
}
})
Color System
Semantic Colors
| Color | Usage | Example |
|---|---|---|
primary | Main brand color, CTAs | Buttons, links |
secondary | Secondary actions | Subtle buttons |
tertiary | Tertiary elements | Backgrounds |
info | Informational content | Info alerts |
success | Success states | Success messages |
warning | Warning content | Warning alerts |
error | Error states | Error messages |
neutral | Text, backgrounds | Body text |
Using Colors
Copy
<template>
<!-- Button colors -->
<UButton color="primary">Primary</UButton>
<UButton color="secondary">Secondary</UButton>
<UButton color="success">Success</UButton>
<UButton color="error">Delete</UButton>
<!-- Alert colors -->
<UAlert color="info" title="Info" />
<UAlert color="warning" title="Warning" />
<UAlert color="error" title="Error" />
<!-- Badge colors -->
<UBadge color="success">Active</UBadge>
<UBadge color="error">Inactive</UBadge>
</template>
Common Components
Buttons
Copy
<template>
<!-- Variants -->
<UButton>Default (Solid)</UButton>
<UButton variant="outline">Outline</UButton>
<UButton variant="soft">Soft</UButton>
<UButton variant="ghost">Ghost</UButton>
<UButton variant="link">Link</UButton>
<!-- Sizes -->
<UButton size="xs">Extra Small</UButton>
<UButton size="sm">Small</UButton>
<UButton size="md">Medium</UButton>
<UButton size="lg">Large</UButton>
<UButton size="xl">Extra Large</UButton>
<!-- With Icons -->
<UButton icon="i-heroicons-plus">Add Item</UButton>
<UButton trailing-icon="i-heroicons-arrow-right">Next</UButton>
<!-- Loading -->
<UButton :loading="isLoading">Submit</UButton>
<!-- Disabled -->
<UButton :disabled="!isValid">Submit</UButton>
<!-- Block (full width) -->
<UButton block>Full Width Button</UButton>
</template>
Form Inputs
Copy
<template>
<!-- Text Input -->
<UInput v-model="name" placeholder="Enter name" />
<!-- With Icon -->
<UInput
v-model="search"
icon="i-heroicons-magnifying-glass"
placeholder="Search..."
/>
<!-- With Label -->
<UFormGroup label="Email" name="email">
<UInput v-model="email" type="email" />
</UFormGroup>
<!-- With Error -->
<UFormGroup label="Password" :error="errors.password">
<UInput v-model="password" type="password" />
</UFormGroup>
<!-- Textarea -->
<UTextarea v-model="description" placeholder="Description" />
<!-- Select -->
<USelect
v-model="category"
:options="categories"
placeholder="Select category"
/>
<!-- Checkbox -->
<UCheckbox v-model="agreed" label="I agree to terms" />
<!-- Toggle -->
<UToggle v-model="isActive" />
<!-- Radio Group -->
<URadioGroup v-model="selected" :options="options" />
</template>
<script setup lang="ts">
const categories = [
{ label: 'Electronics', value: 'electronics' },
{ label: 'Clothing', value: 'clothing' },
{ label: 'Food', value: 'food' },
]
</script>
Cards
Copy
<template>
<UCard>
<template #header>
<h3 class="text-lg font-semibold">Card Title</h3>
</template>
<p>Card content goes here.</p>
<template #footer>
<div class="flex justify-end gap-2">
<UButton variant="outline">Cancel</UButton>
<UButton>Save</UButton>
</div>
</template>
</UCard>
</template>
Modals
Copy
<template>
<UButton @click="isOpen = true">Open Modal</UButton>
<UModal v-model="isOpen">
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">Modal Title</h3>
<UButton
icon="i-heroicons-x-mark"
variant="ghost"
@click="isOpen = false"
/>
</div>
</template>
<p>Modal content...</p>
<template #footer>
<div class="flex justify-end gap-2">
<UButton variant="outline" @click="isOpen = false">
Cancel
</UButton>
<UButton @click="handleSave">Save</UButton>
</div>
</template>
</UCard>
</UModal>
</template>
<script setup lang="ts">
const isOpen = ref(false)
</script>
Tables
Copy
<template>
<UTable
:columns="columns"
:rows="products"
:loading="loading"
>
<template #price-data="{ row }">
{{ formatCurrency(row.price) }}
</template>
<template #status-data="{ row }">
<UBadge :color="row.status === 'active' ? 'success' : 'error'">
{{ row.status }}
</UBadge>
</template>
<template #actions-data="{ row }">
<UDropdown :items="getActions(row)">
<UButton icon="i-heroicons-ellipsis-vertical" variant="ghost" />
</UDropdown>
</template>
</UTable>
</template>
<script setup lang="ts">
const columns = [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'sku', label: 'SKU' },
{ key: 'price', label: 'Price', sortable: true },
{ key: 'status', label: 'Status' },
{ key: 'actions', label: '' },
]
const getActions = (row: Product) => [[
{ label: 'Edit', icon: 'i-heroicons-pencil', click: () => editProduct(row) },
{ label: 'Delete', icon: 'i-heroicons-trash', click: () => deleteProduct(row) },
]]
</script>
Alerts & Notifications
Copy
<template>
<!-- Static Alert -->
<UAlert
color="info"
title="Information"
description="This is an informational message."
/>
<!-- Closable Alert -->
<UAlert
v-if="showAlert"
color="success"
title="Success!"
description="Your changes have been saved."
:close-button="{ icon: 'i-heroicons-x-mark' }"
@close="showAlert = false"
/>
</template>
<script setup lang="ts">
// Toast notifications
const toast = useToast()
const showSuccess = () => {
toast.add({
title: 'Success',
description: 'Operation completed successfully.',
color: 'success',
})
}
const showError = () => {
toast.add({
title: 'Error',
description: 'Something went wrong.',
color: 'error',
})
}
</script>
Pagination
Copy
<template>
<UPagination
v-model="page"
:total="total"
:page-count="pageSize"
/>
</template>
<script setup lang="ts">
const page = ref(1)
const pageSize = 20
const total = ref(100)
</script>
Dropdown Menu
Copy
<template>
<UDropdown :items="items">
<UButton trailing-icon="i-heroicons-chevron-down">
Options
</UButton>
</UDropdown>
</template>
<script setup lang="ts">
const items = [
[
{ label: 'Edit', icon: 'i-heroicons-pencil' },
{ label: 'Duplicate', icon: 'i-heroicons-document-duplicate' },
],
[
{ label: 'Archive', icon: 'i-heroicons-archive-box' },
{ label: 'Delete', icon: 'i-heroicons-trash', color: 'error' },
],
]
</script>
Icons
Using Heroicons
Copy
<template>
<!-- Outline (default) -->
<UIcon name="i-heroicons-home" />
<!-- Solid -->
<UIcon name="i-heroicons-home-solid" />
<!-- With size -->
<UIcon name="i-heroicons-home" class="w-6 h-6" />
<!-- With color -->
<UIcon name="i-heroicons-home" class="text-primary-500" />
</template>
Using Lucide Icons
Copy
<template>
<UIcon name="i-lucide-settings" />
<UIcon name="i-lucide-user" />
<UIcon name="i-lucide-search" />
</template>
Layout Components
Container
Copy
<template>
<UContainer>
<h1>Page Content</h1>
</UContainer>
</template>
Skeleton (Loading State)
Copy
<template>
<div v-if="loading">
<USkeleton class="h-4 w-48 mb-2" />
<USkeleton class="h-4 w-32" />
</div>
<div v-else>
{{ data }}
</div>
</template>
Divider
Copy
<template>
<UDivider />
<UDivider label="OR" />
<UDivider orientation="vertical" />
</template>
Form Validation
Copy
<template>
<UForm :state="state" :schema="schema" @submit="onSubmit">
<UFormGroup label="Name" name="name">
<UInput v-model="state.name" />
</UFormGroup>
<UFormGroup label="Email" name="email">
<UInput v-model="state.email" type="email" />
</UFormGroup>
<UFormGroup label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormGroup>
<UButton type="submit">Submit</UButton>
</UForm>
</template>
<script setup lang="ts">
import { z } from 'zod'
const schema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
const state = reactive({
name: '',
email: '',
password: '',
})
const onSubmit = async () => {
// Form is valid, handle submission
}
</script>
Dark Mode
Copy
<script setup lang="ts">
const colorMode = useColorMode()
const toggleDarkMode = () => {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
</script>
<template>
<UButton
:icon="colorMode.value === 'dark' ? 'i-heroicons-sun' : 'i-heroicons-moon'"
variant="ghost"
@click="toggleDarkMode"
/>
</template>
TailwindCSS Utilities
Common Classes
Copy
<template>
<!-- Spacing -->
<div class="p-4 m-2 px-6 py-3">Padding & Margin</div>
<!-- Flexbox -->
<div class="flex items-center justify-between gap-4">
<span>Left</span>
<span>Right</span>
</div>
<!-- Grid -->
<div class="grid grid-cols-3 gap-4">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<!-- Typography -->
<h1 class="text-2xl font-bold">Heading</h1>
<p class="text-gray-600 text-sm">Paragraph</p>
<!-- Colors -->
<div class="bg-primary-500 text-white">Primary Background</div>
<div class="border border-gray-200 rounded-lg">Border</div>
<!-- Responsive -->
<div class="hidden md:block">Visible on medium screens</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
Responsive Grid
</div>
</template>
Best Practices
Use Semantic Colors
Use Semantic Colors
Copy
<!-- Good -->
<UButton color="error">Delete</UButton>
<UAlert color="success" title="Saved" />
<!-- Avoid -->
<UButton class="bg-red-500">Delete</UButton>
Use Component Props Over Classes
Use Component Props Over Classes
Copy
<!-- Good -->
<UButton size="lg" variant="outline">Button</UButton>
<!-- Avoid -->
<UButton class="text-lg border">Button</UButton>
Consistent Spacing
Consistent Spacing
Copy
<!-- Use Tailwind spacing utilities -->
<div class="space-y-4">
<UFormGroup>...</UFormGroup>
<UFormGroup>...</UFormGroup>
</div>