theming


# Theming Guide

Customize your site’s appearance with the MarkoPress theme system.

# Overview

MarkoPress uses a powerful slot-based theme system that lets you customize any part of your site without forking the entire theme. The theme is built with:

  • CSS Custom Properties - Easy color and style customization
  • Component Slots - Override any component without touching the core
  • Layout System - Flexible layouts for different page types
  • Dark Mode - Built-in dark mode support

# Quick Customization

The fastest way to customize your site is through CSS variables.

# CSS Variables

Create .markopress/theme/styles.css in your project:

/* .markopress/theme/styles.css */

:root {
  
  --bg-primary: #f8f9fa;
  --bg-secondary: white;
  --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

  
  --text-primary: #333;
  --text-secondary: #666;
  --text-inverted: white;

  
  --accent-color: #667eea;
  --accent-hover: #5568d3;

  
  --border-color: #e9ecef;
  --card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  --code-bg: #f4f4f4;
}

.dark-mode {
  
  --bg-primary: #1a1a1a;
  --bg-secondary: #2d2d2d;
  --text-primary: #e0e0e0;
  --text-secondary: #a0a0a0;
  --border-color: #404040;
  --accent-color: #7c8ffc;
  --code-bg: #1e1e1e;
}

# Example: Custom Color Scheme

Transform your site with a new color palette:

:root {
  
  --bg-gradient: linear-gradient(135deg, #0061ff 0%, #60efff 100%);
  --accent-color: #0061ff;
  --accent-hover: #0052cc;
}
:root {
  
  --bg-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  --accent-color: #f5576c;
  --accent-hover: #d64558;
}
:root {
  
  --bg-gradient: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
  --accent-color: #11998e;
  --accent-hover: #0e8076;
}

# Component Overrides

Override any theme component by copying it to your project.

# Override Structure

your-project/
├── .markopress/
│   └── theme/
│       ├── components/      # Override components
│       │   ├── Header.marko
│       │   ├── Footer.marko
│       │   └── Sidebar.marko
│       ├── layouts/         # Override layouts
│       │   ├── default.marko
│       │   └── docs.marko
│       └── styles.css       # Custom styles

# Custom Header

Copy Header.marko from @markopress/theme-default and modify:

<!-- .markopress/theme/components/Header.marko -->

class {
  onCreate() {
    this.state = {
      mobileMenuOpen: false,
    };
  }

  toggleMobileMenu() {
    this.state.mobileMenuOpen = !this.state.mobileMenuOpen;
  }
}

<header class="custom-header">
  <div class="header-container">
    <!-- Logo -->
    <a href="/" class="logo">
      <img src="/logo.svg" alt="My Site" />
    </a>

    <!-- Navigation -->
    <nav class=${['navbar', { mobile: state.mobileMenuOpen }]}>
      <ul class="nav-links">
        <for|item| of=input.navbar>
          <li>
            <a href=item.link target=item.target || '_self'>
              ${item.text}
            </a>
          </li>
        </for>
      </ul>
    </nav>

    <!-- Mobile Menu Toggle -->
    <button class="mobile-toggle" onClick=>toggleMobileMenu()>
      ☰
    </button>
  </div>
</header>

<style>`
  .custom-header {
    background: var(--bg-secondary);
    padding: 1rem 2rem;
    border-radius: 12px;
    box-shadow: var(--card-shadow);
    margin-bottom: 2rem;
  }

  .header-container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 2rem;
  }

  .logo img {
    height: 40px;
  }

  .navbar {
    display: flex;
    align-items: center;
  }

  .nav-links {
    display: flex;
    list-style: none;
    gap: 2rem;
    margin: 0;
    padding: 0;
  }

  .nav-links a {
    color: var(--text-primary);
    text-decoration: none;
    font-weight: 500;
    transition: color 0.2s;
  }

  .nav-links a:hover {
    color: var(--accent-color);
  }

  .mobile-toggle {
    display: none;
    background: none;
    border: none;
    font-size: 1.5rem;
    cursor: pointer;
  }

  @media (max-width: 768px) {
    .header-container {
      flex-wrap: wrap;
    }

    .navbar {
      display: none;
      width: 100%;
    }

    .navbar.mobile {
      display: block;
    }

    .nav-links {
      flex-direction: column;
      gap: 1rem;
      padding: 1rem 0;
    }

    .mobile-toggle {
      display: block;
    }
  }
`</style>
<!-- .markopress/theme/components/Footer.marko -->

<footer class="custom-footer">
  <div class="footer-container">
    <!-- Copyright -->
    <div class="footer-copyright">
      <p>${input.copyright || '© {year} My Site'}</p>
    </div>

    <!-- Footer Links -->
    <if(input.links)>
      <nav class="footer-links">
        <for|link| of=input.links>
          <a href=link.link target=link.target || '_self'>
            ${link.text}
          </a>
        </for>
      </nav>
    </if>

    <!-- Social Links -->
    <div class="footer-social">
      <a href="https://github.com/user/repo" aria-label="GitHub">
        <div data-marko-tag="0"></div>
      </a>
      <a href="https://twitter.com/user" aria-label="Twitter">
        <div data-marko-tag="1"></div>
      </a>
    </div>
  </div>
</footer>

<style>`
  .custom-footer {
    background: var(--bg-secondary);
    padding: 3rem 2rem;
    margin-top: 4rem;
    border-radius: 12px;
  }

  .footer-container {
    max-width: 1200px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 2rem;
  }

  .footer-links {
    display: flex;
    gap: 2rem;
  }

  .footer-links a {
    color: var(--text-secondary);
    text-decoration: none;
  }

  .footer-links a:hover {
    color: var(--accent-color);
  }

  @media (max-width: 768px) {
    .footer-container {
      flex-direction: column;
      text-align: center;
    }

    .footer-links {
      flex-direction: column;
      gap: 1rem;
    }
  }
`</style>

# Layout Customization

Customize layouts for different page types.

# Documentation Layout

Create .markopress/theme/layouts/docs.marko:

<!-- .markopress/theme/layouts/docs.marko -->

class {
  onCreate() {
    this.state = {
      tocOpen: false,
    };
  }
}

<include('./layouts/default.marko')>
  <@header>input.header || <components.Header/> </@header>
  <@body>
    <div class="docs-layout">
      <!-- Sidebar -->
      <aside class="docs-sidebar">
        <include(input.sidebar/>
      </aside>

      <!-- Main Content -->
      <main class="docs-main">
        <div class="docs-content">
          <include(input.content/>
        </div>

        <!-- Prev/Next Navigation -->
        <if(input.prev || input.next)>
          <nav class="docs-nav">
            <if(input.prev)>
              <a href=input.prev.link class="docs-nav-prev">
                <span class="nav-label">← Previous</span>
                <span class="nav-title">${input.prev.title}</span>
              </a>
            </if>

            <if(input.next)>
              <a href=input.next.link class="docs-nav-next">
                <span class="nav-label">Next →</span>
                <span class="nav-title">${input.next.title}</span>
              </a>
            </if>
          </nav>
        </if>
      </main>

      <!-- Table of Contents -->
      <aside class="docs-toc">
        <include(input.toc/>
      </aside>
    </div>
  </@body>
  <@footer>input.footer || <components.Footer/> </@footer>
</include>

<style>`
  .docs-layout {
    display: grid;
    grid-template-columns: 280px 1fr 200px;
    gap: 2rem;
    position: relative;
  }

  .docs-sidebar {
    position: sticky;
    top: 2rem;
    height: fit-content;
    max-height: calc(100vh - 4rem);
    overflow-y: auto;
  }

  .docs-main {
    min-width: 0; 
  }

  .docs-toc {
    position: sticky;
    top: 2rem;
    height: fit-content;
  }

  .docs-nav {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    margin-top: 3rem;
    padding-top: 2rem;
    border-top: 2px solid var(--border-color);
  }

  .docs-nav a {
    flex: 1;
    padding: 1rem;
    background: var(--bg-primary);
    border: 2px solid var(--border-color);
    border-radius: 8px;
    text-decoration: none;
    color: var(--text-primary);
    transition: all 0.2s;
  }

  .docs-nav a:hover {
    border-color: var(--accent-color);
    background: var(--bg-secondary);
  }

  @media (max-width: 1024px) {
    .docs-layout {
      grid-template-columns: 1fr;
    }

    .docs-sidebar,
    .docs-toc {
      display: none;
    }
  }
`</style>

# Blog Layout

<!-- .markopress/theme/layouts/blog.marko -->

<include('./layouts/default.marko')>
  <@header>input.header || <components.Header/> </@header>
  <@body>
    <article class="blog-post">
      <!-- Post Header -->
      <header class="post-header">
        <h1>${input.title}</h1>
        <div class="post-meta">
          <div data-marko-tag="2"></div>
          <span class="post-author">by ${input.author}</span>
        </div>
        <if(input.tags)>
          <div class="post-tags">
            <for|tag| of=input.tags>
              <span class="tag">${tag}</span>
            </for>
          </div>
        </if>
      </header>

      <!-- Post Content -->
      <div class="post-content">
        <include(input.content/>
      </div>

      <!-- Share Buttons -->
      <div class="post-share">
        <button onClick=>shareToTwitter()>Share on Twitter</button>
        <button onClick=>copyLink()>Copy Link</button>
      </div>
    </article>
  </@body>
  <@footer>input.footer || <components.Footer/> </@footer>
</include>

<style>`
  .blog-post {
    max-width: 720px;
    margin: 0 auto;
  }

  .post-header {
    margin-bottom: 3rem;
  }

  .post-header h1 {
    font-size: 2.5rem;
    margin-bottom: 1rem;
  }

  .post-meta {
    color: var(--text-secondary);
    font-size: 0.9rem;
  }

  .post-tags {
    display: flex;
    gap: 0.5rem;
    margin-top: 1rem;
  }

  .tag {
    padding: 0.25rem 0.75rem;
    background: var(--bg-primary);
    border-radius: 12px;
    font-size: 0.875rem;
  }

  .post-share {
    display: flex;
    gap: 1rem;
    margin-top: 3rem;
    padding-top: 2rem;
    border-top: 2px solid var(--border-color);
  }
`</style>

# Custom Theme Development

Create a completely custom theme from scratch.

# Theme Structure

my-custom-theme/
├── package.json
├── src/
   ├── components/
   ├── Header.marko
   ├── Footer.marko
   └── Sidebar.marko
   ├── layouts/
   ├── default.marko
   ├── docs.marko
   └── blog.marko
   └── styles/
       ├── main.css
       └── syntax.css
└── index.ts

# Package.json

{
  "name": "@my-org/theme-custom",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": "./index.ts",
    "./components/*": "./src/components/*.marko",
    "./layouts/*": "./src/layouts/*.marko",
    "./styles/*": "./src/styles/*.css"
  },
  "marko": {
    "tags": "./src/components"
  }
}

# Theme Entry Point

// index.ts
import type { Theme } from 'markopress';

export const theme: Theme = {
  name: '@my-org/theme-custom',

  // Default layout
  layout: './src/layouts/default.marko',

  // Components
  components: {
    Header: './src/components/Header.marko',
    Footer: './src/components/Footer.marko',
    Sidebar: './src/components/Sidebar.marko',
  },

  // Styles
  styles: [
    './src/styles/main.css',
  ],

  // Layout aliases
  layouts: {
    docs: './src/layouts/docs.marko',
    blog: './src/layouts/blog.marko',
  },
};

# Use Custom Theme

// markopress.config.ts
import { defineConfig } from 'markopress';

export default defineConfig({
  theme: '@my-org/theme-custom',

  themeConfig: {
    // Your theme config
  },
});

# Advanced Customization

# Add Custom Components

Create reusable components in your theme:

<!-- .markopress/theme/components/Callout.marko -->

<div class="callout callout--${input.type || 'info'}">
  <div class="callout-icon">
    ${input.icon || 'ℹ️'}
  </div>
  <div class="callout-content">
    <if(input.title)>
      <strong>${input.title}</strong>
    </if>
    <p>${input.content}</p>
  </div>
</div>

<style>`
  .callout {
    display: flex;
    gap: 1rem;
    padding: 1.5rem;
    border-radius: 8px;
    margin: 1.5rem 0;
  }

  .callout--info {
    background: #e3f2fd;
    border-left: 4px solid #2196f3;
  }

  .callout--warning {
    background: #fff3e0;
    border-left: 4px solid #ff9800;
  }

  .callout--danger {
    background: #ffebee;
    border-left: 4px solid #f44336;
  }

  .callout-icon {
    font-size: 1.5rem;
  }
`</style>

Use in your markdown:

<components.Callout
  type="warning"
  title="Important"
  content="This is a warning callout"
/>

# Dynamic Styling

Use Marko’s reactivity for dynamic styling:

<!-- .markopress/theme/components/ThemeSwitcher.marko -->

class {
  onCreate() {
    this.state = {
      theme: localStorage.getItem('theme') || 'light',
    };

    if (this.state.theme === 'dark') {
      document.body.classList.add('dark-mode');
    }
  }

  setTheme(theme) {
    this.state.theme = theme;
    localStorage.setItem('theme', theme);

    if (theme === 'dark') {
      document.body.classList.add('dark-mode');
    } else {
      document.body.classList.remove('dark-mode');
    }
  }
}

<div class="theme-switcher">
  <button
    class=${['theme-btn', { active: state.theme === 'light' }]}
    onClick=>setTheme('light')
  >
    ☀️ Light
  </button>
  <button
    class=${['theme-btn', { active: state.theme === 'dark' }]}
    onClick=>setTheme('dark')
  >
    🌙 Dark
  </button>
</div>

# Theme Configuration

Configure theme behavior in markopress.config.ts:

export default defineConfig({
  themeConfig: {
    // Site metadata
    name: 'My Site',
    description: 'My awesome site',

    // Navigation
    navbar: [
      { text: 'Home', link: '/' },
      { text: 'Docs', link: '/docs' },
      { text: 'Blog', link: '/blog' },
    ],

    // Sidebar
    sidebar: {
      '/guides/': [
        {
          text: 'Getting Started',
          items: [
            { text: 'Introduction', link: '/guides/intro' },
            { text: 'Installation', link: '/guides/install' },
          ],
        },
      ],
    },

    // Footer
    footer: {
      copyright: '© {year} My Site',
      links: [
        { text: 'Privacy', link: '/privacy' },
        { text: 'GitHub', link: 'https://github.com/user/repo' },
      ],
    },

    // Feature toggles
    features: {
      darkMode: true,
      editLink: true,
      lastUpdated: true,
      prevNext: true,
      sidebar: true,
    },
  },
});

# Best Practices

# 1. Use CSS Variables

Always prefer CSS variables over hard-coded values:

/* ❌ Bad */
.button {
  background: #667eea;
}


.button {
  background: var(--accent-color);
}

# 2. Respect Dark Mode

Always define dark mode overrides:

:root {
  --bg-primary: #f8f9fa;
}

.dark-mode {
  --bg-primary: #1a1a1a;
}

# 3. Responsive Design

Use mobile-first responsive design:

.component {
  
  padding: 1rem;
}

@media (min-width: 768px) {
  .component {
    
    padding: 2rem;
  }
}

# 4. Accessibility

Ensure proper contrast and keyboard navigation:

.button:focus {
  outline: 2px solid var(--accent-color);
  outline-offset: 2px;
}


.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: var(--accent-color);
  color: white;
  padding: 8px;
  text-decoration: none;
}

.skip-link:focus {
  top: 0;
}

# Examples

# Minimal Theme

:root {
  --bg-primary: white;
  --bg-secondary: #fafafa;
  --text-primary: #111;
  --accent-color: #000;
}

# Colorful Theme

:root {
  --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  --accent-color: #764ba2;
}

# High Contrast Theme

:root {
  --bg-primary: white;
  --text-primary: #000;
  --accent-color: #000;
  --border-color: #000;
}

# Next Steps