Skip to main content

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 di nuxt.config.ts:
// 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

ColorUsageExample
primaryMain brand color, CTAsButtons, links
secondarySecondary actionsSubtle buttons
tertiaryTertiary elementsBackgrounds
infoInformational contentInfo alerts
successSuccess statesSuccess messages
warningWarning contentWarning alerts
errorError statesError messages
neutralText, backgroundsBody text

Using Colors

<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

<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

<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

<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

<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

<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

<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

<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>
<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

<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

<template>
  <UIcon name="i-lucide-settings" />
  <UIcon name="i-lucide-user" />
  <UIcon name="i-lucide-search" />
</template>

Layout Components

Container

<template>
  <UContainer>
    <h1>Page Content</h1>
  </UContainer>
</template>

Skeleton (Loading State)

<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

<template>
  <UDivider />
  <UDivider label="OR" />
  <UDivider orientation="vertical" />
</template>

Form Validation

<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

<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

<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

<!-- Good -->
<UButton color="error">Delete</UButton>
<UAlert color="success" title="Saved" />

<!-- Avoid -->
<UButton class="bg-red-500">Delete</UButton>
<!-- Good -->
<UButton size="lg" variant="outline">Button</UButton>

<!-- Avoid -->
<UButton class="text-lg border">Button</UButton>
<!-- Use Tailwind spacing utilities -->
<div class="space-y-4">
  <UFormGroup>...</UFormGroup>
  <UFormGroup>...</UFormGroup>
</div>

Next Steps