Skip to main content

Next.js

Set up Vortex in a Next.js App Router project.

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

For TypeScript projects, align the React types with React 19:

bun add -d @types/react@^19 @types/react-dom@^19

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. The CLI is included in @vortexlabs/vortex-themes.

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

This creates src/themes/acme/. Run the script again after changing vortex.config.ts or upgrading Vortex or Vortex Themes, 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; responsive styles don't work without it.

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. Restart the dev server after configuration changes.

If you customize breakpoints, point PostCSS at your generated definitions; see Custom breakpoints.

Configure package transpilation

Add Vortex to transpilePackages so Next.js processes the package and its CSS Modules. Keep any other options in your existing config.

Use Vortex 0.15.1 or later with Turbopack; it fixes Text Field CSS Modules compilation in 0.15.0.

next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  transpilePackages: ["@vortexlabs/vortex"],
};

export default nextConfig;

Add the provider and theme

Create a client boundary for the provider:

app/providers.tsx
"use client";

import { VortexProvider } from "@vortexlabs/vortex";
import type React from "react";

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

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

  return (
    <VortexProvider defaultTheme="acme" defaultColorMode="light">
      {children}
    </VortexProvider>
  );
};

export default Providers;

Import the generated theme once in your root layout and wrap your app with Providers. For src/app/, use ../themes/acme/globals.css.

Set data-vortex-theme and data-vortex-color-mode on <html> to match the provider, so the first paint uses the right theme. suppressHydrationWarning prevents warnings when these attributes change before hydration, such as from a saved preference.

app/layout.tsx
import "../src/themes/acme/globals.css";
import type React from "react";
import Providers from "./providers";

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

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

  return (
    <html
      lang="en"
      data-vortex-theme="acme"
      data-vortex-color-mode="light"
      suppressHydrationWarning
    >
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
};

export default RootLayout;

Render components

Vortex components need a Client Component ("use client"), since they use React context and refs. Wrapping a page in Providers doesn't make it one.

app/page.tsx
"use client";

import { Button, Stack, Text } from "@vortexlabs/vortex";

const Page = () => (
  <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 Page;

See the Next.js client boundary guide for composing server and client components.

Fonts

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