Theming System
NOW LMS includes a flexible theming system that allows you to customize the appearance of your learning management system. This guide explains how to create, customize, and extend themes.
Overview
The theming system is built using Jinja2 templates and provides a modular approach to styling different aspects of the application. Each theme consists of several components that control different parts of the user interface and can override specific page templates.
Theme Structure
Each theme is located in the now_lms/templates/themes/
directory and follows this structure:
now_lms/templates/themes/your_theme_name/
├── base.j2 # Base template structure
├── header.j2 # HTML head tags and metadata
├── js.j2 # JavaScript libraries and scripts
├── local_style.j2 # Theme-specific CSS styles (uses url_for)
├── navbar.j2 # Navigation bar component
├── notify.j2 # Notification/alert components
├── pagination.j2 # Pagination controls
├── overrides/ # Template overrides directory
│ ├── home.j2 # Custom home page
│ ├── course_list.j2 # Custom course listing page
│ ├── course_view.j2 # Custom course view page
│ ├── program_list.j2 # Custom program listing page
│ └── program_view.j2 # Custom program view page
├── custom_pages/ # Static custom pages directory
│ ├── contacto.j2 # Example: contact page
│ └── info.j2 # Example: info page
└── README.md # Theme documentation
Static Assets
Each theme can include static assets in the static/themes/
directory:
now_lms/static/themes/your_theme_name/
├── theme.css # Main theme stylesheet
├── theme.min.css # Minified version for performance
├── images/ # Theme-specific images
├── fonts/ # Custom fonts
├── js/ # Theme-specific JavaScript
└── videos/ # Theme-specific videos
For more details on managing static assets, see the Static Assets README.
Available Themes
NOW LMS comes with several built-in themes:
-
now_lms: Default theme made with vanilla Boostrap5.
-
corporative: Professional blue theme for corporate environments.
- finance: Federal green theme inspired by financial institutions.
Colors variations of the base theme:
- classic: Minimalist white/gray design with clean typography.
- amber: A warm, autumn-inspired theme.
- lime: A green based theme.
- nebula: A purple based theme.
- ocean: A blue based theme.
- sakira::
Academic Themes
Three prestigious academic themes inspired by world-renowned universities:
- harvard: Burgundy theme (#A41034) inspired by Harvard University
- Classic academic design with traditional typography
- Merriweather/Playfair Display fonts for titles
- Source Sans Pro for body text
- Clean navbar without icons
-
Academic highlights and "Veritas" branding
-
cambridge: Green patina theme (#4A7B6D) inspired by Cambridge University
- Historic architecture and scholarly heritage design
- Cormorant Garamond/EB Garamond fonts for titles
- Open Sans for body text
- Manuscript-inspired design elements
-
Clean navbar without icons
-
oxford: Blue-gray theme (#5A5F68) inspired by Oxford University
- Aristocratic elegance and formal tradition
- Libre Baskerville fonts for titles
- Lato/Nunito for body text
- Sophisticated and refined components
- Clean navbar without icons
Creating a Custom Theme
Step 1: Create Theme Directory
Create a new directory in now_lms/templates/themes/
with your theme name:
mkdir now_lms/templates/themes/my_custom_theme
Step 2: Copy Base Files
Start by copying files from an existing theme as a template:
cp -r now_lms/templates/themes/now_lms/* now_lms/templates/themes/my_custom_theme/
Step 3: Customize Theme Components
Header Component (header.j2
)
Define HTML head tags, meta information, and external resources:
{% macro headertags() -%}
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="{{ site_config.descripcion }}" />
<meta name="author" content="NOW LMS" />
<!-- Bootstrap CSS -->
<link href="{{ url_for('static', filename='node_modules/bootstrap/dist/css/bootstrap.min.css') }}" rel="stylesheet" />
<!-- Custom Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<!-- Icons -->
<link rel="stylesheet" href="{{ url_for('static', filename='node_modules/bootstrap-icons/font/bootstrap-icons.css') }}" />
{%- endmacro %}
Local Styles (local_style.j2
)
Note: For optimal performance, create external CSS files instead of inline styles.
Create your theme CSS file in static/themes/your_theme_name/theme.css
:
/* Custom Theme Variables */
:root {
--primary-color: #yourcolor;
--secondary-color: #yourcolor;
--accent-color: #yourcolor;
--background-color: #yourcolor;
--text-color: #yourcolor;
--success-color: #yourcolor;
--warning-color: #yourcolor;
--danger-color: #yourcolor;
}
/* Your custom CSS styles */
body {
font-family: 'Inter', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
}
.navbar-custom {
background: var(--primary-color) !important;
}
/* Add more custom styles... */
Then create a minified version and update your local_style.j2
:
{% macro local_style() -%}
<link rel="stylesheet" href="{{ url_for('static', filename='themes/your_theme_name/theme.min.css') }}">
{% endmacro %}
Template Overrides
The theming system now supports complete page template overrides. You can customize specific pages by creating templates in the overrides/
directory.
Supported Override Templates
home.j2
: Custom home page templatecourse_list.j2
: Custom course listing pagecourse_view.j2
: Custom individual course pageprogram_list.j2
: Custom program listing pageprogram_view.j2
: Custom individual program page
Using the get_home_template()
Function
The system automatically uses the get_home_template()
function to determine which template to use:
def get_home_template() -> str:
"""Returns the path to the home page template."""
THEME = get_current_theme()
HOME = Path(path.join(get_theme_path(), "overrides", "home.j2"))
if HOME.exists():
return THEMES_DIRECTORY + str(THEME) + "/overrides/home.j2"
else:
return "inicio/home.html" # Default template
Template Override Functions
The system includes several template override functions:
get_home_template()
- Home page overrideget_course_list_template()
- Course listing overrideget_program_list_template()
- Program listing overrideget_course_view_template()
- Course view overrideget_program_view_template()
- Program view override
Custom Pages
Create static custom pages for your theme in the custom_pages/
directory. These pages can be accessed via /custom/<page_name>
.
Creating Custom Pages
- Create a template in
templates/themes/your_theme/custom_pages/
:
<!-- templates/themes/mytheme/custom_pages/contacto.j2 -->
{% set current_theme = current_theme() %}
<!doctype html>
<html lang="es">
<head>
{{ current_theme.headertags() }}
{{ current_theme.local_style() }}
<title>Contacto - {{ site_config.nombre }}</title>
</head>
<body>
{{ current_theme.navbar() }}
<div class="container py-5">
<h1>Contacto</h1>
<p>Esta es una página personalizada del tema.</p>
<!-- Add your custom content here -->
</div>
{{ current_theme.jslibs() }}
</body>
</html>
- Access the page at:
/custom/contacto
Custom Page Security
- Page names are validated to contain only alphanumeric characters, underscores, and hyphens
- Only authenticated themes can serve custom pages
- Pages are cached for 180 seconds for performance
Static Assets Management
Using url_for
for Theme Assets
Reference theme-specific static assets using Flask's url_for
:
<!-- Images -->
<img src="{{ url_for('static', filename='themes/mytheme/images/logo.png') }}" alt="Logo">
<!-- CSS (additional stylesheets) -->
<link rel="stylesheet" href="{{ url_for('static', filename='themes/mytheme/css/custom.css') }}">
<!-- JavaScript -->
<script src="{{ url_for('static', filename='themes/mytheme/js/theme.js') }}"></script>
<!-- Fonts -->
<link href="{{ url_for('static', filename='themes/mytheme/fonts/custom-font.woff2') }}" rel="preload" as="font" type="font/woff2" crossorigin>
<!-- Videos -->
<video src="{{ url_for('static', filename='themes/mytheme/videos/intro.mp4') }}" controls></video>
For complete examples and best practices, see the Static Assets README. color: var(--text-color); }
/* Navigation styles */
.navbar-custom {
background-color: var(--primary-color);
}
/* Button styles */
.btn-custom-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
{%- endmacro %} ```
Navigation Bar (navbar.j2
)
Customize the navigation structure and styling:
jinja2
{% macro navbar() -%}
<nav class="navbar navbar-expand-md navbar-custom" aria-label="Custom navbar">
<div class="container-fluid">
<!-- Logo and brand -->
<a href="{{ url_for('home.pagina_de_inicio') }}" class="navbar-brand">
{% if logo_perzonalizado() %}
<img height="30" src="{{ url_for('static', filename='/files/public/images/logotipo.jpg') }}" alt="LMS" />
{% else %}
<img height="30" src="{{ url_for('static', filename='/icons/logo/logo_white_large.png') }}" alt="NOW LMS" />
{% endif %}
</a>
<!-- Navigation links -->
<div class="collapse navbar-collapse">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a href="{{ url_for('home.pagina_de_inicio') }}" class="nav-link">Home</a>
</li>
<!-- Add more navigation items -->
</ul>
</div>
</div>
</nav>
{%- endmacro %}
Step 4: JavaScript Libraries (js.j2
)
Include JavaScript dependencies and custom scripts:
{% macro jslibs() -%}
<script src="{{ url_for('static', filename='node_modules/bootstrap/dist/js/bootstrap.bundle.min.js') }}"></script>
<!-- Custom JavaScript for your theme -->
<script>
// Your custom JavaScript code
document.addEventListener('DOMContentLoaded', function() {
// Theme-specific initialization
});
</script>
{%- endmacro %}
Step 5: Other Components
notify.j2
: Customize notification and alert stylingpagination.j2
: Define pagination controls appearancebase.j2
: Set up the overall page structure
Theme Integration
Using Theme Functions in Templates
All HTML templates should include the theme style call in the <head>
section:
<!doctype html>
<html lang="es">
{% set current_theme = current_theme() %}
<head>
{{ current_theme.headertags() }}
{{ current_theme.local_style() }}
<title>Page Title</title>
</head>
<body>
{{ current_theme.navbar() }}
<!-- Page content -->
{{ current_theme.jslibs() }}
</body>
</html>
Custom Home Pages
The theming system supports theme-specific home pages through the get_home_template()
function. This allows you to completely override the default home page with a custom design for your theme.
How it works:
def get_home_template() -> str:
"""Returns the path to the home page template."""
THEME = get_current_theme()
HOME = Path(path.join(get_theme_path(), "overrides", "home.j2"))
if HOME.exists():
return THEMES_DIRECTORY + str(THEME) + "/overrides/home.j2"
else:
return "inicio/home.html"
If your theme directory contains a home.j2
file, it will be used instead of the default home page. This gives you complete control over the home page design, layout, and content.
Creating a Custom Home Page:
- Create a
home.j2
file in your theme directory - Design your custom home page using your theme's styling
- Include all necessary theme functions and Bootstrap components
- The system will automatically use your custom home page when the theme is active
Example Custom Home Page Structure:
<!doctype html>
<html lang="es">
{% set current_theme = current_theme() %}
<head>
{{ current_theme.headertags() }}
{{ current_theme.local_style() }}
<title>{{ site_config.nombre }} - Custom Home</title>
</head>
<body>
{{ current_theme.navbar() }}
<!-- Your custom home page content -->
<section class="hero-section">
<!-- Custom hero content -->
</section>
<section class="features-section">
<!-- Custom features content -->
</section>
{{ current_theme.jslibs() }}
</body>
</html>
Static Content and Assets
Static Assets Directory
Theme-specific static assets should be placed in the static/themes/
directory. This allows you to include custom images, CSS files, JavaScript, fonts, and other media specific to your theme.
Directory Structure:
static/themes/
├── your_theme_name/
│ ├── images/
│ │ ├── logo.png
│ │ ├── background.jpg
│ │ └── hero-banner.png
│ ├── css/
│ │ └── additional.css
│ ├── js/
│ │ └── theme-animations.js
│ ├── fonts/
│ │ └── custom-font.woff2
│ └── videos/
│ └── intro.mp4
Using url_for
for Static Assets
Flask's url_for
function is used to generate URLs for static assets. This ensures proper URL generation regardless of the application's configuration.
Examples:
Images:
<!-- Theme logo -->
<img src="{{ url_for('static', filename='themes/harvard/images/logo.png') }}" alt="Harvard Logo">
<!-- Background image -->
<div style="background-image: url('{{ url_for('static', filename='themes/harvard/images/campus-bg.jpg') }}');">
<!-- Content -->
</div>
<!-- Hero banner -->
<img src="{{ url_for('static', filename='themes/cambridge/images/hero-banner.png') }}"
class="img-fluid" alt="Cambridge Banner">
CSS Files:
<!-- Additional theme-specific CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='themes/oxford/css/animations.css') }}">
<!-- Font file -->
<link rel="stylesheet" href="{{ url_for('static', filename='themes/oxford/css/fonts.css') }}">
JavaScript Files:
<!-- Theme-specific JavaScript -->
<script src="{{ url_for('static', filename='themes/harvard/js/interactive.js') }}"></script>
<!-- Theme initialization -->
<script src="{{ url_for('static', filename='themes/cambridge/js/cambridge-init.js') }}"></script>
Fonts:
/* In your theme's CSS */
@font-face {
font-family: 'CustomFont';
src: url('{{ url_for('static', filename='themes/oxford/fonts/oxford-font.woff2') }}') format('woff2');
}
Videos and Audio:
<!-- Video background -->
<video autoplay muted loop class="bg-video">
<source src="{{ url_for('static', filename='themes/harvard/videos/campus-tour.mp4') }}" type="video/mp4">
</video>
<!-- Audio element -->
<audio controls>
<source src="{{ url_for('static', filename='themes/cambridge/audio/bell-chime.mp3') }}" type="audio/mpeg">
</audio>
Best Practices for Static Assets:
- Organization: Keep assets organized in logical subdirectories
- Optimization: Compress images and minify CSS/JS files
- Responsive Images: Provide multiple sizes for different screens
- Accessibility: Include proper alt text and ARIA labels
- Performance: Use appropriate image formats (WebP, SVG when possible)
- Caching: Static assets are automatically cached by Flask
Asset Loading Examples:
Conditional Asset Loading:
{% if current_theme_name == 'harvard' %}
<link rel="stylesheet" href="{{ url_for('static', filename='themes/harvard/css/harvard-extras.css') }}">
{% elif current_theme_name == 'cambridge' %}
<script src="{{ url_for('static', filename='themes/cambridge/js/manuscript-effects.js') }}"></script>
{% endif %}
Dynamic Asset References:
<!-- Dynamic theme assets -->
<img src="{{ url_for('static', filename='themes/' + current_theme_name + '/images/logo.png') }}"
alt="{{ current_theme_name|title }} Logo">
Available Theme Functions
current_theme.headertags()
: HTML head tags and metadatacurrent_theme.local_style()
: Theme CSS stylescurrent_theme.navbar()
: Navigation barcurrent_theme.notify()
: Notification componentscurrent_theme.jslibs()
: JavaScript librariescurrent_theme.rendizar_paginacion()
: Pagination controls
Color Scheme Guidelines
CSS Variables
Use CSS custom properties (variables) for consistent theming:
:root {
/* Primary colors */
--primary: #your-primary-color;
--secondary: #your-secondary-color;
--accent: #your-accent-color;
/* Neutral colors */
--background: #your-background-color;
--surface: #your-surface-color;
--text: #your-text-color;
--text-secondary: #your-secondary-text-color;
/* State colors */
--success: #your-success-color;
--warning: #your-warning-color;
--danger: #your-danger-color;
--info: #your-info-color;
}
Bootstrap Integration
NOW LMS uses Bootstrap 5. You can override Bootstrap variables or use custom CSS classes:
/* Override Bootstrap variables */
.btn-primary {
background-color: var(--primary);
border-color: var(--primary);
}
/* Custom button class */
.btn-theme-custom {
background-color: var(--accent);
color: white;
border: none;
}
Cache Management
The theming system includes automatic cache invalidation. When you change the active theme:
- The system detects the theme change
- Automatically clears the cache if configured
- Ensures immediate theme updates for all users
Best Practices
1. Consistent Design Language
- Use a consistent color palette throughout your theme
- Maintain proper contrast ratios for accessibility
- Follow responsive design principles
2. Performance Optimization
- Minimize CSS and JavaScript file sizes
- Use efficient selectors
- Optimize images and assets
3. Accessibility
- Ensure sufficient color contrast (WCAG 2.1 AA standards)
- Include proper ARIA labels
- Test with screen readers
4. Testing
- Test your theme across different browsers
- Verify responsive behavior on various screen sizes
- Check all interactive elements
Troubleshooting
Theme Not Loading
- Verify theme directory structure
- Check file permissions
- Clear browser cache
- Restart the application if needed
Styling Issues
- Use browser developer tools to debug CSS
- Check for CSS conflicts with Bootstrap
- Verify CSS variable definitions
Cache Issues
- Clear application cache: The system automatically handles this
- Clear browser cache for development
- Check cache configuration in
now_lms/cache.py
Advanced Customization
Custom Components
You can extend themes by adding custom components and including them in your templates.
Theme Inheritance
Consider creating base themes and extending them for variations, though this requires more advanced template techniques.
Integration with Frontend Frameworks
For advanced theming, you can integrate with modern frontend build tools and frameworks while maintaining compatibility with the Jinja2 template system.
Support
For additional help with theming:
- Check the existing themes for examples
- Review the application documentation
- Contact the development team for complex customizations
This documentation covers the essential aspects of the NOW LMS theming system. For specific implementation details, refer to the existing theme files and application code.