Skip to main content

Astro

Set up Vortex in an existing Astro project with React islands.

Install the packages

Vortex components are React components, so add Astro's React integration first:

bunx astro add react

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 ./src/themes"
}
bun run build:theme

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

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 vite.resolve.noExternal so Astro processes its CSS Modules when rendering pages. Without it, the build fails. Keep your existing integrations and options.

astro.config.mjs
import react from "@astrojs/react";
import { defineConfig } from "astro/config";

export default defineConfig({
  integrations: [react()],
  vite: {
    resolve: {
      noExternal: ["@vortexlabs/vortex"],
    },
  },
});

Restart the dev server after configuration changes.

Set up the base layout

Import the generated theme once in your base layout, and set the theme attributes on <html> so the first paint uses the right theme:

src/layouts/Layout.astro
---
import "../themes/acme/globals.css";
---

<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" />
    <title>Vortex app</title>
  </head>
  <body>
    <slot />
  </body>
</html>

Add a provider to each island

Each React island is a separate React tree, so wrap each one in its own VortexProvider. scoped keeps the island's theme and color mode local; the layout sets the page's initial theme. Components that share provider state must be in the same island.

src/components/Hero.tsx
import { Button, Stack, Text, VortexProvider } from "@vortexlabs/vortex";

const Hero = () => (
  <VortexProvider scoped defaultTheme="acme" defaultColorMode="light">
    <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>
  </VortexProvider>
);

export default Hero;

Render the island with a client:* directive so it hydrates:

src/pages/index.astro
---
import Layout from "../layouts/Layout.astro";
import Hero from "../components/Hero";
---

<Layout>
  <Hero client:load />
</Layout>
bun run dev

Fonts

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