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:
npm create vite@latest my-vue-app --template vue-tsBefore going further, you need to install the required dependencies.
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-helpersConfiguring 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:
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:
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 add -D tailwindcss @tailwindcss/vite @egoist/tailwindcss-iconsTo style your project, integrate Tailwind CSS using its Vite plugin:
- Add the plugin to your
vite.config.tsfile:
import tailwindcss from '@tailwindcss/vite'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue(), tailwindcss()],
})- Import Tailwind CSS in your main style file, typically
src/style.css:
@import "tailwindcss";- Optionally, add the
@egoist/tailwindcss-iconsplugin for icons:
@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.
- Add custom configurations to
src/style.cssfor themes, colors, and variants. For example:
@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
@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 add -D @vue/tsconfigFor better auto-completion and type checking, configure TypeScript in your project. This step is optional but highly recommended.
- Update
tsconfig.app.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/ui/*": ["src/ui/*"]
}
},
"exclude": ["src/**/*.test.ts"]
}- Add Node.js types to
tsconfig.node.json:
{
"compilerOptions": {
"types": ["node"]
}
}- Create
tsconfig.test.jsonfor test-specific configurations:
{
"extends": "./tsconfig.node.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/ui/*": ["src/ui/*"]
}
},
"include": ["src/ui/**/*.test.ts"]
}- Update
tsconfig.jsonto reference all configurations:
{
"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 add -D vitest @testing-library/vue jsdomTo configure testing:
- Add a type reference to
vitestat the top ofvite.config.ts:
/// <reference types="vitest" />- Add a
testconfiguration block:
import { defineConfig } from 'vite'
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
},
})- Add a test script to
package.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:
<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!