# CSS and JS Templates in Modyo

## Overview

CSS and JavaScript templates in Modyo are **CDN-served assets** that provide global styles and scripts across a site. They are fundamentally different from snippets and layouts in their capabilities and usage.

## Critical Distinction: CDN-Served vs Server-Rendered

### CSS/JS Templates (CDN-Served)

**Cannot use Liquid markup** - These are static assets served from CDN:
- ❌ No Liquid variables
- ❌ No Liquid loops/conditionals
- ❌ No access to Modyo drops (site, page, spaces, etc.)
- ✅ Plain CSS or JavaScript only
- ✅ Cached and optimized by CDN
- ✅ Fast delivery and performance

**Example - CSS Template**:
```css
/* ✅ CORRECT: Plain CSS using Bootstrap -rgb tokens */
:root, [data-bs-theme=dynamic] {
  --bs-primary-rgb: 216, 27, 96;
  --bs-secondary-rgb: 72, 72, 183;
}

/* ❌ WRONG: Cannot use Liquid */
.btn-primary {
  background: {{ site.primary_color }};  /* Will not work! */
}
```

**Example - JS Template**:
```javascript
// ✅ CORRECT: Plain JavaScript
document.addEventListener('DOMContentLoaded', () => {
  console.log('Site loaded');
  initAnalytics();
});

// ❌ WRONG: Cannot use Liquid
const apiUrl = "{{ site.api_url }}";  // Will not work!
```

### Snippets/Layouts (Server-Rendered)

**Can use Liquid markup** - These are rendered on the server:
- ✅ Full Liquid support
- ✅ Access to all Modyo drops
- ✅ Dynamic content rendering
- ✅ Conditional logic and loops

**Example - Snippet with Liquid**:
```liquid
<!-- ✅ CORRECT: Snippets can use Liquid -->
<style>
  .hero {
    background-color: {{ site.primary_color }};
  }
</style>

{% for product in spaces['products'].types['product'].entries %}
  <div class="product">{{ product.fields['Title'] }}</div>
{% endfor %}
```

## Referencing CSS/JS Templates

CSS and JS templates are referenced in snippets and layouts using **Liquid filters**.

### CSS Template Reference

**Basic syntax**:
```liquid
{{ 'template-name' | asset_url: 'css' | stylesheet_tag }}
```

**With options**:
```liquid
{{ 'root' | asset_url: 'css' | stylesheet_tag: media: 'screen', nonce: csp_nonce }}
{{ 'base' | asset_url: 'css' | stylesheet_tag: media: 'print', nonce: csp_nonce }}
{{ 'dark-theme' | asset_url: 'css' | stylesheet_tag: media: '(prefers-color-scheme: dark)', nonce: csp_nonce }}
```

**Generated HTML**:
```html
<link href="https://cdn.modyo.cloud/uploads/site-123/css/root.css"
      rel="stylesheet"
      type="text/css"
      media="screen"
      nonce="abc123">
```

**Common `stylesheet_tag` options**:
- `media: 'screen'` - Screen devices (default)
- `media: 'print'` - Print styles
- `media: 'all'` - All media types
- `media: '(prefers-color-scheme: dark)'` - Dark mode
- `title: 'Color Style'` - Alternate stylesheets
- `nonce: csp_nonce` - **Required for CSP compliance**

### JS Template Reference

**Basic syntax**:
```liquid
{{ 'template-name' | asset_url: 'js' | script_tag }}
```

**With options**:
```liquid
{{ 'analytics' | asset_url: 'js' | script_tag: async: 'async', nonce: csp_nonce }}
{{ 'utilities' | asset_url: 'js' | script_tag: defer: 'defer', nonce: csp_nonce }}
{{ 'critical' | asset_url: 'js' | script_tag: nonce: csp_nonce }}
```

**Generated HTML**:
```html
<script src="https://cdn.modyo.cloud/uploads/site-123/js/analytics.js"
        type="text/javascript"
        async="async"
        nonce="abc123"></script>
```

**Common `script_tag` options**:
- `async: 'async'` - Load script asynchronously (recommended for analytics)
- `defer: 'defer'` - Defer script execution until DOM ready (recommended for utilities)
- `nonce: csp_nonce` - **Required for CSP compliance**
- No attributes - Blocking load (use for critical scripts only)

### Typical Usage in Layout

**In `head` snippet**:
```liquid
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- CSS Templates (load in order) -->
  {{ 'root' | asset_url: 'css' | stylesheet_tag: media: 'screen', nonce: csp_nonce }}
  {{ 'base' | asset_url: 'css' | stylesheet_tag: media: 'screen', nonce: csp_nonce }}
  {{ 'components' | asset_url: 'css' | stylesheet_tag: media: 'screen', nonce: csp_nonce }}

  {% if site.dark_mode_enabled %}
    {{ 'dark-theme' | asset_url: 'css' | stylesheet_tag: media: '(prefers-color-scheme: dark)', nonce: csp_nonce }}
  {% endif %}
</head>
```

**In `footer` snippet**:
```liquid
<footer>
  <!-- Footer content -->

  <!-- JS Templates (load at end for performance) -->
  {{ 'utilities' | asset_url: 'js' | script_tag: defer: 'defer', nonce: csp_nonce }}
  {{ 'analytics' | asset_url: 'js' | script_tag: async: 'async', nonce: csp_nonce }}
</footer>
```

## Best Practices

### CSS Templates

**1. Token-First CSS — root.css vs base.css**:

| File | Purpose | Contains |
|------|---------|----------|
| `root.css` | Design tokens ONLY | `:root { --bs-*-rgb: R,G,B; }` — Bootstrap `-rgb` token overrides |
| `base.css` | Custom rules ONLY | CSS rules Bootstrap doesn't cover (gradients, animations, layouts) |

- In `root.css`, ONLY override `-rgb` base tokens. Dynamic Framework propagates automatically: `--bs-primary-rgb` → `--bs-primary-500-rgb` → `--bs-primary-500` → `.btn-primary` bg.
- NEVER generate `.btn-primary {}`, `.btn-outline-*` blocks in root.css — this breaks the token propagation chain.
- NEVER use hex values directly (`#4C1D95`). Always use `rgb(var(--bs-primary-rgb))`.
- In `base.css`, for values that can't reference tokens (gradients), leave a comment: `/* hardcoded: update if --bs-primary-rgb changes */`.

**✅ Correct root.css**:
```css
:root, [data-bs-theme=dynamic] {
  --bs-primary-rgb: 76, 29, 149;
  --bs-secondary-rgb: 6, 182, 212;
  --bs-body-bg-rgb: 248, 246, 255;
  --bs-body-color-rgb: 45, 27, 78;
  --bs-link-color-rgb: var(--bs-primary-rgb);
}
```

**❌ Wrong root.css (breaks token chain)**:
```css
--bs-primary-rgb: 76, 29, 149;
.btn-primary { --bs-btn-bg: #4C1D95; }  /* WRONG: hardcoded hex */
.btn-outline-primary { ... }             /* WRONG: component override */
```

**✅ Correct base.css**:
```css
/* Custom rules only — reference tokens, not hex values */
.hero-gradient {
  /* hardcoded: update if --bs-primary-rgb changes */
  background: linear-gradient(135deg, rgb(var(--bs-primary-rgb)), rgb(var(--bs-secondary-rgb)));
}
.site-main { min-height: 60vh; }
```

**2. Leverage Bootstrap Theme**:
```css
/* Don't duplicate Bootstrap - extend it */
/* ✅ GOOD */
.custom-component {
  background: rgb(var(--bs-primary-rgb));
  padding: var(--bs-ref-spacer-4);
}

/* ❌ BAD */
.custom-component {
  background: #f7d1df;  /* Hard-coded value */
  padding: 16px;
}
```

**3. Mobile-First Approach**:
```css
/* Mobile styles first */
.container {
  padding: 1rem;
}

/* Desktop styles with media queries */
@media (min-width: 768px) {
  .container {
    padding: 2rem;
  }
}
```

### JS Templates

**1. Use `defer` for Most Scripts**:
```liquid
<!-- ✅ GOOD: Non-blocking, executes after DOM ready -->
{{ 'utilities' | asset_url: 'js' | script_tag: defer: 'defer', nonce: csp_nonce }}
```

**2. Use `async` for Independent Scripts**:
```liquid
<!-- ✅ GOOD: Analytics doesn't need to block -->
{{ 'analytics' | asset_url: 'js' | script_tag: async: 'async', nonce: csp_nonce }}
```

**3. Only Block for Critical Scripts**:
```liquid
<!-- ⚠️ BLOCKING: Use only when absolutely necessary -->
{{ 'critical-polyfill' | asset_url: 'js' | script_tag: nonce: csp_nonce }}
```

**4. Keep JS Templates Focused**:
```javascript
// analytics.js - Single purpose
(function() {
  if (typeof gtag !== 'undefined') {
    gtag('config', 'GA-XXXXXX');
  }
})();

// utilities.js - Related utilities
const Utils = {
  debounce: (fn, delay) => { /* ... */ },
  throttle: (fn, limit) => { /* ... */ }
};
```

### Performance Optimization

**1. Minimize Number of Templates**:
```liquid
<!-- ❌ BAD: Too many requests -->
{{ 'colors' | asset_url: 'css' | stylesheet_tag }}
{{ 'typography' | asset_url: 'css' | stylesheet_tag }}
{{ 'buttons' | asset_url: 'css' | stylesheet_tag }}
{{ 'cards' | asset_url: 'css' | stylesheet_tag }}

<!-- ✅ GOOD: Combined into fewer files -->
{{ 'root' | asset_url: 'css' | stylesheet_tag }}
{{ 'components' | asset_url: 'css' | stylesheet_tag }}
```

**2. Load Order Matters**:
```liquid
<!-- CSS: Load in dependency order -->
{{ 'root' | asset_url: 'css' | stylesheet_tag }}      <!-- Variables first -->
{{ 'base' | asset_url: 'css' | stylesheet_tag }}      <!-- Base styles second -->
{{ 'components' | asset_url: 'css' | stylesheet_tag }} <!-- Components last -->

<!-- JS: Defer most scripts -->
{{ 'polyfills' | asset_url: 'js' | script_tag }}              <!-- Blocking if needed -->
{{ 'utilities' | asset_url: 'js' | script_tag: defer: 'defer' }}  <!-- Deferred -->
{{ 'analytics' | asset_url: 'js' | script_tag: async: 'async' }}  <!-- Async -->
```

**3. Use Media Queries for Print Styles**:
```liquid
{{ 'screen' | asset_url: 'css' | stylesheet_tag: media: 'screen' }}
{{ 'print' | asset_url: 'css' | stylesheet_tag: media: 'print' }}
```

## Creating CSS/JS Templates

### Via MCP Tools

**Create CSS template**:
```typescript
template-create({
  platformSlug: "fed-team",
  siteId: 4605,
  name: "root",
  type: "css",
  body: `
    :root {
      --bs-primary: rgb(216, 27, 96);
      --bs-secondary: rgb(72, 72, 183);
    }

    body {
      font-family: 'Source Sans 3', sans-serif;
      color: var(--bs-gray-900);
    }
  `
})
```

**Create JS template**:
```typescript
template-create({
  platformSlug: "fed-team",
  siteId: 4605,
  name: "analytics",
  type: "js",
  body: `
    (function() {
      console.log('Analytics initialized');

      // Track page views
      if (typeof gtag !== 'undefined') {
        gtag('event', 'page_view', {
          page_path: window.location.pathname
        });
      }
    })();
  `
})
```

**Update template body**:
```typescript
template-save({
  platformSlug: "fed-team",
  siteId: 4605,
  templateId: 123,
  body: `
    /* Updated CSS */
    .new-class {
      color: var(--bs-primary);
    }
  `
})
```

## Security: CSP Nonce

**Always include `nonce: csp_nonce`** for Content Security Policy compliance:

```liquid
<!-- ✅ CORRECT: With nonce -->
{{ 'root' | asset_url: 'css' | stylesheet_tag: nonce: csp_nonce }}
{{ 'app' | asset_url: 'js' | script_tag: defer: 'defer', nonce: csp_nonce }}

<!-- ❌ WRONG: Missing nonce (may be blocked by CSP) -->
{{ 'root' | asset_url: 'css' | stylesheet_tag }}
{{ 'app' | asset_url: 'js' | script_tag }}
```

The `csp_nonce` variable is automatically provided by Modyo and changes on each request for security.

## Common Mistakes

### ❌ Mistake #1: Using Liquid in CSS/JS Templates

```css
/* ❌ WRONG: This will not work! */
.button {
  background: {{ site.primary_color }};
}
```

**Solution**: Override Bootstrap `-rgb` tokens in root.css:
```css
/* ✅ CORRECT — use Bootstrap tokens */
:root, [data-bs-theme=dynamic] {
  --bs-primary-rgb: 216, 27, 96;
}
/* Buttons automatically use --bs-primary-rgb via Dynamic Framework */
```

### ❌ Mistake #2: Not Publishing Changes

After updating a CSS/JS template, you must **publish** via releases:
```typescript
// 1. Get publishable elements
release-get-elements-to-publish({ platformSlug, siteId })

// 2. Publish template
release-create({
  platformSlug,
  siteId,
  data: {
    template: [{ id: templateId, selected: true }]
  }
})
```

### ❌ Mistake #3: Blocking JS Load

```liquid
<!-- ❌ BAD: Blocks page render -->
{{ 'non-critical' | asset_url: 'js' | script_tag }}

<!-- ✅ GOOD: Non-blocking -->
{{ 'non-critical' | asset_url: 'js' | script_tag: defer: 'defer' }}
```

### ❌ Mistake #4: Missing CSP Nonce

```liquid
<!-- ❌ INSECURE: May be blocked by CSP -->
{{ 'app' | asset_url: 'js' | script_tag }}

<!-- ✅ SECURE: Includes nonce -->
{{ 'app' | asset_url: 'js' | script_tag: nonce: csp_nonce }}
```

## Comparison Table

| Feature | CSS/JS Templates | Snippets/Layouts |
|---------|------------------|------------------|
| **Liquid Support** | ❌ No | ✅ Yes |
| **Delivery** | CDN-served | Server-rendered |
| **Caching** | Aggressive CDN caching | Per-request render |
| **Performance** | Fast (CDN) | Depends on complexity |
| **Use Case** | Global styles/scripts | Dynamic content |
| **Access to Drops** | ❌ No | ✅ Yes (site, page, spaces, etc.) |
| **Variables** | CSS variables only | Liquid variables |
| **Conditionals** | CSS media queries only | Liquid if/unless |
| **Loops** | ❌ No | ✅ Yes (Liquid for loops) |

## Related Documentation

- [Templates Documentation](https://docs.modyo.com/es/platform/channels/templates.html) - Official Modyo docs
- [Liquid Markup](https://docs.modyo.com/en/platform/channels/liquid-markup.html) - Liquid reference
- [Bootstrap 5 Best Practices](./WIDGET_DEVELOPMENT_BEST_PRACTICES.md) - Widget styling guide

---

**Document Version**: 1.0.0
**Last Updated**: 2025-01-10
**Purpose**: Complete guide for CSS and JS templates in Modyo Channels
