Set up a Vite + React project with Proper UI in minutes — the Tailwind Vite plugin, path aliases, and provider wiring included.
Vite has no framework-specific router or server/client split to work around, which makes it the simplest target to wire up by hand — but the CLI still saves you the boilerplate.
Installation
Run this from the root of your Vite project:
npx @properui/cli@latest init --vite
It detects your Tailwind version, src/ layout, and package manager, then writes components.json, the theme token file, utils/cx.ts, adds the Tailwind imports and @source line to your CSS file, and wraps your app entry in ThemeProvider. It doesn't install npm packages, touch vite.config.ts, or add RouterProvider — set those up yourself as shown below.
Manual Installation
1. Install the package and the Tailwind Vite plugin
npm install @properui/ui react-aria-components next-themes
npm install -D @tailwindcss/vite
2. Register the Tailwind plugin and the @/ alias
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import path from "node:path";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
Match the alias in tsconfig.json so your editor and type-checker resolve it too:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
3. Import the stylesheet
@properui/ui/styles/globals.css already bundles the Tailwind import, the design tokens, typography, and every plugin the components need — you only add one @source line so Tailwind's scanner also reads the package's own class names:
/* src/index.css */
@import "@properui/ui/styles/globals.css";
@source "../node_modules/@properui/ui/src/**/*.{ts,tsx}";
4. Wire up the providers
@properui/ui/providers only exports a Next.js-flavored RouterProvider (it reads useRouter from next/navigation internally), so it doesn't work in Vite. Use React Aria's own RouterProvider from react-aria-components instead, and hand it your router's navigate function — useNavigate if you're using react-router:
// src/providers.tsx
import type { ReactNode } from "react";
import { RouterProvider } from "react-aria-components";
import { useNavigate } from "react-router-dom";
import { ThemeProvider } from "@properui/ui/providers";
export const Providers = ({ children }: { children: ReactNode }) => {
const navigate = useNavigate();
return (
<ThemeProvider>
<RouterProvider navigate={navigate}>{children}</RouterProvider>
</ThemeProvider>
);
};
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { App } from "./app";
import "./index.css";
import { Providers } from "./providers";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<Providers>
<App />
</Providers>
</BrowserRouter>
</StrictMode>,
);
No router at all? Drop RouterProvider entirely. Every component still renders and behaves correctly — href links just do a full page load instead of a client-side transition.
Using Proper UI components
Import a component from its subpath, the same one the CLI writes when you run add:
import { Button } from "@properui/ui/components/base/buttons/button";
export const App = () => <Button size="md">Get started</Button>;
Theme toggle
next-themes works outside of Next.js too — it only needs localStorage and a document, both of which a Vite SPA has:
// src/components/theme-toggle-button.tsx
import { useTheme } from "next-themes";
import { Button } from "@properui/ui/components/base/buttons/button";
export const ThemeToggleButton = () => {
const { resolvedTheme, setTheme } = useTheme();
return <Button onPress={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}>Toggle theme</Button>;
};