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!