What “Theming” Means in CSS-in-JS

Theming is the ability to change a site’s visual style consistently—swapping color palettes for dark mode being the classic use case. For users, this is about choosing a theme or honoring an OS-level preference. For developers, good theming is largely a development-time concern: themes should be declaratively defined up front and swapped at runtime without plumbing changes through components.

Two broad strategies exist for switching visuals. You can change the CSS selectors an element references (e.g., different class names), or you can swap the actual style definitions while keeping the selectors untouched. In practice, themes tend to define a bounded set of CSS entities referenced by many HTML nodes—think datagrids, tree views, or code editors. Replacing style definitions is therefore usually cheaper than traversing the DOM and rewriting class attributes.

Native CSS played with this idea via alternate stylesheets: multiple stylesheets carrying identical rule names, with only one active at a time.

<link rel="stylesheet" href="default.css" title="Default" />
<link rel="alternate stylesheet" href="dark.css" title="Dark" />

The browser picks one based on setting, which works because both files define the same selectors with differing values. Unfortunately, browser support is inconsistent, and the approach never gained traction.

Most CSS-in-JS libraries take the opposite path, automating scoping by generating unique class names per component. That makes theming hard: changing a theme requires updating the markup along with the styles. Mimcss sits somewhere in the middle. Styles are written in TypeScript classes that mirror CSS files, and inherited properties keep a single generated name across derived classes—meaning a new theme can be activated without touching the HTML.

Writing Styles as TypeScript Classes

Mimcss models a stylesheet as a Style Definition class. Properties inside the class declare CSS rules via a small DSL that stays close to native CSS syntax.

class MyStyles extends css.StyleDefinition
{
    significant = this.$class({ fontWeight: 700, color: "red" })
}

Rules are activated and deactivated by constructing and destroying instances. Activation writes CSS rules to the DOM and returns an instance object; properties on that instance expose generated names for use in markup.

let styles = css.activate(MyStyles);
// use styles.significant.name as-is
css.deactivate(MyStyles);

Mimcss generates unique names for classes, IDs, and other named entities on first activation. These names persist across activate/deactivate cycles. In development builds, names incorporate the class and property (e.g., MyStyles_significant); production yields short hashes. The fact that name generation is tied to the class where a property is first declared is key to the theming model.

Inheritance as a Theming Mechanism

CSS-in-JS libraries typically dispatch theme changes through provider components or context, forcing re-renders. Mimcss instead treats a theme as a derived stylesheet class. A base declaration class sets out named rules without values; derived classes supply values and override defaults.

class Base extends css.StyleDefinition
{
    pad = this.$class({ padding: 4 })
}

class Derived extends Base
{
    pad = this.$class({ padding: 8 })
}

Activating Derived produces a pad rule whose generated name is Base_pad, not Derived_pad. The name samples from the base class, but the style declaration—the padding: 8px—comes from the derived class. Extending this pattern across multiple implementations yields a single stable selector name with swappable values.

class AnotherDerived extends Base
{
    pad = this.$class({ padding: 16 })
}

All three classes share the Base_pad name. This is one of the major downsides of class inheritance as a theming tool: the first defined name wins. But in Mimcss, to avoid sticky generated names from the first activated class, theme declarations are expected to slot styles into a purpose-built ThemeDefinition base class.

Defining and Activating a Theme

Theme declarations derive from ThemeDefinition rather than StyleDefinition. The declaration class outlines the interface—names of classes, custom properties, and their types—without enclosing any styles.

class BorderTheme extends css.ThemeDefinition
{
    borderShape = this.$class()
}

class SquareBorderTheme extends BorderTheme
{
    borderShape = this.$class({ borderRadius: 0 })
}

class RoundBorderTheme extends BorderTheme
{
    borderShape = this.$class({ borderRadius: 20 })
}

Runtime activation targets the concrete implementation.

let theme: BorderTheme = css.activate(RoundBorderTheme);
// component rendering uses theme.borderShape.name

Activating only one concrete class per declaration chain is enforced by ThemeDefinition: e.g., RoundBorderTheme and SquareBorderTheme cannot both be active. Across independent declaration branches, multiple themes can coexist.

Sharing Theme Values Across Styles

Typical themes also define recurring colors, sizes, and fonts. In Mimcss these surface as custom CSS properties exposed by the theme declaration. A declaration might provide a foreground and background variable:

class ColorTheme extends css.ThemeDefinition
{
    fgColor = this.$var("color", undefined, "--global-fg")
    bgColor = this.$var("color", undefined, "--global-bg")
}

class LightTheme extends ColorTheme
{
    fgColor = "black"
    bgColor = "white"
}

Components that need theme-driven styling can leverage theme properties through the $use call inside a style definition.

class MyStyles extends css.StyleDefinition
{
    container = this.$class({
        color: this.$use(ColorTheme).fgColor,
        backgroundColor: this.$use(ColorTheme).bgColor
    })
}

At render time, these become actual var(--global-fg) references; the generated rules look like this:

.n153 { color: var(--global-fg); background-color: var(--global-bg) }

This decouples component code from theme implementations. A component knows only the declaration class, while vendors can ship new theme classes as separate packages. For systems with their own naming conventions—say, Material Design’s --mdc-theme--primary—the third $var argument overrides Mimcss’ generated name. Concrete implementations just supply values because the name binding derives from the declaration.

Multiple Themes on One Page

Only one class from a given theme hierarchy can be active by default, which is generally adequate for full-page themes. Side-by-side theme previews are uncommon but not impossible, and Mimcss supports them with namespace-scoped overrides.

The approach redefines the theme’s custom properties within additional CSS rules, allowing distinct regions to pull from different theme implementations. Suppose the component needs a light section on top and a dark one beneath it. A block class patterns the shared layout, and top and bottom classes inherit those properties while pinning different variable sets:

class MyStyles extends css.StyleDefinition
{
    theme = this.$use(ColorTheme)

    block = this.$class({ color: this.theme.fgColor, backgroundColor: this.theme.bgColor })

    top = this.$class({
        "++": this.block.name,
        "--": LightTheme
    })

    bottom = this.$class({
        "++": this.block.name,
        "--": DarkTheme
    })
}

Concatenation via the "++" keyword yields a CSS class like "n153 n248"; the "--" extended property tells Mimcss to write all custom properties from the referenced theme at that point in the cascade.

Fit for Theming or Not?

Mimcss tackles theming with inheritance instead of context providers or Hooks. A theme declaration is a class interface; theme implementations are subclasses; and the generated names remain stable so HTML never has to change when a theme object swaps in.

The pattern outperforms what plain CSS offers, especially for framework-agnostic uses, and the semantic checkpoints via TypeScript distinguish it from libraries that merely shuffle unique suffixes. Those needing to see whether the model suits their project can dig into the full documentation or spin up examples in the playground.