Monsoon v0.2.0
Made by Alexandre Kohler Star

Installation

Install Monsoon in React with Vite

In a Vite React app Monsoon is one import in src/main.jsx. Vite resolves monsooncss to the prebuilt stylesheet, bundles it with your own CSS and fingerprints the file. Components just use className.

3 min readReact with Vite

Install

Start from npm create vite@latest my-app -- --template react (or react-ts), then choose one of the two setups.

Link the stylesheet in the root index.html. Vite leaves external URLs alone.

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>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/monsooncss@0.2.0/dist/monsoon.min.css" />
    <script>(function(){var t;try{t=localStorage.getItem('monsoon-theme')}catch(e){}var d=t==='dark'||(t!=='light'&&matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.classList.add(d?'dark':'light')})()</script>
  </head>
  <body class="bg-background text-foreground antialiased">
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

The Vite starter ships demo rules in src/index.css and src/App.css (a centered #root, its own colors). Empty or delete them, otherwise they paint over the Monsoon theme.

Use the classes

Classes go in className. Keep every class name as a complete string, even when it is conditional, so the purge step can find it later:

src/components/PlanCard.jsx
const tones = {
  primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
  secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
};

export function Button({ tone = 'primary', className = '', ...props }) {
  return (
    <button
      type="button"
      className={'rounded-lg px-4 py-2 font-medium ' + tones[tone] + ' ' + className}
      {...props}
    />
  );
}

export function PlanCard({ name, price, featured = false }) {
  return (
    <article className={featured ? 'rounded-2xl border bg-surface p-6 space-y-4 ring-2 ring-ring' : 'rounded-2xl border bg-surface p-6 space-y-4 shadow-sm'}>
      <h2 className="text-lg font-semibold">{name}</h2>
      <p className="text-3xl font-bold">{price}</p>
      <Button tone={featured ? 'primary' : 'secondary'} className="w-full">
        Choose {name}
      </Button>
    </article>
  );
}
src/App.jsx
import { PlanCard } from './components/PlanCard.jsx';

export default function App() {
  return (
    <main className="mx-auto max-w-5xl px-6 py-12">
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
        <PlanCard name="Starter" price="$0" />
        <PlanCard name="Team" price="$12" featured />
        <PlanCard name="Business" price="$40" />
      </div>
    </main>
  );
}

Dark mode

Keep the head script from the install step in index.html in both setups: it runs before React loads, so the first paint already has the right scheme. The toggle then only flips the class and remembers the choice:

src/components/ThemeToggle.jsx
import { useState } from 'react';

function readTheme() {
  return document.documentElement.classList.contains('dark') ? 'dark' : 'light';
}

export default function ThemeToggle() {
  const [theme, setTheme] = useState(readTheme);

  function toggle() {
    const next = theme === 'dark' ? 'light' : 'dark';
    const root = document.documentElement;
    root.classList.remove('dark', 'light');
    root.classList.add(next);
    try { localStorage.setItem('monsoon-theme', next); } catch (e) {}
    setTheme(next);
  }

  return (
    <button
      type="button"
      onClick={toggle}
      aria-pressed={theme === 'dark'}
      className="rounded-lg border px-3 py-2 text-sm hover:bg-muted"
    >
      {theme === 'dark' ? 'Light mode' : 'Dark mode'}
    </button>
  );
}

To force a scheme on one part of the tree, add the class to that element: <aside className="dark bg-background text-foreground p-6">.

Theme with --seed

React passes custom properties in the style object as they are, so the seed can come from state, props or a user setting:

src/components/BrandPreview.jsx
import { useState } from 'react';

export default function BrandPreview() {
  const [seed, setSeed] = useState('#0d9488');

  return (
    <section style={{ '--seed': seed }} className="rounded-2xl bg-surface p-6 space-y-4">
      <input type="color" value={seed} onChange={(e) => setSeed(e.target.value)} />
      <button type="button" className="rounded-lg bg-primary text-primary-foreground px-4 py-2">
        Primary
      </button>
    </section>
  );
}

In TypeScript, cast the object: style={{ '--seed': seed } as React.CSSProperties}. Every semantic token (primary, secondary, accent, surfaces, muted text, borders and the focus ring) is derived from --seed in the browser. Theming lists the tokens.

Using the purge CLI

Write the purged file before every production build:

package.json
{
  "scripts": {
    "dev": "vite",
    "css:purge": "monsooncss purge \"index.html\" \"src/**/*.{js,jsx,ts,tsx}\" -o src/monsoon.purged.css",
    "build": "npm run css:purge && vite build",
    "preview": "vite preview"
  }
}

Then let Vite swap the import only for builds. import 'monsooncss' stays in src/main.jsx, development keeps the full file (so a new class works the moment you type it), and vite build bundles the purged one:

vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { fileURLToPath } from 'node:url';

const purged = fileURLToPath(new URL('./src/monsoon.purged.css', import.meta.url));

export default defineConfig(({ command }) => ({
  plugins: [react()],
  resolve: {
    alias: command === 'build' ? { monsooncss: purged } : {},
  },
}));

Using the CDN setup instead? Write to public/monsoon.css and change the <link> in index.html to /monsoon.css.

The purge CLI reads your files as plain text and keeps every class name it can see. It cannot see a name that is assembled at runtime, so write each class in full: isActive ? 'bg-primary' : 'bg-muted' survives a purge, 'bg-' + color does not. Names that only exist at runtime (from a database, a CMS or an API) go in --safelist bg-success,bg-danger.

The purged file always keeps the reset, the theme tokens, the .dark and .light scopes and the keyframes, so dark mode and --seed keep working. See the purge CLI reference for every option.

Next steps

Frequently asked questions

Does Monsoon need a PostCSS or Vite plugin in React?

No. It is a finished stylesheet, so Vite handles it like any other CSS import. Nothing scans your components in development.

Can I import Monsoon from a component instead of main.jsx?

You can, but import it once at the entry point. Vite then places it before component styles, and your own rules keep winning on equal specificity.

No recent searches

Try , , or .

Recent

Loading the search index

No results for ""

Check the spelling, or search a class name such as px-4.