Theming System
Relevant source files
The following files were used as context for generating this wiki page:The Theming System in the RPG Character Sheet Builder provides a robust framework for customizing the visual appearance of both the character sheet content and the application's user interface. It is divided into two distinct parts: Sheet Content Theming, which controls the look and feel of the character sheet itself (e.g., fonts, colors, and block styles for themes like "Medieval" or "Cyberpunk"), and Application UI Theming, which manages the application's overall light and dark modes.
This system is designed to be extensible, with themes defined as configuration objects. State is managed globally via Zustand, ensuring that style changes are propagated reactively throughout the application. Components subscribe to this state and apply the appropriate styles, primarily using Tailwind CSS utility classes defined in the theme configurations.
Architecture Overview
The theming system's architecture revolves around a central state store (useSheetStore) that holds the currently selected theme. The user interacts with UI components (ToolsSidebar) to change the theme, which triggers an action to update the store. Components responsible for rendering content (BlockRenderer) listen for changes in the store and re-render with the new styles defined in the theme configuration (themes.ts). A separate hook (useTheme) handles the application-wide light/dark mode toggle.
The following diagram illustrates the data flow for changing a sheet's content theme.
This diagram shows the flow from user interaction in the ToolsSidebar to the final rendering of a styled block by the BlockRenderer.
Sources: src/components/layout/ToolsSidebar.tsx:102-104, src/store/useSheetStore.ts:74-77, src/components/sheet/BlockRenderer.tsx:10-11, 28-33
Sheet Content Theming
This is the core of the visual customization for the character sheet canvas. It allows users to select from predefined themes that dictate fonts, colors, and styles for all content blocks.
Theme Definition
Themes are defined in a central object THEMES located in src/lib/themes.ts. Each theme is identified by a unique key (ThemeKey) and conforms to the ThemeConfig interface.
Sources: src/lib/themes.ts:1-4, 30
ThemeConfig Interface
The ThemeConfig interface specifies the structure for every theme object.
This diagram shows the data structure of a single theme configuration.
Sources: src/lib/themes.ts:4-28
The properties of ThemeConfig are detailed below:
| Property | Type | Description |
|---|---|---|
name | string | The display name of the theme (e.g., "Modern Clean"). |
fonts | object | Contains font-family strings for heading and body text. These fonts are imported in index.css. |
colors | object | Contains Tailwind CSS utility classes for text, accent, and faint colors. |
blocks | object | Contains Tailwind CSS utility classes for styling specific block types like header, formBox, etc. |
Sources: src/lib/themes.ts:4-28, src/index.css:2-4
Available Themes
The system comes with three predefined themes.
| Key | Name | Description |
|---|---|---|
simple | Modern Clean | A clean, modern theme using "Inter" font and blue accents. |
medieval | Ancient Scroll | A fantasy theme using "Cinzel" and "Crimson Text" fonts with brown and red colors, resembling a scroll. |
cyberpunk | Neon Future | A sci-fi theme using "Orbitron" and "Share Tech Mono" fonts with cyan and pink neon colors. |
Sources: src/lib/themes.ts:30-108
State Management
The currently active theme is managed globally by a Zustand store defined in useSheetStore.
- State: The
globalStyle.themeproperty holds the key of the active theme (e.g.,"simple"). It defaults to"simple". - Action: The
setTheme(theme: ThemeKey)action is used to update the theme. It takes aThemeKeyand updates theglobalStyleobject in the store.
Sources: src/store/useSheetStore.ts:60, src/store/useSheetStore.ts:74-77
Theme Selection UI
The ToolsSidebar component provides the user interface for selecting a theme.
- It imports the
THEMESobject and thesetThemeaction fromuseSheetStore. - It renders a button for each available theme by mapping over
Object.keys(THEMES). - The
onClickhandler for each button callssetTheme(themeKey), passing the key of the selected theme. - The currently active theme's button is styled differently to indicate it is selected.
Sources: src/components/layout/ToolsSidebar.tsx:13, 14, 99-117
Theme Application
The BlockRenderer component is responsible for applying the theme's styles to the individual blocks on the canvas.
The following sequence diagram shows how a block is styled when the theme changes.
This diagram illustrates the sequence of events from a user action to the visual update of a block.
Sources: src/components/layout/ToolsSidebar.tsx:102, src/store/useSheetStore.ts:74, src/components/sheet/BlockRenderer.tsx:10-11
Inside BlockRenderer, the process is as follows:
- It gets the current
globalStylefromuseSheetStore. - It retrieves the complete theme configuration object from
THEMESusing the current theme key (e.g.,THEMES[globalStyle.theme]). - For each block type (e.g.,
header,form-box), it selects the appropriate Tailwind CSS class strings from the theme object. - These classes are applied to the rendered elements using the
cnutility, along with theme-specific font families and colors.
Sources: src/components/sheet/BlockRenderer.tsx:10-11, 28-33, 50-58
// src/components/sheet/BlockRenderer.tsx
// ...
const { updateBlockData, removeBlock, globalStyle } = useSheetStore();
// Get the current theme configuration object
const currentTheme = THEMES[globalStyle.theme];
// ...
// --- 1. HEADERS (H1, H2, H3) ---
if (block.type === "header") {
const level = block.config?.level || 1;
// Select the class from the theme based on level
const themeClass = level === 1 ? currentTheme.blocks.header.h1 : currentTheme.blocks.header.h2;
return (
<div className="w-full h-full group relative flex items-end">
<DeleteBtn />
<input
className={cn(
"w-full bg-transparent border-none focus:outline-none nodrag",
themeClass, // Theme specific font-weight, borders, spacing
currentTheme.colors.text // Theme specific color
)}
style={{ fontFamily: currentTheme.fonts.heading }}
// ...
/>
</div>
);
}
This code snippet from BlockRenderer.tsx demonstrates how styles from the currentTheme object are dynamically applied to a header block.
Sources: src/components/sheet/BlockRenderer.tsx:10-11, 28-44
Application UI Theming (Light/Dark Mode)
Separate from the sheet content theming, the application also supports a global light/dark mode for its own UI (top bar, sidebars, etc.). This is managed by the useTheme custom hook.
useTheme Hook
The useTheme hook, defined in src/hooks/useTheme.ts, encapsulates the logic for managing the light/dark mode.
- Initialization: On load, it determines the initial theme by checking
localStoragefirst, then falling back to the user's system preference (prefers-color-scheme: dark). The default is'light'. - State: It uses a
useStatehook to manage the current theme ('light'or'dark'). - Effect: An
useEffecthook runs whenever the theme changes. It removes the old theme class from the<html>element, adds the new one (lightordark), and persists the new theme tolocalStorage. - Toggle Function: It exposes a
toggleThemefunction that switches the theme between'light'and'dark'.
Sources: src/hooks/useTheme.ts:3-26
UI Toggle and Implementation
- The
TopBarcomponent uses theuseThemehook to get the current theme and thetoggleThemefunction. - It renders a button with a Sun or Moon icon that calls
toggleThemewhen clicked, allowing the user to switch modes. - The actual styling is handled by CSS. The
index.cssfile defines CSS variables for both the root (:root) and.darkscopes. When thedarkclass is present on the<html>element, the dark mode variables are applied, changing the application's appearance.
Sources: src/components/layout/TopBar.tsx:16, 61-67, src/index.css:100-177