# How Lepton Manages Theme Switching Between Light and Dark Modes

> Discover how Lepton manages theme switching for light and dark modes. Lepton applies CSS custom properties from JSON definitions, persisting user preferences in its global store.

- Repository: [CosmoX/Lepton](https://github.com/hackjutsu/lepton)
- Tags: internals
- Published: 2026-02-23

---

**Lepton manages theme switching through a centralized ThemeManager class that reads JSON theme definitions and applies them as CSS custom properties to the `:root` element, with the user's preference persisted in a global configuration store.**

Lepton, the open-source GitHub gist desktop client, implements a robust theme switching system that allows seamless transitions between light and dark visual modes. Understanding how Lepton handles theme switching reveals an elegant architecture combining JSON configuration files, CSS custom properties, and a singleton manager pattern. This article breaks down the complete implementation based on the latest source code from the `hackjutsu/Lepton` repository.

## Understanding Lepton's Theme Architecture

The theme system rests on three foundational pillars: static JSON definitions, a runtime manager, and persistent configuration storage.

In `app/utilities/themeManager/themes/`, two files—[`lightTheme.json`](https://github.com/hackjutsu/Lepton/blob/main/lightTheme.json) and [`darkTheme.json`](https://github.com/hackjutsu/Lepton/blob/main/darkTheme.json)—define the CSS custom property values for each palette. These properties include keys like `bg-primary` and `text-primary` that map to specific color values.

The **ThemeManager** class, located in [`app/utilities/themeManager/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/themeManager/index.js), serves as the runtime engine. It reads the selected theme definition and injects those values into the document's root element as CSS variables.

Finally, the system relies on `nconf` to persist user preferences. When the application initializes in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js), the configuration from [`configs/defaultConfig.js`](https://github.com/hackjutsu/Lepton/blob/main/configs/defaultConfig.js) loads globally as [`global.conf`](https://github.com/hackjutsu/Lepton/blob/main/global.conf), making the stored theme value accessible throughout the app via `conf.get('theme')`.

## Theme Definition Files (JSON)

The static theme definitions reside in JSON files within the themes directory. Each file contains a flat mapping of CSS variable names to color hex codes.

For example, [`lightTheme.json`](https://github.com/hackjutsu/Lepton/blob/main/lightTheme.json) and [`darkTheme.json`](https://github.com/hackjutsu/Lepton/blob/main/darkTheme.json) both define the same set of keys—such as `bg-primary`, `text-primary`, and border colors—but assign different values appropriate to each mode. This ensures visual consistency across the application while allowing complete palette swapping.

## The ThemeManager Implementation

The core logic lives in [`app/utilities/themeManager/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/themeManager/index.js). This singleton class exposes two primary methods: `setTheme(themeName)` and `toggleTheme()`.

When `setTheme()` receives a theme name (either `"light"` or `"dark"`), it forwards the call to `generateScheme()`. This private method loads the corresponding JSON file and iterates over each key-value pair, calling `document.documentElement.style.setProperty('--key', value)` for every entry. By mutating the CSS variable map on the `:root` element, the manager triggers an instantaneous global style update without requiring component re-renders.

The `toggleTheme()` method alternates the internal `currentTheme` state between `"light"` and `"dark"` before invoking `generateScheme()`. While this API exists for programmatic use, note that it is not currently wired to a UI element in the main interface.

## Configuration and Initialization Flow

The theme application follows a specific initialization sequence when Lepton starts:

1. **Configuration Loading**: In [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js), `nconf` loads [`configs/defaultConfig.js`](https://github.com/hackjutsu/Lepton/blob/main/configs/defaultConfig.js) and exposes it globally as [`global.conf`](https://github.com/hackjutsu/Lepton/blob/main/global.conf).
2. **Manager Instantiation**: In [`app/containers/appContainer/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/appContainer/index.js), the application creates a singleton instance: `const themeManager = new ThemeManager()`.
3. **Theme Application**: Immediately following instantiation, the container calls `themeManager.setTheme(conf.get('theme'))`, retrieving the persisted user preference and applying it before the UI renders.

This flow ensures that the correct theme appears immediately upon application launch, preventing flash-of-unstyled-content or visual flickering between modes.

## Consuming Themes in Components

Components reference the theme variables through standard CSS `var()` functions. In [`app/containers/snippet/index.scss`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/snippet/index.scss), for instance, styles declare:

```scss
.snippet-card {
  background-color: var(--bg-primary);
  color: var(--text-primary);
}

```

Because the ThemeManager updates the `:root` element directly, all components using these variables reflect changes instantly without JavaScript intervention.

Certain components require asset adaptation beyond CSS variables. The About page ([`app/containers/aboutPage/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/aboutPage/index.js)) and code editor components (`gistEditor` and `codeArea`) read `conf.get('theme')` at render time to select appropriate image assets or CodeMirror highlight themes (such as switching between `one-dark` and `github` highlighting).

## Practical Implementation Examples

To manually apply a theme in a component or utility:

```javascript
import ThemeManager from '../../utilities/themeManager'

const manager = new ThemeManager()
// Apply dark theme explicitly
manager.setTheme('dark')

```

To toggle themes programmatically (useful for keyboard shortcuts or menu items):

```javascript
// Assuming manager was instantiated earlier
function onThemeShortcut () {
  manager.toggleTheme()
}

```

For dynamic asset selection based on the current configuration:

```javascript
const highlightTheme = conf.get('theme') === 'dark' ? 'one-dark' : 'github'

```

## Summary

- **Lepton theme switching** relies on JSON definition files ([`lightTheme.json`](https://github.com/hackjutsu/Lepton/blob/main/lightTheme.json) and [`darkTheme.json`](https://github.com/hackjutsu/Lepton/blob/main/darkTheme.json)) that map CSS custom property names to color values.
- The **ThemeManager** class in [`app/utilities/themeManager/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/themeManager/index.js) applies themes by setting variables on the `:root` element via `document.documentElement.style.setProperty()`.
- User preferences persist in [`configs/defaultConfig.js`](https://github.com/hackjutsu/Lepton/blob/main/configs/defaultConfig.js) and load globally through `nconf` in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js).
- The **AppContainer** initializes the manager and applies the stored theme during application startup.
- UI components consume themes through CSS `var()` functions, enabling instant updates without re-renders.
- Certain components dynamically select assets by reading `conf.get('theme')` directly.

## Frequently Asked Questions

### Where does Lepton store theme definitions?

Lepton stores theme definitions in two JSON files located at [`app/utilities/themeManager/themes/lightTheme.json`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/themeManager/themes/lightTheme.json) and [`darkTheme.json`](https://github.com/hackjutsu/Lepton/blob/main/darkTheme.json). These files contain key-value pairs mapping CSS variable names (like `bg-primary`) to hex color codes. The ThemeManager class reads these files at runtime to populate the CSS custom properties on the document root.

### How does Lepton apply the selected theme without reloading the application?

The ThemeManager uses CSS custom properties (variables) injected into the `:root` element. When `setTheme()` is called, it iterates over the selected JSON file and calls `document.documentElement.style.setProperty('--key', value)` for each entry. Because components reference these variables via `var(--key)` in their SCSS files, the UI updates instantly without requiring a page reload or component remount.

### Can I programmatically toggle between light and dark modes in Lepton?

Yes. The ThemeManager class exposes a `toggleTheme()` method in [`app/utilities/themeManager/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/themeManager/index.js) that switches the internal state between `"light"` and `"dark"` and regenerates the color scheme. While this method is not currently connected to a UI button in the main interface, the API is available for custom keyboard shortcuts or menu implementations.

### How does Lepton remember the user's theme preference across sessions?

The application uses `nconf` to persist configuration data. When a user selects a theme, the value is stored in the global configuration object (defined in [`configs/defaultConfig.js`](https://github.com/hackjutsu/Lepton/blob/main/configs/defaultConfig.js) and loaded in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js)). On application startup, [`app/containers/appContainer/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/appContainer/index.js) reads this value using `conf.get('theme')` and passes it to the ThemeManager before the UI renders.