BlogNetwork Pro

Theme Development Guide

Theme Development Guide

This guide explains how to create, customize, and manage themes programmatically in Multi-Domain AutoBlogger. Learn how to build theme systems, create themes via code, and extend the theming capabilities.

Note: This guide is for developers working with themes in code. For information on improving the theme system itself, see the Improving Theme Implementation Guide.

How Themes Work

The theme system operates in three layers:

Layer 1: Database Storage

Themes are stored in PostgreSQL with all customization values as database columns:

Layer 2: CSS Generation

Theme data is transformed into CSS variables at request time using the generateThemeCSS() function. This creates a stylesheet with variables like --color-primary, --color-link-hover, and --font-heading.

Layer 3: Application

The generated CSS is injected into the domain layout and applied to all child components via inline <style> tags.

Theme Data Structure

TypeScript Interface

All themes conform to this interface:

interface Theme {
  id: number;
  name: string;
  // Colors
  primaryColor: string;
  secondaryColor: string;
  accentColor: string;
  bodyTextColor: string;
  backgroundColor: string;
  linkHoverColor?: string;
  borderColor?: string;
  codeBgColor?: string;
  codeTextColor?: string;
  shadowOpacity?: number;
  
  // Typography
  fontFamily: string;
  headingFont: string;

  // Layout & Visuals
  headerLayout?: string;
  footerLayout?: string;
  menuStyle?: string;
  sidebarPosition?: string;
  contentWidth?: string;
  backgroundPattern?: string; 
  
  createdAt: Date;
  updatedAt: Date;
}

Database Schema

The themes table is defined in lib/db/schema.ts:

export const themes = pgTable("themes", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
  
  // Colors
  primaryColor: varchar("primary_color", { length: 7 }).notNull(),
  secondaryColor: varchar("secondary_color", { length: 7 }).notNull(),
  accentColor: varchar("accent_color", { length: 7 }).notNull(),
  bodyTextColor: varchar("body_text_color", { length: 7 }).notNull(),
  backgroundColor: varchar("background_color", { length: 7 }).notNull(),
  linkHoverColor: varchar("link_hover_color", { length: 7 }),
  borderColor: varchar("border_color", { length: 7 }),
  codeBgColor: varchar("code_bg_color", { length: 7 }),
  codeTextColor: varchar("code_text_color", { length: 7 }),
  
  // Layout
  headerLayout: varchar("header_layout", { length: 50 }),
  footerLayout: varchar("footer_layout", { length: 50 }),
  // ... other layout fields
});

Example Theme Object

{
  "id": 1,
  "name": "Dark Professional",
  "primaryColor": "#1f2937",
  "secondaryColor": "#6b7280",
  "accentColor": "#3b82f6",
  "linkHoverColor": "#2563eb",
  "borderColor": "#374151",
  "fontFamily": "Inter, sans-serif",
  "headingFont": "Roboto, sans-serif",
  "headerLayout": "centered",
  "createdAt": "2024-01-15T10:30:00Z"
}

Creating Themes Programmatically

Method 1: Via REST API (Recommended)

Create themes using the REST API endpoint:

const response = await fetch('/api/themes', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Modern Startup',
    primaryColor: '#0c3c26',
    secondaryColor: '#475569',
    accentColor: '#2563eb',
    bodyTextColor: '#1f2937',
    backgroundColor: '#ffffff',
    fontFamily: 'Inter, sans-serif',
    headingFont: 'Poppins, sans-serif',
  }),
});

const theme = await response.json();
console.log(`Theme created with ID: ${theme.id}`);

Endpoint: POST /api/themes
Authentication: Required (admin)
Returns: Theme object with generated ID

Method 2: Via Drizzle ORM (Backend)

Create themes directly from backend code or scripts:

import { db } from '@/lib/db/client';
import { themes } from '@/lib/db/schema';

async function createTheme() {
  const newTheme = await db.insert(themes).values({
    name: 'Modern Startup',
    primaryColor: '#0c3c26',
    secondaryColor: '#475569',
    accentColor: '#2563eb',
    bodyTextColor: '#1f2937',
    backgroundColor: '#ffffff',
    fontFamily: 'Inter, sans-serif',
    headingFont: 'Poppins, sans-serif',
  }).returning();

  return newTheme[0];
}

Method 3: Bulk Seeding

Create multiple theme presets in a seed script:

// /scripts/seed-themes.ts
import { db } from '@/lib/db/client';
import { themes } from '@/lib/db/schema';

const themePresets = [
  {
    name: 'Modern Startup',
    primaryColor: '#0c3c26',
    secondaryColor: '#475569',
    accentColor: '#2563eb',
    bodyTextColor: '#1f2937',
    backgroundColor: '#ffffff',
    fontFamily: 'Inter, sans-serif',
    headingFont: 'Poppins, sans-serif',
  },
  {
    name: 'Minimalist Tech',
    primaryColor: '#1f2937',
    secondaryColor: '#6b7280',
    accentColor: '#0891b2',
    bodyTextColor: '#374151',
    backgroundColor: '#ffffff',
    fontFamily: 'Open Sans, sans-serif',
    headingFont: 'Inter, sans-serif',
  },
];

async function seedThemes() {
  for (const themeData of themePresets) {
    const existing = await db.query.themes.findFirst({
      where: (themes, { eq }) => eq(themes.name, themeData.name),
    });

    if (!existing) {
      await db.insert(themes).values(themeData);
      console.log(`✅ Created: ${themeData.name}`);
    }
  }
}

seedThemes().catch(console.error);

Run with: npx tsx scripts/seed-themes.ts

Understanding CSS Generation

The generateThemeCSS() Function

Located in lib/themes/utils.ts, this function converts a theme object into CSS:

export function generateThemeCSS(theme: Theme): string {
  return `
    :root {
      --color-primary: ${theme.primaryColor};
      --color-secondary: ${theme.secondaryColor};
      --color-accent: ${theme.accentColor};
      --font-family: ${theme.fontFamily};
      --font-heading: ${theme.headingFont};
      --color-body-text: ${theme.bodyTextColor};
      --color-background: ${theme.backgroundColor};
    }
    body {
      font-family: var(--font-family);
      color: var(--color-body-text);
      background-color: var(--color-background);
    }
    h1, h2, h3, h4, h5, h6 {
      font-family: var(--font-heading);
    }
  `;
}

Generated CSS Output Example

:root {
  --color-primary: #0c3c26;
  --color-link-hover: #0a2e1d;
  --color-border: #e2e8f0;
  --color-code-bg: #f8fafc;
  --font-family: Inter, sans-serif;
  --font-heading: Poppins, sans-serif;
  --pattern-background: url('data:image/svg+xml...');
}
body {
  font-family: var(--font-family);
  color: var(--color-body-text);
  background-image: var(--pattern-background);
}
a:hover {
  color: var(--color-link-hover);
}
code {
  background-color: var(--color-code-bg);
}

Using CSS Variables in Components

Once CSS variables are available, use them in your components:

// In JSX
<h1 style={{ color: 'var(--color-primary)' }}>
  Site Title
</h1>

<p style={{ color: 'var(--color-body-text)' }}>
  Article content
</p>

<a style={{ color: 'var(--color-accent)' }}>
  Link
</a>

// In CSS
.header {
  color: var(--color-primary);
  background-color: var(--color-background);
}

a {
  color: var(--color-accent);
}

Working with Themes in Code

Getting a Theme by ID

import { db } from '@/lib/db/client';

const theme = await db.query.themes.findFirst({
  where: (themes, { eq }) => eq(themes.id, themeId),
});

if (theme) {
  console.log(`Theme: ${theme.name}`);
}

Getting All Themes

const allThemes = await db.query.themes.findMany();

Getting a Domain's Theme

const domainWithTheme = await db.query.domains.findFirst({
  where: (domains, { eq }) => eq(domains.id, domainId),
  with: {
    theme: true,
  },
});

if (domainWithTheme?.theme) {
  const css = generateThemeCSS(domainWithTheme.theme);
}

Updating a Theme

import { eq } from 'drizzle-orm';

await db.update(themes)
  .set({
    primaryColor: '#0c3c26',
    updatedAt: new Date(),
  })
  .where(eq(themes.id, themeId));

Deleting a Theme

await db.delete(themes)
  .where(eq(themes.id, themeId));

Assigning Themes to Domains

Via API

await fetch(`/api/domains/${domainId}`, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    themeId: 1,
  }),
});

Via Drizzle ORM

import { eq } from 'drizzle-orm';

await db.update(domains)
  .set({
    themeId: 1,
    updatedAt: new Date(),
  })
  .where(eq(domains.id, domainId));

Extending the Theme System

Adding a New Color Property

To add a new customizable color (example: error color):

1. Update Database Schema

// lib/db/schema.ts
export const themes = pgTable("themes", {
  // ... existing fields
  errorColor: varchar("error_color", { length: 7 })
    .notNull()
    .default("#dc2626"),
});

2. Update TypeScript Interface

// lib/themes/utils.ts
export interface Theme {
  // ... existing fields
  errorColor: string;
}

3. Update CSS Generation

export function generateThemeCSS(theme: Theme): string {
  return `
    :root {
      // ... existing variables
      --color-error: ${theme.errorColor};
    }
    // ... existing rules
  `;
}

4. Update API Endpoints

// /app/api/themes/route.ts
const newTheme = await sql`
  INSERT INTO themes (..., error_color)
  VALUES (..., $9)
  RETURNING *
`;

5. Update Admin Form (optional)

// /components/admin/ThemeForm.tsx
<div className="space-y-2">
  <label htmlFor="errorColor">Error Color</label>
  <input
    type="color"
    id="errorColor"
    {...form.register('errorColor')}
    className="w-12 h-10"
  />
</div>

6. Use in Components

<div style={{ color: 'var(--color-error)' }}>
  Error message
</div>

Theme Provider & Context

Using the ThemeProvider

The theme system includes a React Context for accessing theme data in client components:

// lib/themes/provider.tsx
import { createContext, useContext } from 'react';

const ThemeContext = createContext<{ theme: Theme | null }>({ theme: null });

export function ThemeProvider({ children, theme }) {
  return (
    <ThemeContext.Provider value={{ theme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}

Accessing Theme in Client Components

'use client';

import { useTheme } from '@/lib/themes/provider';

export function MyComponent() {
  const { theme } = useTheme();

  return (
    <div>
      <p>Current theme: {theme?.name}</p>
      <p>Primary color: {theme?.primaryColor}</p>
    </div>
  );
}

Best Practices

Color Management

Font Management

CSS Generation

Database Operations

Complete Example: Theme Creation Workflow

Scenario: Create Theme and Assign to Domain

// Step 1: Create a theme
async function setupTheme() {
  // Create theme
  const themeResponse = await fetch('/api/themes', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      name: 'Brand 2024',
      primaryColor: '#1e3a8a',
      secondaryColor: '#0284c7',
      accentColor: '#dc2626',
      fontFamily: 'Inter, sans-serif',
      headingFont: 'Poppins, sans-serif',
      bodyTextColor: '#111827',
      backgroundColor: '#ffffff',
    }),
  });

  const theme = await themeResponse.json();
  console.log(`✅ Theme created: ${theme.id}`);

  // Step 2: Assign to domain
  const domainResponse = await fetch(`/api/domains/${domainId}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ themeId: theme.id }),
  });

  const updatedDomain = await domainResponse.json();
  console.log(`✅ Theme assigned to domain: ${updatedDomain.domain_name}`);

  return { theme, domain: updatedDomain };
}

Testing Themes

Manual Testing Checklist

Automated Testing Example

import { generateThemeCSS } from '@/lib/themes/utils';

describe('Theme CSS Generation', () => {
  it('should generate CSS variables from theme', () => {
    const theme = {
      primaryColor: '#000000',
      secondaryColor: '#666666',
      accentColor: '#0066cc',
      fontFamily: 'Arial, sans-serif',
      headingFont: 'Georgia, serif',
      bodyTextColor: '#333333',
      backgroundColor: '#ffffff',
    };

    const css = generateThemeCSS(theme);

    expect(css).toContain('--color-primary: #000000');
    expect(css).toContain('--font-family: Arial, sans-serif');
  });
});

API Endpoints Reference

Theme CRUD Operations

Domain Theme Assignment

Key Files Reference

Theme System Core

API & Admin

Troubleshooting

Theme Not Applying

Font Not Loading

Colors Look Wrong

Next Steps

Now that you understand theme development: