Installation

Vite

Wavi in a Vite + React app: no Next.js needed. This guide is the one we test against: Vite 8, React 19, Tailwind CSS 4.

Requirements

React 19, Tailwind CSS 4, Vite 6 or newer, Node.js 20 or newer. Starting from an existing app? Skip step 1.
  1. Create the app

    $ npx create-vite@latest my-app --template react-ts
  2. Install the package and Tailwind CSS

    $ npm install @waveinno/ui
    $ npm install -D tailwindcss @tailwindcss/vite

    The package does not need Next.js: its only Next.js part lives in @waveinno/ui/next, which a Vite app never imports.

  3. Add Tailwind to Vite

    vite.config.ts
    import tailwindcss from "@tailwindcss/vite";
    import react from "@vitejs/plugin-react";
    import { defineConfig } from "vite";
    
    export default defineConfig({
      plugins: [react(), tailwindcss()],
    });
  4. Add the styles

    Replace everything in src/index.css (Vite's starter styles fight the tokens), and delete src/App.css.

    src/index.css
    @import "tailwindcss";
    @import "@waveinno/ui/styles.css";
    
    /* Tailwind only generates classes it can see: point it at the package. */
    @source "../node_modules/@waveinno/ui/dist";
    
    /* Noto Sans Bengali after each family: it carries Bangla and the Taka sign (৳). */
    :root {
      --font-inter: "Inter", "Noto Sans Bengali";
      --font-jakarta: "Plus Jakarta Sans", "Noto Sans Bengali";
      --font-jetbrains: "JetBrains Mono";
    }
  5. Fonts and the theme script

    In index.html: the font families, and the script that applies a saved dark theme before the first paint. Prefer self-hosting? Install @fontsource-variable/inter and friends and import them in main.tsx instead of the links.

    index.html
    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>My app</title>
    
        <!-- The four families of the design system. -->
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
        <link
          rel="stylesheet"
          href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@500;600;700&family=JetBrains+Mono:wght@400;500&family=Noto+Sans+Bengali:wght@400;500;600;700&display=swap"
        />
    
        <!-- Applies the saved light/dark theme and density before first paint: no flash.
             The same script as themeBootScript() from "@waveinno/ui". -->
        <script>(function(){try{var t=localStorage.getItem("waveinno.theme");if(t!=="light"&&t!=="dark")t=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";var d=localStorage.getItem("waveinno.density")||"default";var e=document.documentElement;e.setAttribute("data-theme",t);e.setAttribute("data-density",d);e.style.colorScheme=t;if(localStorage.getItem("waveinno.panel")==="1")e.setAttribute("data-panel-collapsed","");}catch(_){}})();</script>
      </head>
      <body class="bg-canvas text-body antialiased">
        <div id="root"></div>
        <script type="module" src="/src/main.tsx"></script>
      </body>
    </html>
  6. Add the providers

    src/main.tsx
    import { StrictMode } from "react";
    import { createRoot } from "react-dom/client";
    
    import { Toaster, TooltipProvider } from "@waveinno/ui";
    
    import App from "./App";
    import "./index.css";
    
    createRoot(document.getElementById("root")!).render(
      <StrictMode>
        <TooltipProvider delayDuration={300}>
          <App />
          <Toaster />
        </TooltipProvider>
      </StrictMode>,
    );
  7. Use the components

    src/App.tsx
    import { useState } from "react";
    
    import { Alert, Button, DatePicker, Menu, MenuItem, ThemeToggle, toast } from "@waveinno/ui";
    
    export default function App() {
      const [day, setDay] = useState(""); // an ISO day, "" for none
      return (
        <div className="flex min-h-dvh gap-8 p-8">
          <Menu variant="raised" defaultValue="overview" className="w-60">
            <MenuItem value="overview">Overview</MenuItem>
            <MenuItem value="sessions" count={12}>Sessions</MenuItem>
            <MenuItem value="settings">Settings</MenuItem>
          </Menu>
          <main className="max-w-md flex-1 space-y-4">
            <div className="flex items-center justify-between">
              <h1 className="disp text-2xl font-bold">Hello, WAVEINNO</h1>
              <ThemeToggle />
            </div>
            <Alert tone="success" title="Running on Vite" />
            <DatePicker value={day} onChange={setDay} />
            <Button variant="primary" onClick={() => toast.success("Saved")}>Save</Button>
          </main>
        </div>
      );
    }
    $ npx vite

With React Router#

Without a router adapter, links in the package are plain <a> tags: they work, with a full page load. With React Router, hand the package its Link, location and navigate once, and the shell, breadcrumbs and command palette navigate in place. See Routing for the details.

src/ui-router.tsx
import { Link, useLocation, useNavigate } from "react-router";

import { RouterProvider, type RouterAdapter } from "@waveinno/ui";

// Tell the package to link and navigate through React Router.
const reactRouter: RouterAdapter = {
  Link: ({ href, ...props }) => <Link to={href} {...props} />,
  usePathname: () => useLocation().pathname,
  useNavigate: () => {
    const navigate = useNavigate();
    return (href) => navigate(href);
  },
};

// Inside <BrowserRouter> (or your data router's root route):
export function UiRouter({ children }: { children: React.ReactNode }) {
  return <RouterProvider adapter={reactRouter}>{children}</RouterProvider>;
}

Troubleshooting#

  • Components look unstyled. The @source path must be relative to src/index.css and reach node_modules/@waveinno/ui/dist; in a monorepo it may need more ../.
  • Odd margins, centred text, purple links. Vite's starter index.css or App.css is still loaded.
  • “Could not resolve next/link”. Something imports @waveinno/ui/next; in Vite, use RouterProvider instead.
  • A white flash before dark mode. The theme script is missing from index.html, or runs after the stylesheet.