How to Customize the Editor Theme and Dark Mode Using Shadcn UI in Reactjs-Tiptap-Editor

You can customize the reactjs-tiptap-editor theme and dark mode by mounting the ThemeColorReactive component and calling themeActions.setTheme(), setColor(), or setBorderRadius() to update CSS custom properties that mirror Shadcn UI's design tokens.

The reactjs-tiptap-editor library ships with a lightweight, Shadcn UI-compatible theming system that uses CSS custom properties for instant visual updates. All color palettes, border radii, and mode switches are managed through a reactive signal-based store that injects variables at runtime. This approach allows the editor to visually match any Shadcn UI component set without recompiling your application.

How Theming Works Under the Hood

The theming architecture consists of three core layers: a static palette definition, a reactive state store, and a runtime CSS injector.

The THEME Object and Signal Store

In src/theme/theme.ts, the library defines a THEME object containing light and dark HSL color palettes for multiple colorways (default, red, violet, etc.). A themeSignal store (using reactjs-signal) tracks the current theme, color, and borderRadius selections. The useTheme() hook provides read access to these values, while themeActions offers mutators: setTheme(), setColor(), and setBorderRadius().

Runtime CSS Injection

The ThemeColorReactive component (located in src/store/ThemeColorReactive.tsx) watches the store via useEffect. When values change, it builds a CSS string from THEME[theme][color] and injects it into the document head using updateCSS() from src/utils/dynamicCSS.ts. This creates a <style data-id="richtext-theme"> tag with priority 50, ensuring the variables override defaults without conflicts. All editor styles in src/styles/editor.scss reference these variables (e.g., background: hsl(var(--background))), enabling instant theme switches.

Setting Up Theme Reactivity

To enable theming in your application, import and render the ThemeColorReactive component at your root level. This component is already demonstrated in the library's playground at playground/src/App.tsx.

import { ThemeColorReactive } from 'reactjs-tiptap-editor/theme';
import { themeActions, useTheme } from 'reactjs-tiptap-editor/theme';

function App() {
  const { theme } = useTheme();

  return (
    <>
      {/* Injects CSS variables for the current theme */}
      <ThemeColorReactive />
      
      {/* Your editor components */}
      <button onClick={() => themeActions.setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Dark Mode
      </button>
    </>
  );
}

Mounting this component ensures that any subsequent calls to themeActions immediately update the editor's visual appearance.

Switching Between Light and Dark Modes

Toggle between light and dark modes using the setTheme() action. The library defaults to light mode with a 0.65rem border radius.

import { themeActions } from 'reactjs-tiptap-editor/theme';

// Switch to dark mode
themeActions.setTheme('dark');

// Revert to light mode
themeActions.setTheme('light');

When setTheme() updates the themeSignal store, ThemeColorReactive re-renders and injects the corresponding dark or light palette from the THEME object into the CSS variables.

Changing Color Palettes

Select from built-in Shadcn UI colorways using setColor(). Supported keys include default, red, blue, green, orange, rose, violet, and yellow, as defined in THEME.light and THEME.dark.

// Apply the violet color palette
themeActions.setColor('violet');

These palettes contain HSL values identical to those generated by the Shadcn UI theme generator, ensuring visual consistency with Shadcn UI components.

Adjusting Border Radius

Customize the global border radius independently of the color palette using setBorderRadius(). This updates the --radius CSS custom property, as implemented in ThemeColorReactive lines 28-30.

// Set a tighter radius
themeActions.setBorderRadius('0.4rem');

// Or a rounder appearance
themeActions.setBorderRadius('0.75rem');

The ThemeColorReactive component specifically handles the radius token by substituting the user-provided value into the generated CSS block, overriding the static theme definition.

Integrating Custom Shadcn UI Themes

To import a custom theme generated from ui.shadcn.com/themes, extend the THEME object in your application or modify src/theme/theme.ts directly.

import { THEME } from 'reactjs-tiptap-editor/theme';

// Merge a custom palette from Shadcn UI generator
THEME.light.myCustom = {
  radius: '0.5rem',
  background: '0 0% 100%',
  foreground: '240 10% 3.9%',
  primary: '240 5.9% 10%',
  // ... additional tokens
};

// Now select it programmatically
themeActions.setColor('myCustom');

Because the editor uses standard CSS custom properties (--background, --primary, --radius, etc.), any descendant components—including your custom UI—automatically inherit the selected theme.

Summary

  • Mount ThemeColorReactive at your application root to enable runtime CSS variable injection from src/store/ThemeColorReactive.tsx.
  • Use themeActions.setTheme() to toggle between light and dark modes via the signal-based store in src/theme/theme.ts.
  • Call themeActions.setColor() to switch between Shadcn UI-compatible color palettes (violet, red, orange, etc.).
  • Adjust themeActions.setBorderRadius() to modify the global --radius variable without touching CSS files.
  • Extend the THEME object to import custom palettes from the Shadcn UI theme generator, maintaining full compatibility with Shadcn UI design tokens.

Frequently Asked Questions

How do I persist the user's theme selection across page reloads?

The themeSignal store in src/theme/theme.ts manages state in memory only. To persist selections, subscribe to the useTheme() hook and save values to localStorage. On application mount, read these values and call themeActions.setTheme() and setColor() to restore the user's preferences before rendering the editor.

Can I use the editor's theme variables in my own components outside the editor?

Yes. Because ThemeColorReactive injects variables into the document root, any component can reference them using standard CSS custom properties: background: hsl(var(--primary)) or border-radius: var(--radius). Ensure your components are descendants of the React tree where ThemeColorReactive is mounted.

What happens if I don't include the ThemeColorReactive component?

Without ThemeColorReactive, the CSS custom properties (--background, --foreground, etc.) are never injected into the page. The editor will lack styling and appear unstyled or broken, as src/styles/editor.scss depends on these variables for all visual presentation.

Does the library support system-level dark mode preferences?

The library does not automatically detect prefers-color-scheme in the default implementation. You must manually call themeActions.setTheme('dark') when the system preference changes. Implement a useEffect hook that listens to window.matchMedia('(prefers-color-scheme: dark)') events and updates the store accordingly to achieve automatic switching.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →