React Router
Add Vamos UI to a React Router v7 app in framework mode. Remix v2 apps follow the same steps after the upgrade to React Router v7.
Requirements: React 19, Tailwind CSS v4, Node 20 or newer.
Steps
- Create the app
pnpm create react-router@latest my-panel cd my-panelThe default template already uses Tailwind v4 through
@tailwindcss/viteand hasapp/app.css. In an existing app, add it withpnpm add -D tailwindcss @tailwindcss/viteandtailwindcss()invite.config.ts. - Install the kit
pnpm add @vamos-ui/react - Import the styles
Replace the template's
app/app.css(its@themefont and thehtml, bodybackground rules would override the tokens):app/app.css@import "tailwindcss"; @import "@vamos-ui/react/styles.css"; /* The data-font="editorial" preset reads these variables. */ :root { --ff-onest: "Onest"; --ff-instrument: "Instrument Serif"; --ff-jetbrains: "JetBrains Mono"; }No @source line neededImporting@vamos-ui/react/styles.cssaftertailwindcssis enough: Tailwind v4 also scans the package the stylesheet comes from, so the classes used inside the components are generated. - Link adapter for the router
The kit renders plain
<a href>by default. This adapter maps its link props onto React Router'sLinkand gives it anavigate():// app/vamos-router.tsx import * as React from "react"; import { Link, useNavigate } from "react-router"; import type { VamosLinkProps, VamosProviderProps } from "@vamos-ui/react"; /** Kit links (sidebar, breadcrumb, pagination…) become React Router links. */ export const RouterLink = React.forwardRef<HTMLAnchorElement, VamosLinkProps>(function RouterLink( { href, prefetch, replace, ...props }, ref, ) { return <Link ref={ref} to={href} replace={replace} prefetch={prefetch ? "intent" : "none"} {...props} />; }); /** navigate() for the kit (command palette, row clicks). */ export function useVamosNavigate(): NonNullable<VamosProviderProps["navigate"]> { const navigate = useNavigate(); return React.useCallback( (href, options) => navigate(href, { replace: options?.replace, preventScrollReset: options?.scroll === false }), [navigate], ); } - Theme attributes, boot script, fonts and providers in root.tsx
The six axes are attributes on
<html>(or any element):class="dark",data-material="glass|solid",data-accent="lime|ocean|ember|rose|graphite",data-density="comfortable|compact",data-font="editorial|grotesk|corporate|system"and, optionally,data-radius="sharp|round"for corners. The boot script reads the user's saved choice fromlocalStorage(keyvamos-theme) before the first paint. SeeThemingfor every token.The page is server-rendered, so the boot script goes in
Layout's<head>andsuppressHydrationWarningkeeps React quiet about the attributes it sets. The providers wrap theOutlet, becauseLayoutalso renders the error boundary.// app/root.tsx import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; import type { Route } from "./+types/root"; import { TooltipProvider, VamosProvider } from "@vamos-ui/react"; import { RouterLink, useVamosNavigate } from "./vamos-router"; import "./app.css"; export const links: Route.LinksFunction = () => [ { rel: "preconnect", href: "https://fonts.googleapis.com" }, { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Onest:wght@100..900&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@100..800&display=swap" }, ]; // Restore the saved theme axes before the first paint. const boot = `try{var s=JSON.parse(localStorage.getItem("vamos-theme")||"{}");var h=document.documentElement;if(s.dark)h.classList.add("dark");h.dataset.material=s.material||"glass";h.dataset.accent=s.accent||"lime";h.dataset.density=s.density||"comfortable";h.dataset.font=s.font||"editorial"}catch(e){}`; export function Layout({ children }: { children: React.ReactNode }) { return ( <html lang="en" data-material="glass" data-accent="lime" data-density="comfortable" data-font="editorial" suppressHydrationWarning > <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <script dangerouslySetInnerHTML={{ __html: boot }} /> <Meta /> <Links /> </head> <body className="min-h-dvh font-sans"> {children} <ScrollRestoration /> <Scripts /> </body> </html> ); } export default function App() { const navigate = useVamosNavigate(); return ( <VamosProvider locale="en-US" Link={RouterLink} navigate={navigate}> <TooltipProvider> <Outlet /> </TooltipProvider> </VamosProvider> ); } - Use a component
// app/routes/home.tsx import { Badge, Button, Card, CardBody, CardHeader } from "@vamos-ui/react"; export default function Welcome() { return ( <main className="mx-auto grid max-w-xl gap-4 p-8"> <Card> <CardHeader title="Spring campaign" description="Newsletter · 12,480 recipients" action={<Badge tone="success" dot>Scheduled</Badge>} /> <CardBody className="flex gap-2"> <Button variant="primary">Publish</Button> <Button>Preview</Button> </CardBody> </Card> </main> ); }pnpm dev
With the CLI instead
Prefer owning the source? Instead of the npm package, let the CLI copy the kit into your project. It detects the framework, Tailwind v4 and your package manager, writes vamos.json, adds the stylesheet import to app/app.css and prints the <html> attributes:
npx vamos-ui init
npx vamos-ui add button card badgeComponents land in components/vamos (or src/components/vamos) and are imported from your alias, for example @/components/vamos. Every written file is recorded in .vamos-lock.json, so npx vamos-ui diff and upgrade keep your edits apart from kit updates.
app/ with the ~/* alias. After init, set "uiDir": "app/components/vamos", "blocksDir": "app/components/blocks" and "importAlias": "~/components/vamos" in vamos.json before the first add.Remix v2
Remix v2 continues as React Router v7. Upgrade with the official Remix upgrade guide first, then follow the steps above: the imports change from @remix-run/react to react-router, and everything kit-related is identical.