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, and utils/cx.ts. In your CSS entry file it inserts the full stylesheet block right after your existing @import "tailwindcss" line: the theme import, the plugin registrations, the custom variants, and the utility layer the components need, not just a bare @source line. It also writes the @ alias into both halves Vite needs, since Vite has no built-in tsconfig-paths support: a paths entry in tsconfig.app.json/tsconfig.json and a matching resolve.alias in vite.config.ts. Unless you pass --no-providers, it copies the registry's ThemeProvider (the next-themes-based one, with forcedTheme/enableSystem props available if you need to lock or relax the theme) and wraps your app entry in it; it does not add RouterProvider, since that depends on which router you're using (see below). Unless you pass --no-tooling-ignores, it also adds ESLint and Prettier ignore entries for the vendored component directories, so a strict consumer's lint/format config doesn't fail on copied-in source. init installs nothing itself: it prints the install command last, after every file it wrote, unless you pass --install, in which case it runs that install for you.
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 exports only ThemeProvider. The Next.js-flavored RouterProvider sits on its own subpath: it reads useRouter from next/navigation, so importing it from Vite fails the build. 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>;
};
If your tool supports MCP servers, the Proper UI MCP lets the agent search screens, flows and sections and plan the install, with the CLI writing the files.