Assets: CSS, JS, and Front-End Architecture Intermediate 12 min read

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.css

Load 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

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.css

CSS 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

  1. Flat specificity: All selectors are single class
  2. Self-documenting: Class names explain relationships
  3. Scoped by default: No accidental style leaks
  4. Easy to find: Search for .product-card finds 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

  1. Choose a file strategy (single, multiple, or hybrid) based on theme size
  2. Use CSS custom properties for theme settings integration
  3. Follow BEM naming for predictable, maintainable selectors
  4. Scope dynamic styles with section.id when needed
  5. Layer your CSS from generic to specific
  6. Create utilities for common patterns
  7. Start with a reset for consistent baseline
  8. 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...