CSS Architecture User Guide
Overview
This guide covers practical applications of MintyFlask's five-layer CSS architecture. It assumes you've read the Quick Start and understand the basic layer concepts.
What you'll learn:
- Working with all five layers in depth
- Common development scenarios with complete examples
- CSS custom properties and theming patterns
- Dark mode implementation
- Troubleshooting layer issues
- Best practices for maintainable styles
Understanding Layer Precedence
How Layers Work
CSS layers create an explicit hierarchy that's independent of specificity:
@layer components {
/* This has LOWER priority... */
div.container section.blog article.post {
background: blue;
}
}
@layer theme {
/* ...than this simple selector */
.post {
background: red; /* WINS! */
}
}
/* Result: background is red */
The layer order determines precedence. No need for specificity hacks.
Layer Declaration
Layers are declared in your main CSS file (input.css):
/* themes/blog/static/css/src/input.css */
/* Import Tailwind */
@import "tailwindcss";
/* Declare layer order (lowest to highest priority) */
@layer reset {
/* Browser resets */
}
@layer base {
/* Foundation styles */
@import "foundations/tokens.css";
@import "foundations/typography.css";
}
@layer components {
/* Core components */
@import "components/buttons.css";
@import "components/cards.css";
/* Mixin components */
@import "../../../mixins/blog/static/css/blog.css";
}
@layer theme {
/* Theme customisations */
@import "custom/overrides.css";
@import "custom/components.css";
}
@layer utilities {
/* High-priority helpers */
@import "utilities/accessibility.css";
}
The Five Layers in Detail
Layer 1: Reset (Minimal Usage)
Purpose: Browser normalisation beyond what Tailwind provides
When to use:
- Custom resets needed for your project
- Normalising legacy browser behaviour
- Resetting third-party widget styles
Example:
/* themes/_core/static/css/foundations/reset.css */
@layer reset {
/* Custom form element reset */
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
}
/* Fix for specific browser quirk */
button {
-webkit-appearance: button;
}
}
Best practices:
- Keep it minimal—Tailwind handles most resets
- Document why each reset is needed
- Consider if you really need it
Layer 2: Base (Foundation)
Purpose: Design tokens, typography, default element styles
When to use:
- Defining design tokens (colours, spacing, typography)
- Setting default HTML element styles
- Establishing typography scale
- Creating dark mode token variations
Example - Design Tokens:
/* themes/blog/static/css/foundations/tokens.css */
@layer base {
:root {
/* Colour tokens */
--color-primary: oklch(60% 0.15 250);
--color-secondary: oklch(65% 0.12 180);
--color-surface: oklch(98% 0 0);
--color-surface-dark: oklch(20% 0 0);
--color-border: oklch(85% 0 0);
--color-border-dark: oklch(30% 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);
/* Spacing scale */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
/* Typography scale */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
/* Border radius */
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
}
}
Example - Typography:
/* themes/_core/static/css/foundations/typography.css */
@layer base {
body {
font-family:
system-ui,
-apple-system,
sans-serif;
font-size: var(--text-base);
line-height: 1.5;
color: var(--color-text-primary);
background: var(--color-surface);
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 600;
line-height: 1.2;
margin-bottom: var(--spacing-md);
color: var(--color-text-primary);
}
h1 {
font-size: var(--text-3xl);
}
h2 {
font-size: var(--text-2xl);
}
h3 {
font-size: var(--text-xl);
}
h4 {
font-size: var(--text-lg);
}
p {
margin-bottom: var(--spacing-md);
}
a {
color: var(--color-primary);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
}
Best practices:
- Use CSS custom properties for all values
- Create semantic token names
- Keep element styles minimal
- Document token purpose
Layer 3: Components (Mixin Development)
Purpose: Reusable components from mixins and core
When to use:
- Building mixin features
- Creating reusable components
- Developing core component library
- Any style that might be used across themes
Example - Blog Mixin Component:
/* mixins/blog/static/css/blog.css */
@layer components {
/* Blog Navigation */
.blog-navigation {
display: flex;
gap: var(--spacing-md);
padding: var(--spacing-lg) 0;
border-bottom: 1px solid var(--color-border);
}
.blog-navigation-link {
color: var(--color-text-secondary);
font-weight: 500;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-sm);
transition: all 0.2s ease;
}
.blog-navigation-link:hover {
background: var(--color-surface-hover);
color: var(--color-text-primary);
}
.blog-navigation-link--active {
background: var(--color-primary);
color: white;
}
/* Post Card Component */
.blog-post-card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}
.blog-post-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.blog-post-card--featured {
border-width: 2px;
border-color: var(--color-primary);
}
.blog-post-title {
font-size: var(--text-xl);
font-weight: 600;
margin-bottom: var(--spacing-sm);
color: var(--color-text-primary);
}
.blog-post-excerpt {
color: var(--color-text-secondary);
line-height: 1.6;
margin-bottom: var(--spacing-md);
}
.blog-post-meta {
display: flex;
gap: var(--spacing-md);
font-size: var(--text-sm);
color: var(--color-text-tertiary);
}
/* Dark mode variants */
.dark .blog-navigation {
border-color: var(--color-border-dark);
}
.dark .blog-post-card {
background: var(--color-surface-dark);
border-color: var(--color-border-dark);
}
.dark .blog-post-card:hover {
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.1);
}
}
Component variants with BEM:
@layer components {
/* Base component */
.blog-button {
padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius-md);
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
/* Size modifiers */
.blog-button--small {
padding: var(--spacing-xs) var(--spacing-md);
font-size: var(--text-sm);
}
.blog-button--large {
padding: var(--spacing-md) var(--spacing-xl);
font-size: var(--text-lg);
}
/* Intent modifiers */
.blog-button--primary {
background: var(--color-primary);
color: white;
}
.blog-button--secondary {
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text-primary);
}
/* State modifiers */
.blog-button:hover {
transform: translateY(-1px);
}
.blog-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
Best practices:
- Use mixin-specific prefix (
.blog-,.portfolio-) - Employ BEM modifiers for variants
- Always include dark mode styles
- Use custom properties, never hardcode
- Keep components focused and single-purpose
Layer 4: Theme (Customisation)
Purpose: Site-specific customisations and overrides
When to use:
- Customising mixin components for your theme
- Adding theme-specific components
- Overriding design tokens
- Creating unique branded elements
Example - Theme Overrides:
/* themes/agency/static/css/custom/overrides.css */
@layer theme {
/* Override blog card for agency theme */
.blog-post-card {
border-radius: 0; /* Square corners */
border-width: 2px;
border-color: var(--color-accent);
}
.blog-post-card:hover {
border-color: var(--color-primary);
transform: scale(1.02); /* Different hover */
}
/* Override navigation for agency theme */
.blog-navigation {
background: var(--color-surface-elevated);
padding: var(--spacing-md) var(--spacing-lg);
border-radius: var(--radius-lg);
border: none;
}
.blog-navigation-link {
text-transform: uppercase;
font-size: var(--text-sm);
letter-spacing: 0.05em;
}
}
Example - Theme Components:
/* themes/agency/static/css/custom/components.css */
@layer theme {
/* Hero section unique to agency theme */
.agency-hero {
background: linear-gradient(
135deg,
var(--color-primary),
var(--color-secondary)
);
colour: white;
padding: var(--spacing-2xl) var(--spacing-lg);
text-align: centre;
position: relative;
overflow: hidden;
}
.agency-hero::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(
circle at top right,
rgba(255, 255, 255, 0.1),
transparent
);
}
.agency-hero-title {
font-size: var(--text-3xl);
font-weight: 700;
margin-bottom: var(--spacing-md);
position: relative;
z-index: 1;
}
.agency-hero-subtitle {
font-size: var(--text-xl);
opacity: 0.9;
position: relative;
z-index: 1;
}
/* Client logo grid */
.agency-clients {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: var(--spacing-lg);
padding: var(--spacing-xl) 0;
}
.agency-client-logo {
display: flex;
align-items: centre;
justify-content: centre;
padding: var(--spacing-md);
opacity: 0.6;
transition: opacity 0.2s ease;
}
.agency-client-logo:hover {
opacity: 1;
}
}
Example - Token Overrides:
/* themes/agency/static/css/foundations/tokens.css */
@layer theme {
:root {
/* Override spacing for more generous layout */
--spacing-md: 1.25rem;
--spacing-lg: 2rem;
--spacing-xl: 3rem;
/* Override typography for larger text */
--text-base: 1.125rem;
--text-lg: 1.25rem;
--text-xl: 1.5rem;
/* Agency-specific tokens */
--color-accent: oklch(65% 0.18 45);
--color-surface-elevated: oklch(99% 0 0);
}
}
Best practices:
- Override only what you need
- Maintain accessibility
- Don't break component functionality
- Document major overrides
- Test with multiple mixins
Layer 5: Utilities (High Priority)
Purpose: Accessibility helpers, debugging, emergency overrides
When to use:
- Screen reader only content
- Print styles
- Debugging tools
- Truly exceptional overrides
Example - Accessibility:
/* themes/_core/static/css/utilities/accessibility.css */
@layer utilities {
/* Screen reader only */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Focus visible for keyboard navigation */
.focus-visible:focus {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* Skip to content link */
.skip-to-content {
position: absolute;
top: -40px;
left: 0;
background: var(--color-primary);
color: white;
padding: var(--spacing-md);
z-index: 1000;
}
.skip-to-content:focus {
top: 0;
}
}
Example - Print Styles:
/* themes/_core/static/css/utilities/print.css */
@layer utilities {
@media print {
.no-print {
display: none !important;
}
.page-break-before {
page-break-before: always;
}
.page-break-after {
page-break-after: always;
}
/* Optimize for print */
body {
font-size: 12pt;
line-height: 1.4;
}
a {
text-decoration: underline;
}
a[href]::after {
content: " (" attr(href) ")";
font-size: 90%;
}
}
}
Example - Development Debugging:
/* themes/_core/static/css/utilities/debug.css */
@layer utilities {
/* Outline all elements for debugging */
.debug-layout * {
outline: 1px solid rgba(255, 0, 0, 0.3);
}
/* Show hidden elements */
.debug-hidden [hidden],
.debug-hidden .sr-only {
position: static !important;
width: auto !important;
height: auto !important;
opacity: 0.5 !important;
outline: 2px dashed orange !important;
}
/* Grid overlay */
.debug-grid {
background-image:
linear-gradient(rgba(0, 0, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 0, 255, 0.1) 1px, transparent 1px);
background-size: 20px 20px;
}
}
Best practices:
- Use sparingly—utilities should be exceptional
- Document why
!importantis needed if used - Prefer higher layers over
!important - Remove debug utilities from production
Common Development Scenarios
Scenario 1: Creating a New Mixin
Goal: Build a portfolio mixin with project cards and filtering.
Step 1: Plan your components
Portfolio mixin needs:
- .portfolio-grid (container)
- .portfolio-project-card (individual projects)
- .portfolio-filter (category filtering)
- .portfolio-project-title
- .portfolio-project-description
- .portfolio-project-tags
Step 2: Create component CSS
/* mixins/portfolio/static/css/portfolio.css */
@layer components {
/* Portfolio grid container */
.portfolio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: var(--spacing-lg);
padding: var(--spacing-lg) 0;
}
/* Project card */
.portfolio-project-card {
background: var(--color-surface);
border-radius: var(--radius-lg);
overflow: hidden;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}
.portfolio-project-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
/* Project image */
.portfolio-project-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
/* Project content */
.portfolio-project-content {
padding: var(--spacing-lg);
}
.portfolio-project-title {
font-size: var(--text-xl);
font-weight: 600;
margin-bottom: var(--spacing-sm);
color: var(--color-text-primary);
}
.portfolio-project-description {
color: var(--color-text-secondary);
line-height: 1.6;
margin-bottom: var(--spacing-md);
}
/* Project tags */
.portfolio-project-tags {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.portfolio-project-tag {
padding: var(--spacing-xs) var(--spacing-sm);
background: var(--color-surface-elevated);
border-radius: var(--radius-sm);
font-size: var(--text-sm);
color: var(--color-text-secondary);
}
/* Category filter */
.portfolio-filter {
display: flex;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-lg);
flex-wrap: wrap;
}
.portfolio-filter-button {
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: transparent;
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.2s ease;
}
.portfolio-filter-button:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
.portfolio-filter-button--active {
background: var(--color-primary);
border-color: var(--color-primary);
color: white;
}
/* Dark mode */
.dark .portfolio-project-card {
background: var(--color-surface-dark);
}
.dark .portfolio-project-tag {
background: var(--color-border-dark);
}
.dark .portfolio-filter-button {
border-color: var(--color-border-dark);
}
}
Step 3: Use in template
<!-- Filter -->
<div class="portfolio-filter">
<button class="portfolio-filter-button portfolio-filter-button--active">
All
</button>
<button class="portfolio-filter-button">Web Design</button>
<button class="portfolio-filter-button">Branding</button>
</div>
<!-- Grid -->
<div class="portfolio-grid">
<article class="portfolio-project-card">
<img src="project.jpg" alt="Project name" class="portfolio-project-image" />
<div class="portfolio-project-content">
<h3 class="portfolio-project-title">Project Title</h3>
<p class="portfolio-project-description">
Brief description of the project...
</p>
<div class="portfolio-project-tags">
<span class="portfolio-project-tag">Web Design</span>
<span class="portfolio-project-tag">React</span>
</div>
</div>
</article>
<!-- More cards... -->
</div>
Scenario 2: Customising a Mixin for Your Theme
Goal: Customise the blog mixin for a minimal design aesthetic.
/* themes/minimal/static/css/custom/overrides.css */
@layer theme {
/* Minimal blog card - remove shadows and borders */
.blog-post-card {
border: none;
border-bottom: 1px solid var(--color-border);
border-radius: 0;
padding: var(--spacing-xl) 0;
}
.blog-post-card:hover {
transform: none;
box-shadow: none;
background: transparent;
}
/* Minimal typography */
.blog-post-title {
font-size: var(--text-2xl);
font-weight: 400; /* Lighter weight */
letter-spacing: -0.02em;
}
.blog-post-excerpt {
color: var(--color-text-tertiary);
font-size: var(--text-sm);
}
/* Minimal navigation */
.blog-navigation {
border: none;
padding: var(--spacing-md) 0;
}
.blog-navigation-link {
background: transparent;
border-radius: 0;
border-bottom: 2px solid transparent;
padding: var(--spacing-sm) 0;
}
.blog-navigation-link:hover {
background: transparent;
border-bottom-color: var(--color-text-primary);
}
.blog-navigation-link--active {
background: transparent;
color: var(--color-text-primary);
border-bottom-color: var(--color-primary);
}
}
Scenario 3: Implementing Dark Mode
Goal: Add comprehensive dark mode support to your theme.
Step 1: Define dark mode tokens
/* themes/blog/static/css/foundations/tokens.css */
@layer base {
:root {
/* Light mode (default) */
--color-surface: oklch(98% 0 0);
--color-text-primary: oklch(20% 0 0);
--color-border: oklch(85% 0 0);
}
/* Dark mode tokens */
.dark {
--color-surface: oklch(20% 0 0);
--color-surface-elevated: oklch(25% 0 0);
--color-text-primary: oklch(95% 0 0);
--color-text-secondary: oklch(75% 0 0);
--color-border: oklch(30% 0 0);
}
}
Step 2: Add dark variants to components
/* mixins/blog/static/css/blog.css */
@layer components {
.blog-post-card {
background: var(--color-surface);
border-color: var(--color-border);
color: var(--color-text-primary);
}
/* Dark mode - tokens handle it automatically! */
/* But add specific dark overrides if needed */
.dark .blog-post-card:hover {
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.1);
}
}
Step 3: Theme toggle (JavaScript)
<button id="theme-toggle" class="theme-toggle">
<span class="light-icon">🌞</span>
<span class="dark-icon">🌙</span>
</button>
<script>
const toggle = document.getElementById("theme-toggle");
const html = document.documentElement;
toggle.addEventListener("click", () => {
html.classList.toggle("dark");
localStorage.setItem(
"theme",
html.classList.contains("dark") ? "dark" : "light",
);
});
// Load saved preference
if (localStorage.getItem("theme") === "dark") {
html.classList.add("dark");
}
</script>
Scenario 4: Responsive Design Patterns
Goal: Create responsive components that work on all screen sizes.
/* mixins/portfolio/static/css/portfolio.css */
@layer components {
/* Mobile-first approach */
.portfolio-grid {
display: grid;
grid-template-columns: 1fr; /* Single column on mobile */
gap: var(--spacing-md);
}
/* Tablet */
@media (min-width: 640px) {
.portfolio-grid {
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing-lg);
}
}
/* Desktop */
@media (min-width: 1024px) {
.portfolio-grid {
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-xl);
}
}
/* Project card responsive */
.portfolio-project-content {
padding: var(--spacing-md);
}
@media (min-width: 640px) {
.portfolio-project-content {
padding: var(--spacing-lg);
}
}
/* Responsive typography */
.portfolio-project-title {
font-size: var(--text-lg);
}
@media (min-width: 640px) {
.portfolio-project-title {
font-size: var(--text-xl);
}
}
@media (min-width: 1024px) {
.portfolio-project-title {
font-size: var(--text-2xl);
}
}
}
Troubleshooting
Problem: Styles Not Applying
Symptom: Your CSS doesn't seem to work.
Common causes:
-
Wrong layer priority
/* Wrong: trying to override theme with components */ @layer components { .blog-post-card { background: red; /* Won't work if theme sets it */ } } /* Right: use higher layer */ @layer theme { .blog-post-card { background: red; } } -
Unlayered CSS
/* Wrong: unlayered CSS has unpredictable priority */ .blog-post-card { background: red; } /* Right: always use layers */ @layer theme { .blog-post-card { background: red; } } -
Import order
/* Wrong order in input.css */ @layer theme { @import "custom.css"; } @layer components { @import "blog.css"; /* Loads after theme! */ } /* Right order */ @layer components { @import "blog.css"; } @layer theme { @import "custom.css"; }
Problem: Class Name Conflicts
Symptom: Styles from different mixins interfere.
Cause: Generic class names without prefixes.
Solution:
/* Wrong: conflicts between mixins */
@layer components {
.card {
} /* blog.css */
.card {
} /* portfolio.css - conflict! */
}
/* Right: use prefixes */
@layer components {
.blog-card {
}
.portfolio-card {
}
}
Problem: Dark Mode Not Working
Symptom: Dark mode styles don't apply.
Common causes:
-
Wrong selector
/* Wrong */ @media (prefers-color-scheme: dark) { .card { background: black; } } /* Right: use .dark class */ @layer components { .dark .card { background: black; } } -
Missing token definitions
/* Wrong: no dark mode tokens */ @layer base { :root { --color-surface: white; } } /* Right: define dark tokens */ @layer base { :root { --color-surface: white; } .dark { --color-surface: black; } }
Problem: Specificity Issues
Symptom: Need increasingly specific selectors.
Cause: Fighting specificity instead of using layers.
Solution:
/* Wrong: specificity war */
.container .blog .post {
}
.container .blog .post.featured {
}
body .container .blog .post.featured.highlighted {
}
/* Right: use layers and simple selectors */
@layer components {
.blog-post {
}
.blog-post--featured {
}
}
@layer theme {
.blog-post--highlighted {
}
}
Best Practices Summary
Do's ✓
- Always use layers for all custom CSS
- Use CSS custom properties for all values
- Prefix component classes with mixin name
- Include dark mode variants
- Override minimally in theme layer
- Document unusual overrides
- Test with multiple mixins active
- Keep components focused and reusable
Don'ts ✗
- Don't mix layered and unlayered CSS
- Don't hardcode values
- Don't use overly specific selectors
- Don't put component styles in utilities
- Don't override component functionality
- Don't use
!importantunless in utilities - Don't duplicate entire components
- Don't ignore accessibility
Development Checklist
For every component:
- Wrapped in appropriate
@layer - Uses CSS custom properties
- Has prefixed class names
- Includes dark mode variant
- Responsive on all screen sizes
- Accessible (focus states, ARIA)
- Documented with comments
- Tested with other mixins
Next Steps
You now understand how to work with MintyFlask's layer system in practice. For more information:
- Component Development Guide - Building reusable components
Summary
Key principles:
- Five layers - Each has a specific purpose
- Layer priority - Higher layers always win
- Use tokens - Never hardcode values
- Prefix classes - Avoid naming conflicts
- Dark mode first - Always include dark variants
- Override minimally - Only change what you need
- Test comprehensively - Multiple mixins together
With these practices, you'll build maintainable, conflict-free CSS that works reliably across your entire site.