The realisation arrived quietly, as these things often do. I was building a Flask-based static site generator grounded in semantic HTML and YAML-driven content—what would eventually become MintyFlaskSSG—when someone asked whether the themes could work with a regular Flask application. Not a static site. Just... Flask.
The answer should have been obvious: yes, of course. Themes are templates. Templates are Jinja2 files. Flask renders Jinja2 templates. Nothing about the three-layer template architecture required static site generation. Nothing about semantic components needed build-time processing. The presentation layer I'd built worked perfectly well for dynamic applications.
But they were trapped inside a monolithic project that conflated two distinct concerns.
That tension—presentation locked alongside content processing when each deserved independent consideration—led to splitting the original project into two: MintyFlaskSSG for content management and static site generation, MintyFlaskThemes for presentation and template composition. This is the story of that split, and the philosophy that emerged from recognising presentation as its own concern.
The monolithic problem
The original project handled everything: content processing, routing, build mechanics, template architecture, and visual themes. This made sense initially. Static site generators typically bundle these concerns. Hugo does. Jekyll does. Pelican does. Why wouldn't mine?
The coupling revealed itself gradually. Someone wanted to use the blog templates with a CMS. Another developer liked the semantic component system but needed database-backed content. A third project required the three-layer template architecture without any static site generation at all.
Each request pointed to the same truth: presentation concerns—template hierarchies, semantic components, CSS architecture—existed independently of content processing. The three-layer system didn't care whether content came from Markdown files, databases, or API calls. The semantic component library worked equally well for static builds or dynamic rendering. The mixin architecture enabled composition regardless of deployment method.
I'd built one project solving two distinct problems. Static site generation: how to process content and generate HTML at build time. Presentation architecture: how to compose templates and components in maintainable hierarchies. These problems shared an implementation (both used Flask and Jinja2) but served different purposes.
The split became inevitable once I saw this clearly. MintyFlaskSSG would focus on content management—processing Markdown, handling YAML configuration, managing photo libraries, generating static sites. MintyFlaskThemes would focus on presentation—template composition, semantic components, CSS architecture, visual themes. Each project concentrating on its distinct domain whilst maintaining clean interfaces between them.
Semantic HTML as foundation
MintyFlaskThemes emerges from a specific philosophy about HTML and CSS. I believe markup should carry meaning, not just facilitate styling. An <article> declares document structure. A <nav> explicitly identifies navigation purpose. A <section> delineates content boundaries. This matters for accessibility, searchability, maintainability, and—frankly—for the craft itself.
Modern frameworks often treat HTML as generated output, something produced rather than authored. Component systems abstract markup away. Utility-first CSS encourages inline styling over semantic class names. The emphasis shifts toward rapid development and runtime flexibility.
I understand the appeal. Utility classes let you prototype quickly. Component frameworks enable powerful abstractions. These approaches solve real problems.
But they solve different problems than the ones I face.
When I build websites, I want to understand the HTML that reaches browsers. I want template files containing actual markup I can read and reason about. I want CSS classes that describe what elements are, not just how they look. I want composition through clear hierarchies, not runtime magic.
This preference led to MintyFlaskThemes' semantic-first approach. Components use meaningful class names: .card-base, .nav-primary, .layout-stack-loose. Templates contain real HTML structure. The three-layer architecture enables composition through explicit inheritance patterns you can inspect and understand.
The utility balance
I spent considerable time thinking about semantic classes versus utility classes. Tailwind's utilities excel at rapid prototyping. Need padding? Add p-4. Want flexbox? Use flex items-center. This directness appeals when you're exploring designs or building one-off components.
But maintaining complex systems with pure utilities becomes problematic. When you need the same utility sequence across multiple elements, change management gets tricky. Suppose you want to change bg-blue-500 to bg-blue-600 across specific headers. You could search and replace, but risk affecting unintended elements. You could use comments for tracking, but once you're marking elements semantically in comments, why not use semantic class names in the first place?
For MintyFlaskThemes, I settled on roughly 70% semantic components, 30% utility flexibility. Core components use meaningful class names: .card-intent-primary, .btn-size-lg, .layout-container-article. Templates compose these semantically. But Tailwind utilities remain available for one-off adjustments, spacing tweaks, and rapid prototyping where semantic abstractions feel excessive.
This balance reflects my experience, not a prescription. Your ratio might differ. The architecture accommodates both approaches—semantic components for maintainable structure, utilities for flexibility where needed.
The three-layer architecture
At MintyFlaskThemes' heart sits a template hierarchy that enables composition without complexity:
Theme Layer (themes/business-theme/)
↓ extends/imports
Mixin Layer (mixins/blog/, mixins/photos/)
↓ extends/includes
Core Layer (themes/_core/)
Each layer serves distinct purposes:
Core provides foundation—base templates, essential components, structural patterns that everything builds upon. The HTML skeleton. The semantic component library. Layout primitives that work regardless of content type or visual design.
Mixins supply functionality—blog features, photo galleries, documentation navigation, contact forms. Self-contained feature sets you include as needed. Each mixin brings templates, components, and sometimes CSS additions. They work independently or compose together.
Themes handle presentation—colours, typography, spacing, visual identity. A theme extends core templates and might include mixin templates, but focuses on aesthetic decisions. Swap themes to change appearance whilst keeping functionality intact.
This maps to Flask's standard ChoiceLoader pattern:
template_directories = [
"themes/business-theme/templates/",
"mixins/blog/templates/",
"mixins/photos/templates/",
"themes/_core/templates/"
]
app.jinja_loader = ChoiceLoader([
FileSystemLoader(dir) for dir in template_directories
])
When Flask requests a template, it searches these directories in order. Theme wins if present. Mixins next. Core as fallback. Standard Jinja2 behaviour—no custom loaders, no magic, just organised search paths.
The modularity matters in practice. Need a blog? Include the blog mixin. Want different aesthetics? Swap the theme directory. Building something unique? Override specific templates whilst inheriting everything else. The architecture accommodates customisation without forcing wholesale rewrites.
For developers familiar with other systems, this feels recognisable but more explicit. Hugo has themes and partials. Jekyll uses includes and layouts. MintyFlaskThemes makes resolution order visible and controllable through Flask patterns you can inspect, debug, and extend.
Mixins as compositional units
The mixin system deserves extended treatment elsewhere—it's substantial enough for its own article. But its role in MintyFlaskThemes' architecture warrants brief mention here.
Mixins provide functional components that work across themes. The blog mixin supplies post templates, category pages, tag clouds, archives, pagination—complete blog functionality as a self-contained unit. The photo mixin handles gallery display, image processing integration, lightbox components. Future mixins might provide documentation navigation, e-commerce features, or contact form handling.
Each mixin brings its own templates, components, and styling that extend core foundations. Themes can override mixin templates for visual customisation whilst preserving functionality. This separation—functionality in mixins, aesthetics in themes, foundation in core—enables mixing and matching without conflicts.
The mixin architecture means MintyFlaskThemes grows through addition rather than modification. Need new features? Create a new mixin. Want different presentation? Build a new theme. Core remains stable, mixins provide functionality, themes handle aesthetics. Clean separation, clear composition.
Beyond static sites
Splitting MintyFlaskThemes into its own project recognises that presentation layers transcend deployment methods. The three-layer template architecture works equally well for:
- Static sites generated with MintyFlaskSSG
- Dynamic Flask applications with database backends
- Content management systems built on Flask
- Documentation sites with API-driven content
- Marketing sites with CMS integration
- Blogs using any content storage approach
Nothing about semantic HTML requires static generation. Nothing about three-layer composition demands build-time processing. Templates are templates. Components are components. Flask renders them regardless of where content originates.
This generality matters because it means MintyFlaskThemes serves as foundation for diverse projects. You might use it with MintyFlaskSSG for static sites. You might use it with Flask-SQLAlchemy for database applications. You might use it with custom API integration for headless CMS workflows. The presentation layer remains consistent whilst content sources vary.
The separation also enables independent evolution. MintyFlaskSSG can improve content processing without affecting template architecture. MintyFlaskThemes can refine components without concerning itself with build mechanics. Each project focuses on its distinct domain whilst maintaining clean interfaces between them.
Foundation, not framework
MintyFlaskThemes positions itself as foundation rather than framework. Frameworks make decisions for you, enforce patterns, demand conformity. Foundations provide structure you build upon, patterns you can adopt or adapt, architecture that enables rather than constrains.
The distinction matters in practice. If you want a blog using blog mixin templates exactly as provided—that works. If you need custom post layouts that extend blog mixin patterns—that works too. If you want to build something entirely different that borrows core components and ignores everything else—still works. The architecture accommodates all these approaches.
This flexibility stems from following Flask conventions rather than inventing new ones. Templates use standard Jinja2 inheritance. Components use straightforward CSS classes. The three-layer system maps to Flask's template loader configuration. You can inspect what happens, extend where needed, and understand behaviour through knowledge of Flask and Jinja2 alone.
For developers comfortable with Flask, MintyFlaskThemes should feel immediately comprehensible. The patterns echo what you already know. The architecture extends familiar concepts rather than replacing them. You're working with Flask applications that happen to follow particular template organisation patterns—not learning an entirely new system.
What comes next
MintyFlaskThemes exists now as its own project, available for any Flask application that benefits from semantic components, three-layer composition, and mixin-based functionality. The split from the original monolithic project clarifies what each concern addresses and enables both MintyFlaskThemes and MintyFlaskSSG to evolve independently.
Future development will explore the mixin architecture more deeply—how mixins compose, how themes customise them, how new mixins integrate cleanly. The semantic component system will expand whilst maintaining the 70/30 semantic-utility balance that works for my projects. Theme examples will demonstrate patterns without prescribing specific aesthetics.
But the core philosophy remains: presentation deserves its own foundation, semantic HTML matters, composition through layers beats monolithic templates, and Flask conventions provide sufficient power without requiring framework complexity.