# Theming How the visual theme system works in Laputa. ## Overview Laputa has two layers of theming: 1. **Global CSS variables** (`src/index.css`): App-wide colors, borders, backgrounds — used by all components via Tailwind and direct CSS. 2. **Editor theme** (`src/theme.json`): Editor-specific typography and styling — converted to CSS variables by `useEditorTheme` and applied to the BlockNote container. Currently the app is **light mode only** (dark mode was removed for simplicity). ## Layer 1: Global CSS Variables Defined in `src/index.css` under `:root`: ### Color Palette ```css /* Primary brand */ --primary: #155DFF; /* Blue — links, accents, active states */ --primary-foreground: #FFFFFF; /* Text hierarchy */ --text-primary: #37352F; /* Main text (Notion-like dark gray) */ --text-secondary: #787774; /* Secondary text */ --text-muted: #B4B4B4; /* Muted/placeholder text */ --text-heading: #37352F; /* Headings */ /* Backgrounds */ --bg-primary: #FFFFFF; /* Main content area */ --bg-sidebar: #F7F6F3; /* Sidebar background */ --bg-hover: #EBEBEA; /* Hover state */ --bg-hover-subtle: #F0F0EF; /* Subtle hover (code blocks) */ --bg-selected: #E8F4FE; /* Selected state (blue tint) */ /* Borders */ --border-primary: #E9E9E7; /* Standard borders */ /* Accent colors */ --accent-blue: #155DFF; --accent-green: #00B38B; --accent-orange: #D9730D; --accent-red: #E03E3E; --accent-purple: #A932FF; --accent-yellow: #F0B100; /* Light accent backgrounds (for badges/pills) */ --accent-blue-light: #155DFF14; --accent-green-light: #00B38B14; --accent-purple-light: #A932FF14; --accent-red-light: #E03E3E14; --accent-yellow-light: #F0B10014; ``` ### shadcn/ui Variables The app uses [shadcn/ui](https://ui.shadcn.com/) components, which require their own CSS variable naming convention: ```css --background: #FFFFFF; --foreground: #37352F; --card: #FFFFFF; --card-foreground: #37352F; --primary: #155DFF; --secondary: #EBEBEA; --muted: #F0F0EF; --muted-foreground: #787774; --accent: #EBEBEA; --destructive: #E03E3E; --border: #E9E9E7; --ring: #155DFF; --sidebar: #F7F6F3; ``` ### Tailwind v4 Integration The `@theme inline` block in `index.css` bridges CSS variables to Tailwind's color system: ```css @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-primary: var(--primary); /* ... etc */ } ``` This enables Tailwind classes like `bg-background`, `text-primary`, `border-border`, etc. ## Layer 2: Editor Theme (theme.json) `src/theme.json` controls the BlockNote editor's typography and element styling. It's a nested JSON object with the following structure: ### Structure ```json { "editor": { "fontFamily": "'Inter', -apple-system, BlinkMacSystemFont, sans-serif", "fontSize": 16, "lineHeight": 1.5, "maxWidth": 720, "paddingHorizontal": 40, "paddingVertical": 20, "paragraphSpacing": 8 }, "headings": { "h1": { "fontSize": 32, "fontWeight": 700, "lineHeight": 1.2, "marginTop": 32, "marginBottom": 12, "color": "var(--text-heading)", "letterSpacing": -0.5 }, "h2": { "fontSize": 27, "fontWeight": 600, "lineHeight": 1.4, "marginTop": 28, "marginBottom": 10 }, "h3": { "fontSize": 20, "fontWeight": 600, "lineHeight": 1.4, "marginTop": 24, "marginBottom": 8 }, "h4": { "fontSize": 20, "fontWeight": 600, "lineHeight": 1.4, "marginTop": 20, "marginBottom": 6 } }, "lists": { "bulletSymbol": "\u2022", "bulletSize": 28, "bulletColor": "#177bfd", "indentSize": 24, "itemSpacing": 4, "paddingLeft": 8, "nestedBulletSymbols": ["\u2022", "\u25e6", "\u25aa"], "bulletGap": 6 }, "checkboxes": { "size": 18, "borderRadius": 3, "checkedColor": "var(--accent-blue)", "uncheckedBorderColor": "var(--text-muted)", "gap": 8 }, "inlineStyles": { "bold": { "fontWeight": 700, "color": "var(--text-primary)" }, "italic": { "fontStyle": "italic" }, "strikethrough": { "color": "var(--text-tertiary)", "textDecoration": "line-through" }, "code": { "fontFamily": "'SF Mono', 'Fira Code', monospace", "fontSize": 14, "backgroundColor": "var(--bg-hover-subtle)", "paddingHorizontal": 4, "paddingVertical": 2, "borderRadius": 3 }, "link": { "color": "var(--accent-blue)", "textDecoration": "underline" }, "wikilink": { "color": "var(--accent-blue)", "textDecoration": "none", "borderBottom": "1px dotted var(--accent-blue)", "cursor": "pointer" } }, "codeBlocks": { "fontFamily": "'SF Mono', 'Fira Code', monospace", "fontSize": 13, "lineHeight": 1.5, "backgroundColor": "var(--bg-card)", "paddingHorizontal": 16, "paddingVertical": 12, "borderRadius": 6, "marginVertical": 12 }, "blockquote": { "borderLeftWidth": 3, "borderLeftColor": "var(--accent-blue)", "paddingLeft": 16, "marginVertical": 12, "color": "var(--text-secondary)", "fontStyle": "italic" }, "table": { "borderColor": "var(--border-primary)", "headerBackground": "var(--bg-card)", "cellPaddingHorizontal": 12, "cellPaddingVertical": 8, "fontSize": 14 }, "horizontalRule": { "color": "var(--border-primary)", "marginVertical": 24, "thickness": 1 }, "colors": { "background": "var(--bg-primary)", "text": "var(--text-primary)", "textSecondary": "var(--text-secondary)", "textMuted": "var(--text-muted)", "heading": "var(--text-heading)", "accent": "var(--accent-blue)", "selection": "var(--bg-selected)", "cursor": "var(--text-primary)" } } ``` ### How theme.json Becomes CSS The `useEditorTheme` hook (`src/hooks/useTheme.ts`) flattens the nested structure into CSS custom properties: ```typescript function flattenTheme(obj, prefix = '--') { // Recursively flattens: // { editor: { fontSize: 16 } } → { '--editor-font-size': '16px' } // { headings: { h1: { fontSize: 32 } } } → { '--headings-h1-font-size': '32px' } } ``` Key transformations: - **camelCase → kebab-case**: `fontSize` → `font-size` - **Numeric values get `px`**: `16` → `16px` - **Unitless exceptions**: `lineHeight`, `fontWeight`, `opacity` stay as plain numbers - **String values pass through**: `"var(--text-heading)"` → `var(--text-heading)` - **Arrays are skipped**: `nestedBulletSymbols` is ignored in CSS flattening The resulting flat map is applied as inline styles on the BlockNote container: ```tsx