Skip to main content

React Router

Set up Vortex in an existing React Router project in framework mode. Remix v2 apps need to upgrade to React Router first.

Install the packages

Vortex packages are private and available by invitation. Complete the package access prerequisites before installing.

bun add @vortexlabs/vortex @vortexlabs/vortex-themes
bun add -d postcss@^8 @csstools/postcss-global-data postcss-custom-media cssnano

Generate your theme

Create vortex.config.ts at your project root. Replace acme with your theme name; an empty definition inherits Vortex's defaults.

vortex.config.ts
import type { VortexThemeConfig } from "@vortexlabs/vortex-themes";

const config: VortexThemeConfig = {
  themes: {
    acme: {},
  },
};

export default config;

Add a script to your existing package.json:

package.json
"scripts": {
  "build:theme": "vortex-themes theming --output ./app/themes"
}
bun run build:theme

This creates app/themes/acme/. Run it again after changing vortex.config.ts or upgrading Vortex packages, before starting or building your app.

Configure PostCSS

Vortex styles use @custom-media breakpoint queries that your build must resolve. Add the package's PostCSS preset:

postcss.config.mjs
import { config } from "@vortexlabs/vortex/config/postcss";

export default config;

If you already have a PostCSS config, merge the preset's plugins into it. If you customize breakpoints, point PostCSS at your generated definitions; see Custom breakpoints.

Bundle Vortex for server rendering

Add Vortex to ssr.noExternal so Vite processes its CSS Modules for the server build. Without it, the production server fails to start. Keep your existing plugins and options.

vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [reactRouter()],
  ssr: {
    noExternal: ["@vortexlabs/vortex"],
  },
});

Restart the dev server after configuration changes.

Add the provider and theme

In your root route, import the generated theme, wrap children with VortexProvider, and set the theme attributes on <html> so the first paint uses the right theme:

app/root.tsx
import "./themes/acme/globals.css";
import { VortexProvider } from "@vortexlabs/vortex";
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
import type React from "react";

type Props = {
  children: React.ReactNode;
};

export const Layout = (props: Props) => {
  const { children } = props;

  return (
    <html lang="en" data-vortex-theme="acme" data-vortex-color-mode="light">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <VortexProvider defaultTheme="acme" defaultColorMode="light">
          {children}
        </VortexProvider>
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
};

const App = () => <Outlet />;

export default App;

Use a component

app/routes/home.tsx
import { Button, Stack, Text } from "@vortexlabs/vortex";

const Home = () => (
  <Stack gap={4}>
    <Text as="h1" size={{ xs: "heading-xl-mobile", sm: "heading-xl-desktop" }}>Hello Vortex</Text>
    <Button variant="primary">Get started</Button>
  </Stack>
);

export default Home;
bun run dev

Fonts

Vortex doesn't load font files. Follow the Fonts guide to load the default fonts or your own.