Architecture Overview
Relevant source files
The following files were used as context for generating this wiki page:The Character Sheet Maker is a "Local-First" web application designed for creating and customizing RPG character sheets. It is built using React, TypeScript, and Vite. The architecture is centered around a component-based structure, a centralized state management system, and a dynamic grid-based layout for the user interface. This allows for a high degree of customization and flexibility, as it is not tied to any specific RPG system.
The application's state, including the layout and content of all character sheet elements, is managed globally and persisted in the user's browser via localStorage. This ensures that all work is saved automatically without requiring a backend server. The UI is composed of distinct, reusable components for layout, tools, and rendering, which interact with the central state to provide a reactive user experience.
Core Technologies
The project leverages a modern web development stack to provide a fast and interactive experience. The key dependencies are defined in package.json.
| Library | Version | Role |
|---|---|---|
react | 19.2.0 | Core UI library for building components. |
vite | 7.2.5 | Build tool providing a fast development server and optimized builds. |
typescript | ~5.9.3 | Provides static typing for the entire codebase. |
zustand | 5.0.8 | A small, fast, and scalable state management solution. |
react-grid-layout | 1.5.2 | Powers the draggable and resizable grid system for the canvas. |
tailwindcss | 4.1.17 | A utility-first CSS framework for styling. |
html-to-image | 1.11.13 | Used for converting the sheet's HTML to an image for export. |
jspdf | 3.0.4 | Used for creating PDF files from the generated image. |
Sources: package.json:10-23
The project configuration in vite.config.ts sets up the React plugin and Tailwind CSS integration. It also defines a path alias @ to point to the src directory for cleaner import paths.
Sources: vite.config.ts:6-12, tsconfig.json:10-13
Application Entrypoint and Routing
The application bootstraps from index.html, which loads the main entrypoint script src/main.tsx. This file renders the root App component into the DOM.
The diagram below illustrates the initial loading sequence of the application.
Sources: index.html:10, src/main.tsx:7-12, src/App.tsx:18-24
The App component sets up the application's routing using react-router-dom. It defines two main routes:
/: Renders theHomecomponent, which serves as a simple landing page./editor/:sheetId: Renders the mainEditorcomponent where users build their character sheets.
Sources: src/App.tsx:18-24
UI Structure and Core Components
The main user interface is within the Editor page, which is composed of three primary layout components: TopBar, ToolsSidebar, and Canvas.
This diagram shows the component hierarchy of the editor interface.
Sources: src/pages/Editor.tsx:8-24
TopBar: Contains global actions like exporting the sheet (as PNG or PDF) and toggling the dark/light theme.ToolsSidebar: Provides the tools for adding new elements (Blocks) to the canvas, such as titles, text boxes, and shapes. It also allows the user to change the visual theme of the sheet.Canvas: The main work area wherereact-grid-layoutis used to display and manage the character sheet elements.
State Management with Zustand
The application's state is managed by a single Zustand store defined in src/store/useSheetStore.ts. This store is the single source of truth for the character sheet's content, layout, and styling.
A key feature is the use of the persist middleware, which automatically saves the entire store state to localStorage under the key rpg-sheet-storage. This makes the application "Local-First".
Sources: src/store/useSheetStore.ts:68-71
State Structure
The state is composed of blocks, globalStyle, and other UI-related flags. The Block interface is the fundamental data structure for any element on the sheet.
This diagram shows the main interfaces and their relationships within the Zustand store.
Sources: src/store/useSheetStore.ts:9-48
Key State Actions
The store exposes several actions to manipulate the state. These actions are called from various UI components to provide interactivity.
| Action | Description |
|---|---|
addBlock | Adds a new block of a specified type to the blocks array. |
removeBlock | Removes a block from the blocks array by its ID. |
updateLayout | Synchronizes the x, y, w, h properties of blocks with the layout from react-grid-layout. |
updateBlockData | Updates the data payload of a specific block (e.g., its text content). |
setTheme | Changes the active theme in globalStyle. |
toggleCompact | Toggles the compactType property for the grid layout, enabling or disabling "gravity". |
Sources: src/store/useSheetStore.ts:40-47
The Canvas and Grid System
The <Canvas> component is responsible for rendering the grid and its items. It uses react-grid-layout's <ResponsiveGridLayout> to create a responsive, draggable, and resizable container.
The layout prop of the grid is derived from the blocks array in the Zustand store. When the user moves or resizes an item in the grid, react-grid-layout triggers the onLayoutChange callback, which in turn calls the updateLayout action in the store to persist the new positions.
Sources: src/components/sheet/Canvas.tsx:16, src/components/sheet/Canvas.tsx:44-45
The grid's behavior is controlled by the isCompact state from the store:
- If
isCompactistrue,compactTypeis set to"vertical", and items will stack vertically, pushing others down.preventCollisionisfalse. - If
isCompactisfalse,compactTypeisnull, and items can be freely placed without affecting others.preventCollisionistrue.
Sources: src/components/sheet/Canvas.tsx:41, src/components/sheet/Canvas.tsx:46
Each item in the blocks array is rendered inside a <div> wrapper, which is then passed to the <BlockRenderer> component for rendering the actual content.
Sources: src/components/sheet/Canvas.tsx:54
Block Rendering and Theming
The <BlockRenderer> component is a crucial part of the UI. It acts as a factory that renders different HTML structures based on the type property of the Block object it receives.
It contains conditional logic to render various block types:
header: Renders an<input>styled as a heading.form-box: Renders a label and a<textarea>.shape: Renders a decorative<div>(e.g., square, circle).divider: Renders a horizontal line.container: Renders a background container for grouping other blocks.list: Renders an editable unordered list.image: Renders an image uploader or the uploaded image.text: Renders a simple<textarea>.
Sources: src/components/sheet/BlockRenderer.tsx:28, 51, 88, 117, 127, 142, 175, 216
The styling for each block is not hardcoded. Instead, it is dynamically applied based on the currently selected theme. The BlockRenderer retrieves the current theme configuration from the THEMES object (defined in src/lib/themes.ts) using the globalStyle.theme key from the Zustand store. It then applies the appropriate Tailwind CSS classes from the theme configuration.
The flow of data for theming is as follows:
Export Functionality
The application allows users to export their character sheet as a PNG image or a PDF document. This functionality is handled by utility functions in src/lib/exportUtils.ts and triggered from the TopBar component.
This sequence diagram illustrates the process for both PNG and PDF export.
Sources: src/components/layout/TopBar.tsx:51-55, src/lib/exportUtils.ts:4-20, src/lib/exportUtils.ts:22-42
Both functions target the DOM element with the ID rpg-sheet-canvas, convert it to a PNG data URL using html-to-image, and then trigger a download. For PDF export, the generated image is placed into a new A4-sized PDF document using jspdf.
Conclusion
The architecture of the Character Sheet Maker is robust, scalable, and developer-friendly. By centralizing state in a persistent Zustand store, it creates a seamless local-first user experience. The clear separation of concerns between state management (useSheetStore), layout (Canvas), rendering (BlockRenderer), and theming (themes.ts) results in a modular and maintainable codebase. The use of react-grid-layout provides the core dynamic and customizable user interaction, which is the central feature of the application.