Custom CSS Overrides
Relevant source files
The following files were used as context for generating this wiki page:
Custom CSS Overrides allow you to restyle the hodor login and error pages. Hodor injects these styles directly into the HTML templates, enabling visual customization without requiring a full custom template. You can provide CSS via configuration files or environment variables.
This system supports two main modes: appending styles to the default dark theme or completely replacing the default styles. The CSS remains unescaped to ensure all valid CSS rules function correctly within the browser.
Sources: AGENTS.md:23-25, README.md:278-285
Configuration and State
Hodor manages CSS overrides through two primary configuration keys. The Config struct stores these values during initialization.
| Key | Environment Variable | Type | Default | Description |
|---|---|---|---|---|
custom_css | CUSTOM_CSS | String | Empty | Verbatim CSS injected into the page. |
disable_default_css | DISABLE_DEFAULT_CSS | Boolean | false | When true, hodor removes all built-in styles. |
Sources: src/config.rs:18-21, src/config.rs:56-57, README.md:104-106
Data Flow
During startup, the build_app_state function processes the configuration. It extracts the CSS string and validates that the templates can render with the provided overrides.
The diagram shows how CSS configuration moves from external inputs into the runtime AppState.
Sources: src/state.rs:43-88, src/config.rs:136-150
Template Injection Logic
Hodor uses the minijinja engine to render pages. The AppState passes the custom_css and disable_default_css variables into the template context.
Style Tag Placement
The built-in templates contain conditional blocks to handle the overrides:
- Default Styles: Wrapped in
{% if not disable_default_css %}. These are omitted if the user disables them. - Custom Styles: Wrapped in
{% if custom_css %}. Hodor uses the| safefilter to prevent escaping of CSS characters like brackets and semi-colons.
Sources: src/template.html:6-85, src/error_template.html:6-71, src/templates.rs:77-82
Rendering Sequence
When a request requires a login or error page, the system follows this logic:
The sequence diagram illustrates the decision logic applied during the rendering phase.
Sources: src/templates.rs:114-129, src/templates.rs:131-147
Technical Constraints and Validation
The CSS override system includes specific behaviors to ensure security and functionality.
- Verbatim Injection: Custom CSS is not escaped. This allows complex selectors and rules but requires the user to provide valid CSS.
- Startup Validation: Hodor calls
validate_templateandvalidate_error_templateduring startup. If the CSS causes a template engine error, the process panics and fails to start. - Precedence: Custom styles are injected after built-in styles in the HTML
<head>. This ensures user-defined rules take precedence over default theme rules due to standard CSS cascading.
Sources: src/state.rs:55-68, src/templates.rs:37-64, src/tests/templates.rs:60-64
Implementation Example
The AppState struct holds the processed CSS values used in every rendering call.
// src/state.rs:25-30
pub(crate) struct AppState {
pub(crate) custom_css: String,
pub(crate) disable_default_css: bool,
// ... other fields
}
The rendering function then applies these to the template:
// src/templates.rs:71-84
pub(crate) fn render_login_page(
template_source: &str,
title: &str,
custom_css: &str,
disable_default_css: bool,
show_error: bool,
) -> Result<String, minijinja::Error> {
let mut env = Environment::new();
env.add_template(TEMPLATE_NAME, template_source)?;
env.get_template(TEMPLATE_NAME)?.render(context!(
title => title,
custom_css => custom_css,
disable_default_css => disable_default_css,
show_error => show_error,
))
}
Sources: src/state.rs:25-30, src/templates.rs:71-84
Summary
Custom CSS Overrides provide a lightweight mechanism for branding hodor. By leveraging CUSTOM_CSS and DISABLE_DEFAULT_CSS, you can either tweak the existing dark theme or provide a completely new visual identity. The system ensures that overrides are validated at startup and correctly prioritized during the HTML rendering process.
Sources: README.md:278-295, AGENTS.md:23-25