Theming & Styling
Relevant source files
The following files were used as context for generating this wiki page:The Theming and Styling system in Charlotte is built upon a foundation of CSS variables, Tailwind CSS, and a configurable, dynamic theme engine. It supports light/dark/auto modes, a user-configurable primary color via HSL hue, and specific styling for various components like Markdown content and code blocks. The system is designed to be both configurable through a central file and persistent for users via localStorage.
The core of the styling is managed through CSS files that utilize Tailwind's @apply directive, while dynamic theming logic is handled by Svelte components and TypeScript utility functions. This separation allows for a clear distinction between static styles and dynamic, user-driven theme changes.
Core Styling Architecture
The project uses a combination of global CSS files and Tailwind CSS for its styling foundation. The main entry point for styles is src/styles/main.css, which is imported by other CSS files to ensure correct build order and avoid issues with Tailwind's directives.
Key CSS files include:
src/styles/markdown.css: Provides styles for rendered Markdown content within the.custom-mdclass.src/styles/transition.css: Defines animations for page transitions (handled by Swup) and component load-in effects.src/styles/scrollbar.css: Customizes the appearance of scrollbars.
The system heavily relies on CSS variables for theming, most notably --hue, which is dynamically updated to change the site's primary color.
Sources: src/styles/markdown.css:1, src/styles/transition.css
Theme Configuration
The primary configuration for the site's theme is located in src/config.ts. This file exports a siteConfig object that contains several properties for controlling the default appearance.
Sources: src/config.ts:10-25
siteConfig Theming Properties
| Property | Type | Description |
|---|---|---|
themeColor.hue | number | The default theme color hue, ranging from 0-360. This controls the primary color of the site. |
themeColor.fixed | boolean | If true, hides the theme color selector from visitors, locking the hue. |
defaultTheme | 'light' | 'dark' | 'auto' | Sets the default theme mode for new visitors. |
banner.text.color | object | Defines specific colors for the banner's title and subtitle in both light and dark modes. Can be set to 'default' for white text. |
expressiveCodeConfig.theme | string | Specifies the theme used for code blocks rendered by Expressive Code. The default is github-dark. |
Sources: src/config.ts:18-25, src/config.ts:37-44, src/config.ts:106-110, src/types/config.ts:12-15, src/types/config.ts:16
Light/Dark Mode
The application supports light, dark, and system-default (auto) theme modes. The logic is managed by the LightDarkSwitch.svelte component and helper functions in src/utils/setting-utils.ts.
Implementation Details
- Storage: The user's selected theme (
light,dark, orauto) is stored inlocalStorageunder the keytheme. - Default: If no theme is in
localStorage, the value fromsiteConfig.defaultThemeis used. - Switching: The
LightDarkSwitch.sveltecomponent provides a UI to cycle through the three modes. Clicking the button callssetTheme()to updatelocalStorageand apply the new theme. - Application: The
applyThemeToDocument()function is responsible for toggling thedarkclass on the<html>element. Inautomode, it useswindow.matchMedia('(prefers-color-scheme: dark)')to determine the correct state.
Sources: src/components/LightDarkSwitch.svelte, src/utils/setting-utils.ts:40-60
Theme Application Flow
The following diagram illustrates how the theme is determined and applied when a page loads.
Dynamic Hue Control
A key feature of the theming system is the ability for users to change the primary color of the site. This is achieved by dynamically updating a CSS variable --hue.
- Storage: The selected hue value is stored in
localStorageunder the keyhue. - Default: The default hue is read from the
data-hueattribute of an element with the IDconfig-carrier, which is populated fromsiteConfig.themeColor.hue. - Application: The
setHue()function updates the--hueCSS variable on the:rootelement, which causes a site-wide color change for all elements that use this variable.
This is an example of how the hue is used in CSS:
.group-hover:text-[var(--primary)]
The --primary variable is likely defined using oklch or hsl with var(--hue).
Sources: src/utils/setting-utils.ts:11-26, src/config.ts:18-21, src/components/ArchivePanel.svelte:106
Component Styling
Markdown Content (.custom-md)
Rendered Markdown content is styled via the .custom-md class defined in src/styles/markdown.css. This file uses Tailwind's @apply directive to create consistent styling for standard HTML elements generated from Markdown.
- Headings (
h1-h6): Have a hover effect that reveals an anchor link icon. - Links (
a): Styled with an underline and a background change on hover. - Inline Code (
code): Has a distinct background color (--inline-code-bg) and uses the 'JetBrains Mono Variable' font. - Blockquotes (
blockquote): Feature a vertical bar on the left, created with a::beforepseudo-element.
Sources: src/styles/markdown.css:6-34, src/styles/markdown.css:36-47, src/styles/markdown.css:73-83
Code Blocks (Expressive Code)
Code blocks are rendered using the Astro Expressive Code integration, with a custom toolbar plugin.
Code Toolbar
A custom plugin at src/plugins/expressive-code/code-toolbar.ts adds a toolbar to the top of each code block. This toolbar includes:
- A language badge.
- The code block title (if specified in Markdown meta, e.g.,
```js title="My Code"). - A copy-to-clipboard button with success state feedback.
The toolbar's appearance is defined in the baseStyles of the plugin and uses CSS variables for theming, such as --codeblock-topbar-bg and --hue.
The following diagram shows the structure of the toolbar created by the plugin.
Page Transitions
The site uses Swup.js for page transitions, with animations defined in src/styles/transition.css.
- When a page change is initiated, the
htmltag gets anis-changingclass, and the outgoing content (.transition-swup-fade) fades out. - During the animation, the
htmltag has anis-animatingclass, which appliesopacity: 0and atranslate-ytransform. - New content fades in using a
fade-in-upkeyframe animation, with staggered delays for different page elements (#navbar,#sidebar,#content-wrapper) to create a smooth, sequential appearance.
Sources: src/styles/transition.css:2-7, src/styles/transition.css:18-35