The Theme System

Learn how to manage global styles, use compiled CSS variables, and override component styles using the @alias/styles system.

Overview

A robust theming system is essential for any modern application. In JopiJS, theming is split into two complementary approaches:

  1. Global Theme: Defining consistent colors, fonts, and spacing using CSS variables and Tailwind.
  2. Component Theming: The ability to radically change the appearance of a specific component without touching its valid Logic.

In this guide, you will learn:

  • How to set up global variables in global.css.
  • How to configure the Tailwind theme.
  • How the @alias/styles system allows for style injection.
  • How to create a "Theme Module" to override styles across the app.

1. Global Theme (Variables)

The foundation of any theme is a set of global variables. In JopiJS, this is handled in your module's global.css file.

Variables Only: To maintain a predictable and modular architecture, global.css should ideally only contain CSS variables (Custom Properties). Avoid defining global CSS rules (like body { ... }) directly in global.css if they can be handled via Tailwind or scoped components.

Defining Variables

We recommend using standard CSS variables (Custom Properties) for maximum compatibility.

The Complete global.css

Here is a complete example of a recommended global.css. It defines the raw values in :root and then maps them to Tailwind tokens using the @theme block.

File: src/mod_app/global.css

@import "tailwindcss";

/* 1. Define your raw design tokens */
/* :root targets the highest-level element (<html>), making these variables global. */
:root {
    /* Brand Colors */
    --color-primary: #3b82f6;
    --color-secondary: #64748b;
    
    /* Layout & Surfaces */
    --radius-sm: 4px;
    --radius-md: 8px;
    --bg-main: #f8fafc;
    
    /* Typography */
    --font-heading: "Inter", sans-serif;
}

/* 2. Map tokens to Tailwind utilities */
@theme {
    --color-primary: var(--color-primary);
    --color-secondary: var(--color-secondary);
    
    --radius-sm: var(--radius-sm);
    --radius-md: var(--radius-md);
    
    --color-bg-canvas: var(--bg-main);
    
    --font-heading: var(--font-heading);
}

Now you can use classes like bg-primary, rounded-md, or font-heading anywhere in your application, knowing they are safely backed by CSS variables that can be overridden by other modules.


2. Component Theming (@alias/styles)

While global variables are great for consistency, sometimes you need to completely overhaul the look of a specific component—for example, making a "Halloween Theme" or a "Dark Mode" that changes the layout of a card.

JopiJS provides a specialized mechanism for this called the Styles Alias.

How it Works

Every styled component in JopiJS should import its styles via a generated wrapper: style.gen.ts.

// src/mod_ui/@alias/ui/Button/index.tsx

// ❌  Dont do directly:
// import styles from "./style.module.css";
// ✅  But instead use the generated wrapper:
import styles from "./style.gen.ts"; // <--- The magic happens here!
import { useCssModule } from "jopijs/ui";

export default function Button({ children }) {
    useCssModule(styles);
    return <button className={styles.btn}>{children}</button>;
}

When you build your app, style.gen.ts acts as a smart switch:

  1. Default: It loads the local style.module.css.
  2. Override: If it finds a matching file in an @alias/styles folder, it loads that instead.
Default content of style.gen.ts
import styles from "./style.module.css";
export default styles;

Creating a Theme Module

Let's say we want to create a Dark Theme module that overrides our Button styles.

  1. Create a new module: mod_theme_dark
  2. Mirror the path: Create a file structure in @alias/styles that matches the component's path in @alias/ui.

Target Component: @alias/ui/Button
Style Override: @alias/styles/Button

File: src/mod_theme_dark/@alias/styles/Button/style.module.css

/* Totally different styles for the Dark Theme */
.btn {
    background-color: #1e293b;
    color: white;
    border: 1px solid #475569;
    padding: 12px 24px;
    border-radius: 99px; /* Pill shape instead of standard */
}

.btn:hover {
    background-color: #334155;
}

The Result

As soon as mod_theme_dark is active in your project:

  • The style.gen.ts for the Button will automatically detect the file in mod_theme_dark.
  • It will serve the Dark Theme CSS instead of the original CSS.
  • The actual Button.tsx code remains untouched.

Zero-Cost Abstraction: This switch happens at build time (or transparently in dev).
There is no runtime performance penalty for using the theming system.


3. Best Practices

Keep Styles Decoupled

When writing a component, avoid hardcoding colors. Use the global.css file of the module to define Global Theme variables. This makes "light" theming (just changing variables) easy, reserving the @alias/styles system for "heavy" theming (changing layout/structure).

global.css is for Tokens

Keep your global.css clean. It should serve as a dictionary of design tokens (colors, spacing, fonts). If you need to apply these tokens to global elements (like body or h1), do it in a top-level layout component or using Tailwind's base layer, but try to keep the global.css file itself focused on variable definitions.

Use style.gen.ts Always

Even if you don't plan to theme a component right now, always import style.gen.ts instead of style.module.css. This leaves the door open for future theming without refactoring code.

// ✅ Good
import styles from "./style.gen.ts";

// ❌ Bad (Hard to theme later)
import styles from "./style.module.css";

Organizing Theme Modules

If you have a complex application with multiple themes (e.g., "Default", "High Contrast", "Holiday"), keep them in separate modules like:

  • mod_theme_default
  • mod_theme_contrast
  • mod_theme_holiday

You can then enable/disable them easily by renaming the folder (e.g., adding _ prefix to ignore).