CSS Structure for Maintainability
Learn how to organize your theme's CSS for long-term maintainability using naming conventions, custom properties, and component-based architecture.
A well-organized CSS codebase is crucial for theme maintainability. As themes grow, unstructured CSS becomes a nightmare of specificity wars and unexpected side effects. Let’s explore patterns that keep your styles predictable and scalable.
Single File vs Multiple Files
Single File Approach
Dawn and many themes use a single base.css:
assets/└── base.css (all styles in one file)Pros:
- One HTTP request
- Simpler to manage
- No load order issues
Cons:
- Large file size
- Loads CSS for pages you don’t visit
- Harder to navigate in large themes
Multiple Files Approach
Split CSS by component or page:
assets/├── base.css├── component-header.css├── component-footer.css├── component-product-card.css├── section-hero.css├── template-product.css└── template-collection.cssLoad conditionally:
{{ 'base.css' | asset_url | stylesheet_tag }}
{%- if request.page_type == 'product' -%} {{ 'template-product.css' | asset_url | stylesheet_tag }}{%- endif -%}Pros:
- Smaller initial load
- Only load what’s needed
- Easier to find styles
Cons:
- More HTTP requests
- Need to manage dependencies
- More complex loading logic
Recommended Hybrid Approach
Use a base file for global styles and critical components, with optional page-specific files:
assets/├── base.css # Reset, typography, utilities, critical components├── component-cart.css # Only loaded when cart is used├── template-product.css└── template-collection.cssCSS Custom Properties from Theme Settings
Connect theme settings to CSS using custom properties:
{# snippets/css-variables.liquid #}{% style %}:root { /* Colors */ --color-primary: {{ settings.color_primary }}; --color-secondary: {{ settings.color_secondary }}; --color-background: {{ settings.color_background }}; --color-text: {{ settings.color_text }}; --color-text-light: {{ settings.color_text | color_mix: settings.color_background, 40 }}; --color-border: {{ settings.color_text | color_modify: 'alpha', 0.1 }};
/* Typography */ --font-body: {{ settings.type_body_font.family }}, {{ settings.type_body_font.fallback_families }}; --font-heading: {{ settings.type_header_font.family }}, {{ settings.type_header_font.fallback_families }}; --font-body-weight: {{ settings.type_body_font.weight }}; --font-heading-weight: {{ settings.type_header_font.weight }};
/* Sizing */ --font-size-base: {{ settings.font_size_base }}px; --spacing-unit: {{ settings.spacing_unit }}px; --border-radius: {{ settings.border_radius }}px; --container-width: {{ settings.container_width }}px;
/* Calculated values */ --spacing-xs: calc(var(--spacing-unit) * 0.5); --spacing-sm: calc(var(--spacing-unit) * 0.75); --spacing-md: var(--spacing-unit); --spacing-lg: calc(var(--spacing-unit) * 1.5); --spacing-xl: calc(var(--spacing-unit) * 2); --spacing-2xl: calc(var(--spacing-unit) * 3);}{% endstyle %}Use in your CSS:
body { font-family: var(--font-body); font-size: var(--font-size-base); color: var(--color-text); background: var(--color-background);}
.button { padding: var(--spacing-sm) var(--spacing-md); border-radius: var(--border-radius); background: var(--color-primary);}
.section { padding: var(--spacing-xl) 0;}Naming Conventions
BEM (Block Element Modifier)
BEM provides clear, predictable class names:
/* Block */.product-card {}
/* Element (part of block) */.product-card__image {}.product-card__title {}.product-card__price {}
/* Modifier (variation) */.product-card--featured {}.product-card--sold-out {}.product-card__price--sale {}<article class="product-card product-card--featured"> <img class="product-card__image" src="..." /> <h3 class="product-card__title">Product Name</h3> <p class="product-card__price product-card__price--sale">$19.99</p></article>Benefits of BEM
- Flat specificity: All selectors are single class
- Self-documenting: Class names explain relationships
- Scoped by default: No accidental style leaks
- Easy to find: Search for
.product-cardfinds all related styles
Common Patterns
/* Component with state */.nav-link {}.nav-link--active {}.nav-link--disabled {}
/* Component with layout variations */.product-grid {}.product-grid--2-col {}.product-grid--3-col {}.product-grid--4-col {}
/* Component elements */.modal {}.modal__overlay {}.modal__content {}.modal__header {}.modal__body {}.modal__footer {}.modal__close {}Component-Scoped Styles
Use section.id to scope styles to specific section instances:
<style> #shopify-section-{{ section.id }} { background-color: {{ section.settings.background_color }}; color: {{ section.settings.text_color }}; }
#shopify-section-{{ section.id }} .heading { font-size: {{ section.settings.heading_size }}px; }</style>
<section class="featured-collection"> <h2 class="heading">{{ section.settings.heading }}</h2> <!-- content --></section>When to Use Scoped Styles
- Colors that vary per section instance
- Spacing controlled by section settings
- Dynamic values from the theme editor
When to Use Global Styles
- Base component appearance
- Typography scales
- Layout patterns
- Utility classes
CSS Architecture Layers
Organize your CSS in layers from generic to specific:
/* ========================================================================== 1. SETTINGS - Variables, custom properties ========================================================================== */:root { --color-primary: #000; /* ... */}
/* ========================================================================== 2. BASE - Reset, elements, typography ========================================================================== */*,*::before,*::after { box-sizing: border-box;}
body { margin: 0; font-family: var(--font-body);}
h1,h2,h3 { font-family: var(--font-heading);}
/* ========================================================================== 3. LAYOUT - Container, grid, section spacing ========================================================================== */.container { max-width: var(--container-width); margin: 0 auto; padding: 0 var(--spacing-md);}
.section { padding: var(--spacing-xl) 0;}
/* ========================================================================== 4. COMPONENTS - Reusable UI components ========================================================================== */.button {}.product-card {}.modal {}
/* ========================================================================== 5. SECTIONS - Shopify section styles ========================================================================== */.hero-banner {}.featured-collection {}
/* ========================================================================== 6. UTILITIES - Helper classes ========================================================================== */.visually-hidden {}.text-center {}.mt-0 { margin-top: 0;}Utility Classes
Create reusable utility classes for common patterns:
/* Visibility */.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;}
.hidden { display: none !important;}
/* Text alignment */.text-left { text-align: left;}.text-center { text-align: center;}.text-right { text-align: right;}
/* Flex utilities */.flex { display: flex;}.flex-wrap { flex-wrap: wrap;}.items-center { align-items: center;}.justify-between { justify-content: space-between;}.gap-sm { gap: var(--spacing-sm);}.gap-md { gap: var(--spacing-md);}
/* Spacing */.mt-0 { margin-top: 0;}.mb-0 { margin-bottom: 0;}.mt-md { margin-top: var(--spacing-md);}.mb-md { margin-bottom: var(--spacing-md);}CSS Reset/Normalize
Start with a clean foundation:
/* Modern CSS Reset */*,*::before,*::after { box-sizing: border-box;}
* { margin: 0;}
html { -webkit-text-size-adjust: 100%;}
body { line-height: 1.5; -webkit-font-smoothing: antialiased;}
img,picture,video,canvas,svg { display: block; max-width: 100%;}
input,button,textarea,select { font: inherit;}
p,h1,h2,h3,h4,h5,h6 { overflow-wrap: break-word;}
a { color: inherit; text-decoration: none;}
button { cursor: pointer; background: none; border: none;}
ul,ol { list-style: none; padding: 0;}Complete Component Example
Here’s a well-structured product card component:
/* ========================================================================== Product Card Component ========================================================================== */
.product-card { display: flex; flex-direction: column; height: 100%; text-decoration: none; color: inherit;}
/* Image container with aspect ratio */.product-card__media { position: relative; aspect-ratio: 3/4; overflow: hidden; background: var(--color-background-secondary);}
.product-card__image { width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s ease;}
.product-card:hover .product-card__image { transform: scale(1.05);}
/* Badge positioning */.product-card__badge { position: absolute; top: var(--spacing-sm); left: var(--spacing-sm); padding: var(--spacing-xs) var(--spacing-sm); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;}
.product-card__badge--sale { background: var(--color-sale); color: white;}
.product-card__badge--sold-out { background: var(--color-text); color: var(--color-background);}
/* Content area */.product-card__content { display: flex; flex-direction: column; flex: 1; padding: var(--spacing-sm) 0;}
.product-card__vendor { font-size: 0.75rem; color: var(--color-text-light); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: var(--spacing-xs);}
.product-card__title { font-size: 1rem; font-weight: 500; margin-bottom: var(--spacing-xs);}
/* Price variations */.product-card__price { margin-top: auto; font-weight: 500;}
.product-card__price--sale .product-card__price-current { color: var(--color-sale);}
.product-card__price-compare { color: var(--color-text-light); text-decoration: line-through; margin-left: var(--spacing-xs);}Practice Exercise
Refactor this messy CSS into a well-structured component:
/* Before: Messy CSS */.card { background: white;}.card h3 { font-size: 18px; margin: 10px;}.card .price { color: green;}.card.sale .price { color: red;}.card img { width: 100%;}.card:hover { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);}After: Structured BEM:
/* Product Card Component */.product-card { background: var(--color-background); transition: box-shadow 0.2s ease;}
.product-card:hover { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);}
.product-card__image { width: 100%; aspect-ratio: 1; object-fit: cover;}
.product-card__title { font-size: 1.125rem; margin: var(--spacing-sm);}
.product-card__price { color: var(--color-text); margin: var(--spacing-sm);}
.product-card--sale .product-card__price { color: var(--color-sale);}Key Takeaways
- Choose a file strategy (single, multiple, or hybrid) based on theme size
- Use CSS custom properties for theme settings integration
- Follow BEM naming for predictable, maintainable selectors
- Scope dynamic styles with
section.idwhen needed - Layer your CSS from generic to specific
- Create utilities for common patterns
- Start with a reset for consistent baseline
- Comment section headers for navigation
What’s Next?
With your CSS well-organized, the next lesson covers Responsive Layout Strategies for building themes that work beautifully across all screen sizes.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...