Monsoon v0.2.0
Made by Alexandre Kohler Star

Installation

Install Monsoon in Astro

Astro renders HTML on the server and ships no JavaScript by default, which suits a stylesheet with no runtime. Import Monsoon in your base layout and every page that uses the layout gets it.

2 min readAstro

Install

src/layouts/Layout.astro
---
interface Props {
  title: string;
}

const { title } = Astro.props;
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{title}</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/monsooncss@0.2.0/dist/monsoon.min.css" />
    <script is:inline>(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">
    <slot />
  </body>
</html>

Keep is:inline on the theme script. Without it Astro processes the script as a module, which runs after the page has painted.

Use the classes

Use class for static names and class:list to combine them with conditions. Both render to a plain class attribute:

src/components/PlanCard.astro
---
interface Props {
  name: string;
  price: string;
  featured?: boolean;
}

const { name, price, featured = false } = Astro.props;
---

<article class:list={['rounded-2xl border bg-surface p-6 space-y-4', { 'ring-2 ring-ring': featured, 'shadow-sm': !featured }]}>
  <h2 class="text-lg font-semibold">{name}</h2>
  <p class="text-3xl font-bold">{price}</p>
  <a
    href="/signup"
    class:list={[
      'block rounded-lg px-4 py-2 text-center font-medium',
      featured ? 'bg-primary text-primary-foreground hover:bg-primary/90' : 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
    ]}
  >
    Choose {name}
  </a>
</article>
src/pages/pricing.astro
---
import Layout from '../layouts/Layout.astro';
import PlanCard from '../components/PlanCard.astro';
---

<Layout title="Pricing">
  <main class="mx-auto max-w-5xl px-6 py-12">
    <div class="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>
</Layout>

Dark mode

A processed <script> is fine for the toggle itself, since the head script has already set the scheme:

src/components/ThemeToggle.astro
<button id="theme-toggle" type="button" class="rounded-lg border px-3 py-2 text-sm hover:bg-muted">
  Toggle theme
</button>

<script>
  document.getElementById('theme-toggle')?.addEventListener('click', () => {
    const root = document.documentElement;
    const next = root.classList.contains('dark') ? 'light' : 'dark';
    root.classList.remove('dark', 'light');
    root.classList.add(next);
    try { localStorage.setItem('monsoon-theme', next); } catch (e) {}
  });
</script>

With view transitions (<ClientRouter />), Astro replaces the attributes of <html> on every navigation, which drops the class. Turn the head script into a named function and run it again after each swap:

src/layouts/Layout.astro
<script is:inline>
  function applyTheme() {
    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');
  }
  applyTheme();
  document.addEventListener('astro:after-swap', applyTheme);
</script>

With view transitions, register the toggle listener inside document.addEventListener('astro:page-load', ...) as well, so it binds to the new button after each navigation.

Theme with --seed

Astro accepts a style object and keeps custom property names as they are:

src/components/BrandSection.astro
---
const { brandColor = '#0d9488' } = Astro.props;
---

<section style={{ '--seed': brandColor }} class="rounded-2xl bg-surface p-6 space-y-4">
  <slot />
  <button type="button" class="rounded-lg bg-primary text-primary-foreground px-4 py-2">Primary</button>
</section>

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

package.json
{
  "scripts": {
    "dev": "astro dev",
    "css:purge": "monsooncss purge \"src/**/*.{astro,html,md,mdx,js,ts,jsx,tsx,vue,svelte}\" -o src/styles/monsoon.purged.css",
    "build": "npm run css:purge && astro build"
  }
}

A tiny inline integration points monsooncss at the purged file for astro build only:

astro.config.mjs
import { defineConfig } from 'astro/config';
import { fileURLToPath } from 'node:url';

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

export default defineConfig({
  integrations: [
    {
      name: 'monsoon-purged-css',
      hooks: {
        'astro:config:setup': ({ command, updateConfig }) => {
          if (command === 'build') {
            updateConfig({ vite: { resolve: { alias: { monsooncss: purged } } } });
          }
        },
      },
    },
  ],
});

With the CDN setup, write to public/monsoon.css and change the <link> href 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. Content collections count too: include src/content/** in the glob if your Markdown uses classes.

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

Do framework islands in Astro see Monsoon classes?

Yes. The stylesheet is global, so React, Vue, Svelte and Solid islands use the same classes as .astro components.

Should the stylesheet go in every page?

Import it once in the shared layout. Every page that uses the layout gets it, and Astro deduplicates the file.

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.