Theme Creation User Guide
Overview
This guide covers practical theme development in MintyFlask. It assumes you've read the Quick Start and have created a basic theme.
What you'll learn:
- Advanced template customisation patterns
- Design token strategy and management
- Multi-mixin integration
- Theme variants and inheritance
- Performance optimisation
- Production deployment
Theme Architecture
A MintyFlask theme consists of three layers working together:
Layer 1: Foundation (From _core)
- Base templates (
base.html,single.html,list.html) - Foundation CSS (typography, tokens, core components)
- Core JavaScript (theme switcher, utilities)
Layer 2: Mixins (From mixins/*)
- Feature layouts (
post-list.html,blog-home.html) - Component CSS (
.blog-card,.portfolio-grid) - Feature macros and partials
Layer 3: Theme (Your customisation)
- Page templates (using mixin layouts)
- Theme CSS (token overrides, component customisation)
- Theme-specific components and layouts
Design Token Strategy
Token Categories
Organise your tokens into logical categories:
/* themes/my-blog/static/css/foundations/tokens.css */
@layer theme {
:root {
/* === Colour Palette === */
/* Brand colours */
--color-brand-primary: oklch(60% 0.15 250);
--color-brand-secondary: oklch(65% 0.12 180);
--color-brand-accent: oklch(70% 0.18 45);
/* Semantic colours */
--color-success: oklch(65% 0.15 145);
--color-warning: oklch(70% 0.15 85);
--color-error: oklch(60% 0.15 25);
--color-info: oklch(65% 0.12 240);
/* Surface colours */
--color-surface: oklch(98% 0 0);
--color-surface-elevated: oklch(99% 0 0);
--color-surface-sunken: oklch(96% 0 0);
/* Text colours */
--color-text-primary: oklch(20% 0 0);
--color-text-secondary: oklch(40% 0 0);
--color-text-tertiary: oklch(60% 0 0);
--color-text-inverted: oklch(98% 0 0);
/* Border colours */
--color-border: oklch(85% 0 0);
--color-border-strong: oklch(70% 0 0);
/* === Spacing Scale === */
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 2rem; /* 32px */
--spacing-2xl: 3rem; /* 48px */
--spacing-3xl: 4rem; /* 64px */
/* === Typography Scale === */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
/* Font weights */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Line heights */
--line-height-tight: 1.2;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* === Border Radius === */
--radius-xs: 0.125rem; /* 2px */
--radius-sm: 0.25rem; /* 4px */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
--radius-2xl: 1rem; /* 16px */
--radius-full: 9999px; /* Pill shape */
/* === Shadows === */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* === Transitions === */
--transition-fast: 150ms ease;
--transition-base: 200ms ease;
--transition-slow: 300ms ease;
}
}
Dark Mode Tokens
@layer theme {
.dark {
/* Brand colours (slightly adjusted for dark mode) */
--color-brand-primary: oklch(65% 0.15 250);
--color-brand-secondary: oklch(70% 0.12 180);
/* Surface colours */
--color-surface: oklch(20% 0 0);
--color-surface-elevated: oklch(25% 0 0);
--color-surface-sunken: oklch(15% 0 0);
/* Text colours */
--color-text-primary: oklch(95% 0 0);
--color-text-secondary: oklch(75% 0 0);
--color-text-tertiary: oklch(60% 0 0);
/* Border colours */
--color-border: oklch(30% 0 0);
--color-border-strong: oklch(40% 0 0);
/* Shadows (lighter for dark mode) */
--shadow-sm: 0 1px 2px 0 rgba(255, 255, 255, 0.05);
--shadow-md: 0 4px 6px -1px rgba(255, 255, 255, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(255, 255, 255, 0.1);
}
}
Advanced Template Patterns
Pattern 1: Layout Extension with Configuration
{# themes/agency/templates/pages/services.html #}
{#
# Services page using post-list layout
# Demonstrates extensive configuration
#}
{% extends "layouts/post-list.html" %}
{# Page configuration #}
{% set page_title = "Our Services" %}
{% set page_description = "Professional web development services" %}
{% set page_subtitle %}
<p class="text-lg text-slate-600 dark:text-stone-400 mb-6">
We offer comprehensive solutions for modern web applications
</p>
{% endset %}
{# Empty state configuration #}
{% set empty_state_title = "Services Coming Soon" %}
{% set empty_state_message = "We're preparing our service offerings." %}
{% set empty_state_icon = "M21 13.255A23.931..." %}
{# Hide standard features we don't need #}
{% set show_related_topics = false %}
{% set show_post_statistics = false %}
{# Custom empty state actions #}
{% block empty_state_actions %}
<div class="card-actions card-actions-centre">
<a href="/contact" class="btn-base btn-intent-primary btn-size-lg">
Contact Us
</a>
<a href="/about" class="btn-base btn-intent-secondary btn-size-lg">
Learn More
</a>
</div>
{% endblock %}
{# Additional content after main list #}
{% block additional_content %}
<div class="card-base card-intent-feature card-size-lg mt-12">
<div class="card-body text-centre">
<h3 class="card-title text-2xl mb-4">Custom Solutions</h3>
<p class="card-description mb-6">
Don't see what you need? We create custom solutions tailored to your requirements.
</p>
<a href="/contact" class="btn-base btn-intent-primary btn-size-lg">
Discuss Your Project
</a>
</div>
</div>
{% endblock %}
Pattern 2: Custom Layout Creation
{# themes/agency/templates/layouts/two-column.html #}
{#
# Two-column layout with sidebar
# Extends base and provides content/sidebar blocks
#}
{% extends resolve_base_template('base.html') %}
{% block content %}
<div class="container mx-auto px-4 py-12">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
{# Main content area (2/3 width) #}
<div class="lg:col-span-2">
{% block main_content %}
<p>Main content goes here</p>
{% endblock %}
</div>
{# Sidebar (1/3 width) #}
<aside class="lg:col-span-1">
{% block sidebar %}
<div class="card-base card-intent-info sticky top-4">
<div class="card-header">
<h3 class="card-title">Sidebar</h3>
</div>
<div class="card-body">
<p class="card-description">Sidebar content</p>
</div>
</div>
{% endblock %}
</aside>
</div>
</div>
{% endblock %}
Using the custom layout:
{# themes/agency/templates/pages/case-study.html #}
{% extends "layouts/two-column.html" %}
{% block main_content %}
<article class="prose prose-lg dark:prose-invert max-w-none">
<h1>{{ case_study.title }}</h1>
{{ case_study.content|safe }}
</article>
{% endblock %}
{% block sidebar %}
<div class="space-y-6">
{# Project details card #}
<div class="card-base card-intent-content">
<div class="card-header">
<h3 class="card-title">Project Details</h3>
</div>
<div class="card-body space-y-2">
<div>
<strong>Client:</strong> {{ case_study.client }}
</div>
<div>
<strong>Industry:</strong> {{ case_study.industry }}
</div>
<div>
<strong>Duration:</strong> {{ case_study.duration }}
</div>
</div>
</div>
{# Technologies used #}
<div class="card-base card-intent-info">
<div class="card-header">
<h3 class="card-title">Technologies</h3>
</div>
<div class="card-body">
<div class="flex flex-wrap gap-2">
{% for tech in case_study.technologies %}
<span class="badge-base badge-intent-info badge-size-sm">
{{ tech }}
</span>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}
Pattern 3: Macro-Based Page Builder
{# themes/agency/templates/pages/landing.html #}
{% extends resolve_base_template('base.html') %}
{% from "components/sections.html" import hero, features, cta with context %}
{% block content %}
{# Hero section #}
{{ hero(
title="Transform Your Business",
subtitle="Professional web development solutions",
cta_text="Get Started",
cta_url="/contact",
background="gradient"
) }}
{# Features section #}
{{ features(
title="Why Choose Us",
features=[
{
'icon': '🚀',
'title': 'Fast Delivery',
'description': 'Quick turnaround on all projects'
},
{
'icon': '💎',
'title': 'Quality Code',
'description': 'Clean, maintainable, tested'
},
{
'icon': '🎯',
'title': 'User Focused',
'description': 'Designed with users in mind'
}
]
) }}
{# Call to action #}
{{ cta(
title="Ready to Get Started?",
description="Let's build something amazing together",
primary_button="Contact Us",
primary_url="/contact",
secondary_button="View Work",
secondary_url="/portfolio"
) }}
{% endblock %}
Multi-Mixin Integration
Scenario: Agency Site with Blog and Portfolio
Goal: Create an agency theme that combines blog and portfolio mixins with custom branding.
Step 1: Configure theme dependencies
# themes/agency/config/theme.yaml
name: "Agency Theme"
version: "1.0.0"
parent: "_core"
description: "Professional agency theme with blog and portfolio"
dependencies:
templates:
- "_core/layouts/base.html"
- "_core/layouts/single.html"
mixins:
- "blog"
- "portfolio"
features:
- blog-posts
- portfolio-projects
- team-members
- case-studies
Step 2: Import mixin CSS
/* themes/agency/static/css/src/input.css */
@layer components {
/* Import blog mixin */
@import "../../../../../mixins/blog/static/css/blog.css";
/* Import portfolio mixin */
@import "../../../../../mixins/portfolio/static/css/portfolio.css";
}
@layer theme {
/* Harmonise styles across mixins */
:root {
/* Unified colour scheme */
--color-primary: oklch(55% 0.18 230);
--color-secondary: oklch(60% 0.15 180);
/* Consistent spacing */
--spacing-section: 4rem;
}
/* Unified card styling */
.blog-post-card,
.portfolio-card-base {
border-radius: var(--radius-xl);
border: 2px solid var(--color-border);
transition: all var(--transition-base);
}
.blog-post-card:hover,
.portfolio-card-base:hover {
transform: translateY(-4px);
border-color: var(--color-primary);
box-shadow: var(--shadow-xl);
}
/* Unified typography */
.blog-post-title,
.portfolio-card-title {
font-size: var(--text-2xl);
font-weight: var(--font-weight-bold);
letter-spacing: -0.02em;
}
}
Step 3: Create integrated navigation
{# themes/agency/templates/components/main-nav.html #}
<nav class="main-navigation">
<a href="/" class="nav-link">Home</a>
<a href="/blog" class="nav-link">Blog</a>
<a href="/portfolio" class="nav-link">Portfolio</a>
<a href="/services" class="nav-link">Services</a>
<a href="/about" class="nav-link">About</a>
<a href="/contact" class="nav-link nav-link--cta">Contact</a>
</nav>
Managing Style Conflicts
When using multiple mixins, ensure visual harmony:
@layer theme {
/* Override conflicting styles */
/* Make all cards consistent */
.blog-post-card,
.portfolio-card-base,
.team-member-card {
/* Consistent structure */
background: var(--color-surface);
border: 2px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
/* Consistent interaction */
transition: all var(--transition-base);
cursor: pointer;
}
/* Consistent hover states */
.blog-post-card:hover,
.portfolio-card-base:hover,
.team-member-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
border-color: var(--color-primary);
}
/* Consistent typography */
.blog-post-title,
.portfolio-card-title,
.team-member-name {
font-family: var(--font-heading);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
}
Performance Optimisation
CSS Optimisation
1. Purge unused styles:
// themes/agency/tailwind.config.js
module.exports = {
content: [
"./templates/**/*.html",
"./templates/**/*.jinja",
"../_core/templates/**/*.html",
"../../mixins/*/templates/**/*.html",
],
// ... other config
};
2. Minimise custom CSS:
/* themes/agency/static/css/src/input.css */
@layer theme {
/* Only override what's necessary */
:root {
--color-primary: oklch(55% 0.18 230);
}
/* Don't duplicate entire components */
.blog-post-card {
border-radius: var(--radius-xl); /* Just the change */
}
}
3. Build for production:
# Minified production build
npx tailwindcss -i input.css -o output.css --minify
# With PostCSS optimizations
npx postcss input.css -o output.css --env production
Template Optimisation
1. Use template caching:
# config.py
TEMPLATES_AUTO_RELOAD = False # Production
SEND_FILE_MAX_AGE_DEFAULT = 31536000 # 1 year
2. Lazy load images:
<img
src="project.jpg"
alt="Project"
loading="lazy"
class="portfolio-card-image"
/>
3. Preload critical resources:
<!-- In base.html head -->
<link
rel="preload"
href="{{ url_for('static', filename='css/dist/output.css') }}"
as="style"
/>
<link
rel="preload"
href="{{ url_for('static', filename='fonts/heading.woff2') }}"
as="font"
type="font/woff2"
crossorigin
/>
Theme Variants
Create theme variants that share base styling:
Base theme structure:
themes/
├── agency-base/ # Base theme
│ └── config/
│ └── theme.yaml
├── agency-minimal/ # Minimal variant
│ ├── config/
│ │ └── theme.yaml
│ └── static/css/src/
│ └── input.css
└── agency-bold/ # Bold variant
├── config/
│ └── theme.yaml
└── static/css/src/
└── input.css
Minimal variant:
# themes/agency-minimal/config/theme.yaml
name: "Agency Minimal"
version: "1.0.0"
parent: "agency-base"
description: "Minimal variant of agency theme"
/* themes/agency-minimal/static/css/src/input.css */
@import "../../../agency-base/static/css/src/input.css";
@layer theme {
:root {
/* Minimal colour palette */
--color-primary: oklch(20% 0 0);
--color-secondary: oklch(40% 0 0);
/* Minimal spacing */
--spacing-md: 0.75rem;
--spacing-lg: 1.25rem;
}
/* Remove decorative elements */
.blog-post-card,
.portfolio-card-base {
border: none;
border-bottom: 1px solid var(--color-border);
border-radius: 0;
box-shadow: none;
}
.blog-post-card:hover,
.portfolio-card-base:hover {
transform: none;
box-shadow: none;
background: transparent;
}
}
Production Checklist
Before deploying:
- Build minified CSS
- Test all pages load correctly
- Verify responsive design (mobile, tablet, desktop)
- Test dark mode thoroughly
- Check accessibility (ARIA, focus states, contrast)
- Validate HTML
- Test with slow network
- Verify all images have alt text
- Test keyboard navigation
- Check browser compatibility
- Remove debug/development code
- Set proper cache headers
- Enable compression
- Test performance (Lighthouse)
Troubleshooting
Problem: Mixin styles overriding theme
Cause: Wrong layer order
Solution:
/* Wrong: theme imported before components */
@layer theme {
@import "custom.css";
}
@layer components {
@import "blog.css";
}
/* Right: components before theme */
@layer components {
@import "blog.css";
}
@layer theme {
@import "custom.css";
}
Problem: Inconsistent styling across mixins
Cause: Different default tokens
Solution:
@layer theme {
/* Unify all component styles */
.blog-post-card,
.portfolio-card-base,
.team-member-card {
/* Common structure */
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
/* ... */
}
}
Problem: Dark mode not working everywhere
Cause: Missing dark mode token definitions
Solution:
@layer theme {
/* Define ALL colour tokens for both modes */
:root {
--color-surface: white;
--color-text-primary: black;
}
.dark {
--color-surface: black; /* Don't forget! */
--color-text-primary: white; /* Don't forget! */
}
}
Best Practices Summary
Do's ✓
- Start simple - Begin with minimal customisation
- Use design tokens - Override tokens, not individual styles
- Test with multiple mixins - Ensure compatibility
- Document customisations - Explain why overrides exist
- Follow responsive patterns - Mobile-first approach
- Maintain accessibility - Never sacrifice for design
- Optimise for production - Minify, compress, cache
- Version your themes - Track changes properly
Don'ts ✗
- Don't duplicate components - Override minimally
- Don't hardcode values - Use tokens
- Don't break functionality - Only change appearance
- Don't ignore dark mode - Always provide dark variants
- Don't forget responsive - Test all screen sizes
- Don't skip accessibility - Follow WCAG guidelines
- Don't over-customise - Leverage existing components
- Don't deploy untested - Always test production builds
Next Steps
You now understand advanced theme development. For more information:
- CSS Architecture User Guide - Deep dive into layer system
- Component Development Guide - Creating custom components
- Template Resolution User Guide - Advanced template patterns
Summary
Key principles:
- Build on mixins - Don't reinvent, customise
- Use token system - Change appearance through variables
- Layer properly - Theme layer for customisation
- Test thoroughly - Multiple screens, modes, browsers
- Optimise for production - Minify, cache, compress
- Document everything - Future you will thank you