Skip to content

Vite Integration Guide

This guide explains how to integrate Vue UI into a Vite + Vue project. It assumes you already have a fresh Vite + Vue project. If not, you can create one using the following command:

bash
npm create vite@latest my-vue-app --template vue-ts

Before going further, you need to install the required dependencies.

pnpm
bash
pnpm add scule \
  defu \
  ohash \
  fuse.js \
  reka-ui \
  colortranslator \
  tailwind-variants \
  @vueuse/core \
  @vueuse/shared \
  @vueuse/integrations \
  @tanstack/table-core \
  @tanstack/vue-table \
  embla-carousel \
  embla-carousel-fade \
  embla-carousel-vue \
  embla-carousel-autoplay \
  embla-carousel-auto-scroll \
  embla-carousel-auto-height \
  embla-carousel-class-names \
  embla-carousel-wheel-gestures \
  vue-component-type-helpers

Configuring Vite

To ensure everything works seamlessly, you need to adjust the Vite configuration for dependency optimization and component resolution.

Optimize Dependencies

Add the following configuration to your vite.config.ts file to pre-optimize specific dependencies:

vite.config.ts
ts
import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    include: [
      'scule',
      'reka-ui',
      '@vueuse/core',
      '@vueuse/shared',
      'tailwind-variants',
      '@tanstack/vue-table',
    ],
  },
})

This ensures that the specified dependencies are pre-optimized before being discovered by the Vite server, reducing server reload times.

Add Aliases

Set up an alias in your vite.config.ts file to simplify component imports:

vite.config.ts
ts
import { fileURLToPath } from 'node:url'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'

export default defineConfig({
  resolve: {
    alias: {
      '@/ui': fileURLToPath(new URL('./src/ui', import.meta.url)),
    },
  },
})

This alias allows you to import components using @/ui instead of relative paths. Vue UI internally relies on this alias, so it's essential to configure it.

Setting Up Tailwind CSS

For this section, you need to install Tailwind CSS and its dependencies:

pnpm
bash
pnpm add -D tailwindcss @tailwindcss/vite @egoist/tailwindcss-icons

To style your project, integrate Tailwind CSS using its Vite plugin:

  1. Add the plugin to your vite.config.ts file:
vite.config.ts
ts
import tailwindcss from '@tailwindcss/vite'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [vue(), tailwindcss()],
})
  1. Import Tailwind CSS in your main style file, typically src/style.css:
src/style.css
css
@import "tailwindcss";
  1. Optionally, add the @egoist/tailwindcss-icons plugin for icons:
src/style.css
css
@plugin '@egoist/tailwindcss-icons';

Vue UI uses the Lucide icon library by default. You can customize icons by editing the src/ui/icons/index.ts file.

  1. Add custom configurations to src/style.css for themes, colors, and variants. For example:
src/style.css
css
@variant light (&:where(.light, .light *));
@variant dark (&:where(.dark, .dark *));

@theme default {
  --color-primary: var(--ui-primary);
  --color-secondary: var(--ui-secondary);
  // Add other custom variables here...
}

@layer base {
  body {
    @apply antialiased text-default bg-default scheme-light dark:scheme-dark;
  }

  :root {
    --ui-color-primary-500: var(--color-violet-500);
    --ui-color-secondary-500: var(--color-blue-500);
    // Define other colors and variables...
  }

  .dark {
    --ui-primary: var(--ui-color-primary-400);
    --ui-secondary: var(--ui-color-secondary-400);
    // Define dark theme variables...
  }
}
Click to expand the CSS configuration
src/style.css
css
@variant light (&:where(.light, .light *));
@variant dark (&:where(.dark, .dark *));

@theme default {
  --color-primary: var(--ui-primary);
  --color-primary-50: var(--ui-color-primary-50);
  --color-primary-100: var(--ui-color-primary-100);
  --color-primary-200: var(--ui-color-primary-200);
  --color-primary-300: var(--ui-color-primary-300);
  --color-primary-400: var(--ui-color-primary-400);
  --color-primary-500: var(--ui-color-primary-500);
  --color-primary-600: var(--ui-color-primary-600);
  --color-primary-700: var(--ui-color-primary-700);
  --color-primary-800: var(--ui-color-primary-800);
  --color-primary-900: var(--ui-color-primary-900);
  --color-primary-950: var(--ui-color-primary-950);

  --color-secondary: var(--ui-secondary);
  --color-secondary-50: var(--ui-color-secondary-50);
  --color-secondary-100: var(--ui-color-secondary-100);
  --color-secondary-200: var(--ui-color-secondary-200);
  --color-secondary-300: var(--ui-color-secondary-300);
  --color-secondary-400: var(--ui-color-secondary-400);
  --color-secondary-500: var(--ui-color-secondary-500);
  --color-secondary-600: var(--ui-color-secondary-600);
  --color-secondary-700: var(--ui-color-secondary-700);
  --color-secondary-800: var(--ui-color-secondary-800);
  --color-secondary-900: var(--ui-color-secondary-900);
  --color-secondary-950: var(--ui-color-secondary-950);

  --color-success: var(--ui-success);
  --color-success-50: var(--ui-color-success-50);
  --color-success-100: var(--ui-color-success-100);
  --color-success-200: var(--ui-color-success-200);
  --color-success-300: var(--ui-color-success-300);
  --color-success-400: var(--ui-color-success-400);
  --color-success-500: var(--ui-color-success-500);
  --color-success-600: var(--ui-color-success-600);
  --color-success-700: var(--ui-color-success-700);
  --color-success-800: var(--ui-color-success-800);
  --color-success-900: var(--ui-color-success-900);
  --color-success-950: var(--ui-color-success-950);

  --color-info: var(--ui-info);
  --color-info-50: var(--ui-color-info-50);
  --color-info-100: var(--ui-color-info-100);
  --color-info-200: var(--ui-color-info-200);
  --color-info-300: var(--ui-color-info-300);
  --color-info-400: var(--ui-color-info-400);
  --color-info-500: var(--ui-color-info-500);
  --color-info-600: var(--ui-color-info-600);
  --color-info-700: var(--ui-color-info-700);
  --color-info-800: var(--ui-color-info-800);
  --color-info-900: var(--ui-color-info-900);
  --color-info-950: var(--ui-color-info-950);

  --color-warning: var(--ui-warning);
  --color-warning-50: var(--ui-color-warning-50);
  --color-warning-100: var(--ui-color-warning-100);
  --color-warning-200: var(--ui-color-warning-200);
  --color-warning-300: var(--ui-color-warning-300);
  --color-warning-400: var(--ui-color-warning-400);
  --color-warning-500: var(--ui-color-warning-500);
  --color-warning-600: var(--ui-color-warning-600);
  --color-warning-700: var(--ui-color-warning-700);
  --color-warning-800: var(--ui-color-warning-800);
  --color-warning-900: var(--ui-color-warning-900);
  --color-warning-950: var(--ui-color-warning-950);

  --color-error: var(--ui-error);
  --color-error-50: var(--ui-color-error-50);
  --color-error-100: var(--ui-color-error-100);
  --color-error-200: var(--ui-color-error-200);
  --color-error-300: var(--ui-color-error-300);
  --color-error-400: var(--ui-color-error-400);
  --color-error-500: var(--ui-color-error-500);
  --color-error-600: var(--ui-color-error-600);
  --color-error-700: var(--ui-color-error-700);
  --color-error-800: var(--ui-color-error-800);
  --color-error-900: var(--ui-color-error-900);
  --color-error-950: var(--ui-color-error-950);

  --color-neutral-50: var(--ui-color-neutral-50);
  --color-neutral-100: var(--ui-color-neutral-100);
  --color-neutral-200: var(--ui-color-neutral-200);
  --color-neutral-300: var(--ui-color-neutral-300);
  --color-neutral-400: var(--ui-color-neutral-400);
  --color-neutral-500: var(--ui-color-neutral-500);
  --color-neutral-600: var(--ui-color-neutral-600);
  --color-neutral-700: var(--ui-color-neutral-700);
  --color-neutral-800: var(--ui-color-neutral-800);
  --color-neutral-900: var(--ui-color-neutral-900);
  --color-neutral-950: var(--ui-color-neutral-950);

  --radius-xs: calc(var(--ui-radius) * 0.5);
  --radius-sm: var(--ui-radius);
  --radius-md: calc(var(--ui-radius) * 1.5);
  --radius-lg: calc(var(--ui-radius) * 2);
  --radius-xl: calc(var(--ui-radius) * 3);
  --radius-2xl: calc(var(--ui-radius) * 4);
  --radius-3xl: calc(var(--ui-radius) * 6);
  --text-color-dimmed: var(--ui-text-dimmed);
  --text-color-muted: var(--ui-text-muted);
  --text-color-toned: var(--ui-text-toned);
  --text-color-default: var(--ui-text);
  --text-color-highlighted: var(--ui-text-highlighted);
  --text-color-inverted: var(--ui-text-inverted);
  --background-color-default: var(--ui-bg);
  --background-color-muted: var(--ui-bg-muted);
  --background-color-elevated: var(--ui-bg-elevated);
  --background-color-accented: var(--ui-bg-accented);
  --background-color-inverted: var(--ui-bg-inverted);
  --background-color-border: var(--ui-border);
  --border-color-default: var(--ui-border);
  --border-color-muted: var(--ui-border-muted);
  --border-color-accented: var(--ui-border-accented);
  --border-color-inverted: var(--ui-border-inverted);
  --ring-color-default: var(--ui-border);
  --ring-color-muted: var(--ui-border-muted);
  --ring-color-accented: var(--ui-border-accented);
  --ring-color-inverted: var(--ui-border-inverted);
  --ring-color-bg: var(--ui-bg);
  --divide-color-default: var(--ui-border);
  --divide-color-muted: var(--ui-border-muted);
  --divide-color-accented: var(--ui-border-accented);
  --divide-color-inverted: var(--ui-border-inverted);
  --outline-color-default: var(--ui-border);
  --outline-color-inverted: var(--ui-border-inverted);
  --stroke-default: var(--ui-border);
  --stroke-inverted: var(--ui-border-inverted);
  --fill-default: var(--ui-border);
  --fill-inverted: var(--ui-border-inverted);
}

@layer base {
  body {
    @apply antialiased text-default bg-default scheme-light dark:scheme-dark;
  }

  :root {
    --ui-color-primary-50: var(--color-violet-50);
    --ui-color-primary-100: var(--color-violet-100);
    --ui-color-primary-200: var(--color-violet-200);
    --ui-color-primary-300: var(--color-violet-300);
    --ui-color-primary-400: var(--color-violet-400);
    --ui-color-primary-500: var(--color-violet-500);
    --ui-color-primary-600: var(--color-violet-600);
    --ui-color-primary-700: var(--color-violet-700);
    --ui-color-primary-800: var(--color-violet-800);
    --ui-color-primary-900: var(--color-violet-900);
    --ui-color-primary-950: var(--color-violet-950);

    --ui-color-secondary-50: var(--color-blue-50);
    --ui-color-secondary-100: var(--color-blue-100);
    --ui-color-secondary-200: var(--color-blue-200);
    --ui-color-secondary-300: var(--color-blue-300);
    --ui-color-secondary-400: var(--color-blue-400);
    --ui-color-secondary-500: var(--color-blue-500);
    --ui-color-secondary-600: var(--color-blue-600);
    --ui-color-secondary-700: var(--color-blue-700);
    --ui-color-secondary-800: var(--color-blue-800);
    --ui-color-secondary-900: var(--color-blue-900);
    --ui-color-secondary-950: var(--color-blue-950);

    --ui-color-success-50: var(--color-green-50);
    --ui-color-success-100: var(--color-green-100);
    --ui-color-success-200: var(--color-green-200);
    --ui-color-success-300: var(--color-green-300);
    --ui-color-success-400: var(--color-green-400);
    --ui-color-success-500: var(--color-green-500);
    --ui-color-success-600: var(--color-green-600);
    --ui-color-success-700: var(--color-green-700);
    --ui-color-success-800: var(--color-green-800);
    --ui-color-success-900: var(--color-green-900);
    --ui-color-success-950: var(--color-green-950);

    --ui-color-info-50: var(--color-blue-50);
    --ui-color-info-100: var(--color-blue-100);
    --ui-color-info-200: var(--color-blue-200);
    --ui-color-info-300: var(--color-blue-300);
    --ui-color-info-400: var(--color-blue-400);
    --ui-color-info-500: var(--color-blue-500);
    --ui-color-info-600: var(--color-blue-600);
    --ui-color-info-700: var(--color-blue-700);
    --ui-color-info-800: var(--color-blue-800);
    --ui-color-info-900: var(--color-blue-900);
    --ui-color-info-950: var(--color-blue-950);

    --ui-color-warning-50: var(--color-yellow-50);
    --ui-color-warning-100: var(--color-yellow-100);
    --ui-color-warning-200: var(--color-yellow-200);
    --ui-color-warning-300: var(--color-yellow-300);
    --ui-color-warning-400: var(--color-yellow-400);
    --ui-color-warning-500: var(--color-yellow-500);
    --ui-color-warning-600: var(--color-yellow-600);
    --ui-color-warning-700: var(--color-yellow-700);
    --ui-color-warning-800: var(--color-yellow-800);
    --ui-color-warning-900: var(--color-yellow-900);
    --ui-color-warning-950: var(--color-yellow-950);

    --ui-color-error-50: var(--color-red-50);
    --ui-color-error-100: var(--color-red-100);
    --ui-color-error-200: var(--color-red-200);
    --ui-color-error-300: var(--color-red-300);
    --ui-color-error-400: var(--color-red-400);
    --ui-color-error-500: var(--color-red-500);
    --ui-color-error-600: var(--color-red-600);
    --ui-color-error-700: var(--color-red-700);
    --ui-color-error-800: var(--color-red-800);
    --ui-color-error-900: var(--color-red-900);
    --ui-color-error-950: var(--color-red-950);

    --ui-color-neutral-50: var(--color-slate-50);
    --ui-color-neutral-100: var(--color-slate-100);
    --ui-color-neutral-200: var(--color-slate-200);
    --ui-color-neutral-300: var(--color-slate-300);
    --ui-color-neutral-400: var(--color-slate-400);
    --ui-color-neutral-500: var(--color-slate-500);
    --ui-color-neutral-600: var(--color-slate-600);
    --ui-color-neutral-700: var(--color-slate-700);
    --ui-color-neutral-800: var(--color-slate-800);
    --ui-color-neutral-900: var(--color-slate-900);
    --ui-color-neutral-950: var(--color-slate-950);
  }

  :root,
  .light {
    --ui-primary: var(--ui-color-primary-500);
    --ui-secondary: var(--ui-color-secondary-500);
    --ui-success: var(--ui-color-success-500);
    --ui-info: var(--ui-color-info-500);
    --ui-warning: var(--ui-color-warning-500);
    --ui-error: var(--ui-color-error-500);

    --ui-text-dimmed: var(--ui-color-neutral-400);
    --ui-text-muted: var(--ui-color-neutral-500);
    --ui-text-toned: var(--ui-color-neutral-600);
    --ui-text: var(--ui-color-neutral-700);
    --ui-text-highlighted: var(--ui-color-neutral-900);
    --ui-text-inverted: var(--color-white);

    --ui-bg: var(--color-white);
    --ui-bg-muted: var(--ui-color-neutral-50);
    --ui-bg-elevated: var(--ui-color-neutral-100);
    --ui-bg-accented: var(--ui-color-neutral-200);
    --ui-bg-inverted: var(--ui-color-neutral-900);

    --ui-border: var(--ui-color-neutral-200);
    --ui-border-muted: var(--ui-color-neutral-200);
    --ui-border-accented: var(--ui-color-neutral-300);
    --ui-border-inverted: var(--ui-color-neutral-900);

    --ui-radius: 0.125rem;
    --ui-container: var(--container-7xl);
  }

  .dark {
    --ui-primary: var(--ui-color-primary-400);
    --ui-secondary: var(--ui-color-secondary-400);
    --ui-success: var(--ui-color-success-400);
    --ui-info: var(--ui-color-info-400);
    --ui-warning: var(--ui-color-warning-400);
    --ui-error: var(--ui-color-error-400);

    --ui-text-dimmed: var(--ui-color-neutral-500);
    --ui-text-muted: var(--ui-color-neutral-400);
    --ui-text-toned: var(--ui-color-neutral-300);
    --ui-text: var(--ui-color-neutral-200);
    --ui-text-highlighted: var(--color-white);
    --ui-text-inverted: var(--ui-color-neutral-900);

    --ui-bg: var(--ui-color-neutral-900);
    --ui-bg-muted: var(--ui-color-neutral-800);
    --ui-bg-elevated: var(--ui-color-neutral-800);
    --ui-bg-accented: var(--ui-color-neutral-700);
    --ui-bg-inverted: var(--color-white);

    --ui-border: var(--ui-color-neutral-800);
    --ui-border-muted: var(--ui-color-neutral-700);
    --ui-border-accented: var(--ui-color-neutral-700);
    --ui-border-inverted: var(--color-white);
  }
}

This setup allows you to define light and dark themes, customize colors, and use Tailwind's built-in CSS variables.

TypeScript Configuration

For this section, you'll have to install some additional dependencies:

pnpm
bash
pnpm add -D @vue/tsconfig

For better auto-completion and type checking, configure TypeScript in your project. This step is optional but highly recommended.

  1. Update tsconfig.app.json:
tsconfig.app.json
json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/ui/*": ["src/ui/*"]
    }
  },
  "exclude": ["src/**/*.test.ts"]
}
  1. Add Node.js types to tsconfig.node.json:
tsconfig.node.json
json
{
  "compilerOptions": {
    "types": ["node"]
  }
}
  1. Create tsconfig.test.json for test-specific configurations:
tsconfig.test.json
json
{
  "extends": "./tsconfig.node.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/ui/*": ["src/ui/*"]
    }
  },
  "include": ["src/ui/**/*.test.ts"]
}
  1. Update tsconfig.json to reference all configurations:
tsconfig.json
json
{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" },
    { "path": "./tsconfig.test.json" }
  ]
}

Testing Setup

Vue UI includes tests for all components.

First, install the testing dependencies:

pnpm
bash
pnpm add -D vitest @testing-library/vue jsdom

To configure testing:

  1. Add a type reference to vitest at the top of vite.config.ts:
ts
/// <reference types="vitest" />
  1. Add a test configuration block:
vite.config.ts
ts
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
  },
})
  1. Add a test script to package.json:
package.json
json
{
  "scripts": {
    "test": "vitest"
  }
}

Example Usage

With everything set up, you can start using Vue UI components and composables. Here's an example src/App.vue file:

src/App.vue
vue
<script setup lang="ts">
import App from '@/ui/components/App.vue'
import Button from '@/ui/components/Button.vue'
import { useToast } from '@/ui/composables/useToast'

const toast = useToast()
function onClick() {
  toast.add({
    title: 'Hey there!',
    description: 'This is a toast notification.',
  })
}
</script>

<template>
  <App>
    <div class="p-4 flex flex-col items-start gap-2">
      <h1 class="text-2xl font-bold">
        Welcome to Vue UI
      </h1>

      <Button label="Click Me" @click="onClick" />
    </div>
  </App>
</template>

You're now ready to build your project with Vue UI!