Ep.019 Building a Professional WordPress Block Theme Options System – The Right Way

Views: 79

Welcome back to the Versana WordPress Block Theme Development series! Over the past 18 episodes, we’ve built the foundation of our block theme. Now we’re entering Phase 3: Professional Architecture & Design System.

But before we dive into code, we need to address a critical architectural decision that separates amateur themes from professional, best-selling ones:

How should theme.json and a Theme Options page work together?

This isn’t just a technical question—it’s about building a theme that’s:

  • Future-proof and aligned with WordPress’s vision
  • User-friendly for both beginners and developers
  • Competitive with top-selling themes like Kadence and GeneratePress
  • Extendable through child themes and plugins

In this episode, we’ll build the correct architecture from the ground up.

Understanding the WordPress Block Theme Ecosystem

WordPress’s Vision: Site Editor First

WordPress is moving toward Full Site Editing (FSE) where:

  • theme.json defines the design system structure
  • Site Editor is the primary user interface
  • Visual customization replaces code editing

Top themes embrace this vision while adding value on top.

The Professional Architecture

Here’s how professional block themes actually work:

┌─────────────────────────────────────────┐
│       THEME OPTIONS PAGE                │
│   (Advanced Features & Settings)        │
├─────────────────────────────────────────┤
│ ✓ Load Google Fonts                    │
│ ✓ Performance optimization              │
│ ✓ Feature toggles                       │
│ ✓ Third-party integrations              │
│ ✓ Custom scripts                        │
│ ✓ Developer options                     │
└─────────────┬───────────────────────────┘
              │ Enhances & feeds data
              ↓
┌─────────────────────────────────────────┐
│          theme.json                     │
│    (Design System Structure)            │
├─────────────────────────────────────────┤
│ ✓ Color palette                         │
│ ✓ Typography scale                      │
│ ✓ Spacing system                        │
│ ✓ Layout settings                       │
└─────────────┬───────────────────────────┘
              │ Powers
              ↓
┌─────────────────────────────────────────┐
│         SITE EDITOR                     │
│   (Primary User Interface)              │
├─────────────────────────────────────────┤
│ ✓ Visual color selection               │
│ ✓ Typography customization              │
│ ✓ Block-level styling                   │
│ ✓ Template editing                      │
└─────────────────────────────────────────┘

Clear Separation of Concerns

theme.json + Site Editor handle:

  • ✅ Design choices (colors, fonts, spacing)
  • ✅ Block settings
  • ✅ Per-page customization
  • ✅ Visual, drag-and-drop interface
  • PRIMARY interface for end users

Theme Options Page handles:

  • ✅ Features theme.json CAN’T do
  • ✅ Advanced technical settings
  • ✅ Performance optimization
  • ✅ Third-party integrations
  • ADVANCED interface for power users

What We’ll Build Today

In this episode, we’ll create:

  1. Enhanced theme.json – Professional design system
  2. Smart Theme Options Page – Only features theme.json can’t handle:
    • Google Fonts Integration (loading fonts)
    • Performance Settings (optimization)
    • Feature Toggles (enable/disable features)
    • Integrations (analytics, custom scripts)
    • Advanced Options (custom CSS, developer tools)

Part 1: Building a Professional theme.json

Let’s start by enhancing our theme.json with a complete, professional design system.

Updated theme.json

Replace your current theme.json with this professional version:

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 2,
  "customTemplates": [
    {
      "name": "blocks-demo",
      "title": "Blocks Demo",
      "postTypes": ["page"]
    },
    {
      "name": "blog",
      "title": "Custom Blog",
      "postTypes": ["page"]
    }
  ],
  "settings": {
    "appearanceTools": true,
    "useRootPaddingAwareAlignments": true,
    "color": {
      "custom": true,
      "customDuotone": true,
      "customGradient": true,
      "defaultGradients": false,
      "defaultPalette": false,
      "palette": [
        {
          "slug": "primary",
          "color": "#1A73E8",
          "name": "Primary"
        },
        {
          "slug": "secondary",
          "color": "#E91E63",
          "name": "Secondary"
        },
        {
          "slug": "tertiary",
          "color": "#9C27B0",
          "name": "Tertiary"
        },
        {
          "slug": "neutral-100",
          "color": "#FFFFFF",
          "name": "Neutral 100"
        },
        {
          "slug": "neutral-200",
          "color": "#F5F5F5",
          "name": "Neutral 200"
        },
        {
          "slug": "neutral-300",
          "color": "#E0E0E0",
          "name": "Neutral 300"
        },
        {
          "slug": "neutral-700",
          "color": "#424242",
          "name": "Neutral 700"
        },
        {
          "slug": "neutral-900",
          "color": "#111111",
          "name": "Neutral 900"
        },
        {
          "slug": "success",
          "color": "#4CAF50",
          "name": "Success"
        },
        {
          "slug": "warning",
          "color": "#FF9800",
          "name": "Warning"
        },
        {
          "slug": "error",
          "color": "#F44336",
          "name": "Error"
        },
        {
          "slug": "info",
          "color": "#2196F3",
          "name": "Info"
        }
      ],
      "gradients": [
        {
          "slug": "primary-gradient",
          "gradient": "linear-gradient(135deg, #1A73E8 0%, #9C27B0 100%)",
          "name": "Primary Gradient"
        },
        {
          "slug": "warm-gradient",
          "gradient": "linear-gradient(135deg, #E91E63 0%, #FF9800 100%)",
          "name": "Warm Gradient"
        }
      ]
    },
    "spacing": {
      "padding": true,
      "margin": true,
      "units": ["px", "em", "rem", "%", "vh", "vw"],
      "spacingScale": {
        "steps": 0
      },
      "spacingSizes": [
        {
          "slug": "xs",
          "size": "0.5rem",
          "name": "XSmall"
        },
        {
          "slug": "sm",
          "size": "1rem",
          "name": "Small"
        },
        {
          "slug": "md",
          "size": "1.5rem",
          "name": "Medium"
        },
        {
          "slug": "lg",
          "size": "2rem",
          "name": "Large"
        },
        {
          "slug": "xl",
          "size": "3rem",
          "name": "XLarge"
        },
        {
          "slug": "2xl",
          "size": "4rem",
          "name": "2XLarge"
        }
      ]
    },
    "typography": {
      "customFontSize": true,
      "fontStyle": true,
      "fontWeight": true,
      "letterSpacing": true,
      "textDecoration": true,
      "textTransform": true,
      "dropCap": true,
      "fluid": true,
      "fontSizes": [
        {
          "slug": "xs",
          "size": "0.75rem",
          "name": "XSmall",
          "fluid": false
        },
        {
          "slug": "sm",
          "size": "0.875rem",
          "name": "Small",
          "fluid": false
        },
        {
          "slug": "base",
          "size": "1rem",
          "name": "Base",
          "fluid": false
        },
        {
          "slug": "md",
          "size": "1.125rem",
          "name": "Medium",
          "fluid": {
            "min": "1rem",
            "max": "1.125rem"
          }
        },
        {
          "slug": "lg",
          "size": "1.25rem",
          "name": "Large",
          "fluid": {
            "min": "1.125rem",
            "max": "1.25rem"
          }
        },
        {
          "slug": "xl",
          "size": "1.5rem",
          "name": "XLarge",
          "fluid": {
            "min": "1.25rem",
            "max": "1.5rem"
          }
        },
        {
          "slug": "2xl",
          "size": "2rem",
          "name": "2XLarge",
          "fluid": {
            "min": "1.5rem",
            "max": "2rem"
          }
        },
        {
          "slug": "3xl",
          "size": "2.5rem",
          "name": "3XLarge",
          "fluid": {
            "min": "2rem",
            "max": "2.5rem"
          }
        },
        {
          "slug": "4xl",
          "size": "3rem",
          "name": "4XLarge",
          "fluid": {
            "min": "2.5rem",
            "max": "3rem"
          }
        }
      ],
      "fontFamilies": [
        {
          "fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
          "slug": "system-sans",
          "name": "System Sans-serif"
        },
        {
          "fontFamily": "Georgia, 'Times New Roman', Times, serif",
          "slug": "system-serif",
          "name": "System Serif"
        },
        {
          "fontFamily": "'Courier New', Courier, monospace",
          "slug": "system-mono",
          "name": "System Monospace"
        }
      ]
    },
    "layout": {
      "contentSize": "800px",
      "wideSize": "1200px"
    },
    "border": {
      "color": true,
      "radius": true,
      "style": true,
      "width": true
    },
    "shadow": {
      "defaultPresets": false,
      "presets": [
        {
          "slug": "sm",
          "shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
          "name": "Small"
        },
        {
          "slug": "md",
          "shadow": "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
          "name": "Medium"
        },
        {
          "slug": "lg",
          "shadow": "0 10px 15px -3px rgba(0, 0, 0, 0.1)",
          "name": "Large"
        }
      ]
    }
  },
  "styles": {
    "color": {
      "background": "var(--wp--preset--color--neutral-100)",
      "text": "var(--wp--preset--color--neutral-900)"
    },
    "typography": {
      "fontFamily": "var(--wp--preset--font-family--system-sans)",
      "fontSize": "var(--wp--preset--font-size--base)",
      "lineHeight": "1.6"
    },
    "spacing": {
      "padding": {
        "top": "0",
        "right": "var(--wp--preset--spacing--md)",
        "bottom": "0",
        "left": "var(--wp--preset--spacing--md)"
      }
    },
    "elements": {
      "link": {
        "color": {
          "text": "var(--wp--preset--color--primary)"
        },
        ":hover": {
          "color": {
            "text": "var(--wp--preset--color--secondary)"
          }
        }
      },
      "h1": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--4-xl)",
          "lineHeight": "1.2",
          "fontWeight": "700"
        },
        "spacing": {
          "margin": {
            "top": "0",
            "bottom": "var(--wp--preset--spacing--md)"
          }
        }
      },
      "h2": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--3-xl)",
          "lineHeight": "1.3",
          "fontWeight": "700"
        },
        "spacing": {
          "margin": {
            "top": "var(--wp--preset--spacing--lg)",
            "bottom": "var(--wp--preset--spacing--sm)"
          }
        }
      },
      "h3": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--2-xl)",
          "lineHeight": "1.4",
          "fontWeight": "600"
        }
      },
      "h4": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--xl)",
          "lineHeight": "1.4",
          "fontWeight": "600"
        }
      },
      "h5": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--lg)",
          "fontWeight": "600"
        }
      },
      "h6": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--md)",
          "fontWeight": "600"
        }
      },
      "button": {
        "border": {
          "radius": "4px"
        },
        "color": {
          "background": "var(--wp--preset--color--primary)",
          "text": "var(--wp--preset--color--neutral-100)"
        },
        "spacing": {
          "padding": {
            "top": "0.75rem",
            "right": "1.5rem",
            "bottom": "0.75rem",
            "left": "1.5rem"
          }
        },
        "typography": {
          "fontWeight": "600"
        },
        ":hover": {
          "color": {
            "background": "var(--wp--preset--color--secondary)"
          }
        }
      }
    },
    "blocks": {
      "core/site-title": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--2-xl)",
          "fontWeight": "700"
        }
      },
      "core/navigation": {
        "typography": {
          "fontSize": "var(--wp--preset--font-size--base)"
        }
      }
    }
  }
}

Why This theme.json is Professional

1. Complete Design System

  • 12 carefully chosen colors (brand + neutrals + semantic)
  • 9 font sizes with fluid typography
  • 6 spacing sizes following a scale
  • Shadow presets for depth

2. Accessibility

  • High contrast neutral colors
  • Proper color naming
  • Semantic color meanings

3. Flexibility

  • Multiple font families for different use cases
  • Comprehensive spacing options
  • Border and shadow utilities

4. Performance

  • Disables default WordPress palettes (reduces CSS)
  • Uses CSS custom properties efficiently
  • Optimized for modern browsers

5. User-Friendly

  • Clear, descriptive names
  • Logical organization
  • Ready for Site Editor

Part 2: Theme Options System Architecture

Now let’s build the Theme Options page that handles what theme.json CAN’T do.

File Structure

versana/
├── functions.php
├── inc/
│   ├── theme-options/
│   │   ├── options-init.php (Initialization)
│   │   ├── options-defaults.php (Default values)
│   │   ├── options-sanitize.php (Security)
│   │   ├── options-page.php (Admin interface)
│   │   ├── options-google-fonts.php (Font loading)
│   │   └── options-output.php (Frontend output)
├── assets/
│   ├── css/
│   │   └── admin.css
│   └── js/
│       └── admin.js

Step 1: Create Default Options

Create: inc/theme-options/options-defaults.php

<?php
/**
 * Versana Default Theme Options
 *
 * Defines defaults for ADVANCED features only.
 * Design choices (colors, typography) are in theme.json.
 *
 * @package Versana
 * @since 1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Get default theme options
 *
 * These are ADVANCED settings that theme.json cannot handle.
 *
 * @return array Default options array
 */
function versana_get_default_options() {
    $defaults = array(
        // Google Fonts Tab
        'google_fonts_enabled'     => false,
        'heading_font_google'      => '',
        'body_font_google'         => '',
        'preload_fonts'            => true,
        'font_display_swap'        => true,
        
        // Performance Tab
        'lazy_load_images'         => true,
        'preload_critical_fonts'   => true,
        'disable_emojis'           => false,
        'disable_embeds'           => false,
        'remove_query_strings'     => false,
        
        // Features Tab
        'enable_breadcrumbs'       => false,
        'enable_reading_time'      => false,
        'enable_share_buttons'     => false,
        'enable_toc'               => false,
        'enable_sticky_header'     => false,
        
        // Integrations Tab
        'google_analytics_id'      => '',
        'facebook_pixel_id'        => '',
        'google_tag_manager_id'    => '',
        'header_scripts'           => '',
        'footer_scripts'           => '',
        
        // Advanced Tab
        'custom_css'               => '',
        'enable_developer_mode'    => false,
        'disable_gutenberg_css'    => false,
    );
    
    /**
     * Filter default theme options
     *
     * Allows child themes to modify defaults.
     *
     * @param array $defaults Default options
     */
    return apply_filters( 'versana_default_options', $defaults );
}

/**
 * Get a single default option value
 *
 * @param string $key Option key
 * @return mixed Default value or null
 */
function versana_get_default_option( $key ) {
    $defaults = versana_get_default_options();
    return isset( $defaults[ $key ] ) ? $defaults[ $key ] : null;
}

Step 2: Create Initialization File

Create: inc/theme-options/options-init.php

<?php
/**
 * Versana Theme Options Initialization
 *
 * Core functions for theme options system.
 *
 * @package Versana
 * @since 1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Get theme option value
 *
 * Main function to retrieve option values throughout the theme.
 *
 * @param string $key Option key
 * @param mixed $default Default value if option doesn't exist
 * @return mixed Option value
 */
function versana_get_option( $key, $default = null ) {
    $options = get_option( 'versana_theme_options', array() );
    
    if ( isset( $options[ $key ] ) ) {
        return $options[ $key ];
    }
    
    return $default !== null ? $default : versana_get_default_option( $key );
}

/**
 * Get all theme options
 *
 * @return array Complete options array
 */
function versana_get_all_options() {
    $saved_options = get_option( 'versana_theme_options', array() );
    $defaults = versana_get_default_options();
    return wp_parse_args( $saved_options, $defaults );
}

/**
 * Update a single theme option
 *
 * @param string $key Option key
 * @param mixed $value New value
 * @return bool True if successful
 */
function versana_update_option( $key, $value ) {
    $options = get_option( 'versana_theme_options', array() );
    $options[ $key ] = $value;
    return update_option( 'versana_theme_options', $options );
}

/**
 * Register theme settings with WordPress
 */
function versana_register_theme_settings() {
    register_setting(
        'versana_options',
        'versana_theme_options',
        array(
            'type'              => 'array',
            'sanitize_callback' => 'versana_sanitize_options',
            'default'           => versana_get_default_options(),
        )
    );
}
add_action( 'admin_init', 'versana_register_theme_settings' );

/**
 * Enqueue admin assets
 *
 * @param string $hook Current admin page
 */
function versana_enqueue_admin_assets( $hook ) {
    if ( 'appearance_page_versana-options' !== $hook ) {
        return;
    }
    
    wp_enqueue_style( 'wp-color-picker' );
    wp_enqueue_script( 'wp-color-picker' );
    
    wp_enqueue_style(
        'versana-admin',
        get_template_directory_uri() . '/assets/css/admin.css',
        array(),
        '1.0.0'
    );
    
    wp_enqueue_script(
        'versana-admin',
        get_template_directory_uri() . '/assets/js/admin.js',
        array( 'jquery', 'wp-color-picker' ),
        '1.0.0',
        true
    );
}
add_action( 'admin_enqueue_scripts', 'versana_enqueue_admin_assets' );

Step 3: Create Sanitization File

Create: inc/theme-options/options-sanitize.php

<?php
/**
 * Versana Theme Options Sanitization
 *
 * SECURITY CRITICAL: All user input must be sanitized.
 *
 * @package Versana
 * @since 1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Sanitize all theme options
 *
 * @param array $input Raw input from form
 * @return array Sanitized options
 */
function versana_sanitize_options( $input ) {
    $sanitized = array();
    
    // Boolean fields (checkboxes)
    $boolean_fields = array(
        'google_fonts_enabled',
        'preload_fonts',
        'font_display_swap',
        'lazy_load_images',
        'preload_critical_fonts',
        'disable_emojis',
        'disable_embeds',
        'remove_query_strings',
        'enable_breadcrumbs',
        'enable_reading_time',
        'enable_share_buttons',
        'enable_toc',
        'enable_sticky_header',
        'enable_developer_mode',
        'disable_gutenberg_css',
    );
    
    foreach ( $boolean_fields as $field ) {
        $sanitized[ $field ] = isset( $input[ $field ] ) ? (bool) $input[ $field ] : false;
    }
    
    // Text fields
    $text_fields = array(
        'heading_font_google',
        'body_font_google',
        'google_analytics_id',
        'facebook_pixel_id',
        'google_tag_manager_id',
    );
    
    foreach ( $text_fields as $field ) {
        if ( isset( $input[ $field ] ) ) {
            $sanitized[ $field ] = sanitize_text_field( $input[ $field ] );
        }
    }
    
    // Custom CSS
    if ( isset( $input['custom_css'] ) ) {
        $sanitized['custom_css'] = wp_strip_all_tags( $input['custom_css'] );
    }
    
    // Scripts (only for administrators)
    if ( current_user_can( 'unfiltered_html' ) ) {
        if ( isset( $input['header_scripts'] ) ) {
            $sanitized['header_scripts'] = $input['header_scripts'];
        }
        if ( isset( $input['footer_scripts'] ) ) {
            $sanitized['footer_scripts'] = $input['footer_scripts'];
        }
    }
    
    /**
     * Filter sanitized options
     *
     * @param array $sanitized Sanitized options
     * @param array $input Raw input
     */
    return apply_filters( 'versana_sanitize_options', $sanitized, $input );
}

/**
 * Validate Google Analytics ID format
 *
 * @param string $id Analytics ID
 * @return bool True if valid
 */
function versana_validate_analytics_id( $id ) {
    return preg_match( '/^(UA|G)-[0-9]+-[0-9]+$/', $id ) || preg_match( '/^G-[A-Z0-9]+$/', $id );
}

Step 4: Create Admin Page

Create: inc/theme-options/options-page.php

<?php
/**
 * Versana Theme Options Admin Page
 *
 * Renders the admin interface for ADVANCED settings only.
 * Design customization happens in Site Editor.
 *
 * @package Versana
 * @since 1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Add theme options page to admin menu
 */
function versana_add_theme_options_page() {
    add_theme_page(
        __( 'Versana Options', 'versana' ),
        __( 'Theme Options', 'versana' ),
        'edit_theme_options',
        'versana-options',
        'versana_render_options_page',
        2
    );
}
add_action( 'admin_menu', 'versana_add_theme_options_page' );

/**
 * Render the main options page
 */
function versana_render_options_page() {
    if ( ! current_user_can( 'edit_theme_options' ) ) {
        wp_die( __( 'You do not have sufficient permissions to access this page.', 'versana' ) );
    }
    
    $active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( $_GET['tab'] ) : 'fonts';
    
    $valid_tabs = array( 'fonts', 'performance', 'features', 'integrations', 'advanced' );
    if ( ! in_array( $active_tab, $valid_tabs, true ) ) {
        $active_tab = 'fonts';
    }
    
    ?>
    <div class="wrap versana-options-wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        
        <div class="versana-options-header">
            <p class="description">
                <?php esc_html_e( 'Configure advanced features and settings. For design customization (colors, typography, spacing), use the Site Editor under Appearance → Editor.', 'versana' ); ?>
            </p>
            
            <a href="<?php echo esc_url( admin_url( 'site-editor.php' ) ); ?>" class="button button-primary">
                <?php esc_html_e( 'Open Site Editor', 'versana' ); ?>
            </a>
        </div>
        
        <?php settings_errors(); ?>
        
        <h2 class="nav-tab-wrapper">
            <a href="?page=versana-options&tab=fonts" 
               class="nav-tab <?php echo $active_tab === 'fonts' ? 'nav-tab-active' : ''; ?>">
                <span class="dashicons dashicons-editor-textcolor"></span>
                <?php esc_html_e( 'Google Fonts', 'versana' ); ?>
            </a>
            <a href="?page=versana-options&tab=performance" 
               class="nav-tab <?php echo $active_tab === 'performance' ? 'nav-tab-active' : ''; ?>">
                <span class="dashicons dashicons-performance"></span>
                <?php esc_html_e( 'Performance', 'versana' ); ?>
            </a>
            <a href="?page=versana-options&tab=features" 
               class="nav-tab <?php echo $active_tab === 'features' ? 'nav-tab-active' : ''; ?>">
                <span class="dashicons dashicons-admin-plugins"></span>
                <?php esc_html_e( 'Features', 'versana' ); ?>
            </a>
            <a href="?page=versana-options&tab=integrations" 
               class="nav-tab <?php echo $active_tab === 'integrations' ? 'nav-tab-active' : ''; ?>">
                <span class="dashicons dashicons-admin-links"></span>
                <?php esc_html_e( 'Integrations', 'versana' ); ?>
            </a>
            <a href="?page=versana-options&tab=advanced" 
               class="nav-tab <?php echo $active_tab === 'advanced' ? 'nav-tab-active' : ''; ?>">
                <span class="dashicons dashicons-admin-generic"></span>
                <?php esc_html_e( 'Advanced', 'versana' ); ?>
            </a>
        </h2>
        
        <form method="post" action="options.php">
            <?php
            settings_fields( 'versana_options' );
            
            switch ( $active_tab ) {
                case 'performance':
                    versana_render_performance_tab();
                    break;
                case 'features':
                    versana_render_features_tab();
                    break;
                case 'integrations':
                    versana_render_integrations_tab();
                    break;
                case 'advanced':
                    versana_render_advanced_tab();
                    break;
                case 'fonts':
                default:
                    versana_render_fonts_tab();
                    break;
            }
            
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

/**
 * Render Google Fonts tab
 */
function versana_render_fonts_tab() {
    ?>
    <div class="versana-tab-content">
        <h2><?php esc_html_e( 'Google Fonts Integration', 'versana' ); ?></h2>
        <p class="description">
            <?php esc_html_e( 'Load custom fonts from Google Fonts. Font selection will be added in the next episode.', 'versana' ); ?>
        </p>
        
        <table class="form-table" role="presentation">
            <tbody>
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Enable Google Fonts', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[google_fonts_enabled]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'google_fonts_enabled' ), true ); ?> />
                            <?php esc_html_e( 'Load fonts from Google Fonts', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Enable this to use Google Fonts instead of system fonts.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <label for="heading_font_google">
                            <?php esc_html_e( 'Heading Font', 'versana' ); ?>
                        </label>
                    </th>
                    <td>
                        <input type="text" 
                               id="heading_font_google" 
                               name="versana_theme_options[heading_font_google]" 
                               value="<?php echo esc_attr( versana_get_option( 'heading_font_google' ) ); ?>" 
                               class="regular-text" 
                               placeholder="<?php esc_attr_e( 'e.g., Inter', 'versana' ); ?>" />
                        <p class="description">
                            <?php esc_html_e( 'Google Font name for headings. We\'ll add a font picker in Episode 20.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <label for="body_font_google">
                            <?php esc_html_e( 'Body Font', 'versana' ); ?>
                        </label>
                    </th>
                    <td>
                        <input type="text" 
                               id="body_font_google" 
                               name="versana_theme_options[body_font_google]" 
                               value="<?php echo esc_attr( versana_get_option( 'body_font_google' ) ); ?>" 
                               class="regular-text" 
                               placeholder="<?php esc_attr_e( 'e.g., Roboto', 'versana' ); ?>" />
                        <p class="description">
                            <?php esc_html_e( 'Google Font name for body text.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Font Display', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[font_display_swap]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'font_display_swap' ), true ); ?> />
                            <?php esc_html_e( 'Use font-display: swap', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Prevents invisible text while fonts load. Recommended for performance.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Preload Fonts', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[preload_fonts]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'preload_fonts' ), true ); ?> />
                            <?php esc_html_e( 'Preload critical font files', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Improves performance by loading fonts earlier. Recommended.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <?php
}

/**
 * Render Performance tab
 */
function versana_render_performance_tab() {
    ?>
    <div class="versana-tab-content">
        <h2><?php esc_html_e( 'Performance Optimization', 'versana' ); ?></h2>
        <p class="description">
            <?php esc_html_e( 'Optimize your site for speed and performance.', 'versana' ); ?>
        </p>
        
        <table class="form-table" role="presentation">
            <tbody>
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Image Optimization', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[lazy_load_images]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'lazy_load_images' ), true ); ?> />
                            <?php esc_html_e( 'Enable lazy loading for images', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Images load only when they\'re about to enter the viewport.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Font Loading', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[preload_critical_fonts]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'preload_critical_fonts' ), true ); ?> />
                            <?php esc_html_e( 'Preload critical fonts', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Loads essential fonts earlier for faster text rendering.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Disable Emojis', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[disable_emojis]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'disable_emojis' ), true ); ?> />
                            <?php esc_html_e( 'Remove WordPress emoji scripts', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Reduces HTTP requests. Modern browsers support emojis natively.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Disable Embeds', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[disable_embeds]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'disable_embeds' ), true ); ?> />
                            <?php esc_html_e( 'Remove WordPress embed scripts', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Disables oEmbed discovery. Only disable if you don\'t use embeds.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Query Strings', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[remove_query_strings]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'remove_query_strings' ), true ); ?> />
                            <?php esc_html_e( 'Remove query strings from static resources', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Some caching systems cache better without query strings.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <?php
}

/**
 * Render Features tab
 */
function versana_render_features_tab() {
    ?>
    <div class="versana-tab-content">
        <h2><?php esc_html_e( 'Theme Features', 'versana' ); ?></h2>
        <p class="description">
            <?php esc_html_e( 'Enable or disable theme features. These will be built in future episodes.', 'versana' ); ?>
        </p>
        
        <table class="form-table" role="presentation">
            <tbody>
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Breadcrumbs', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[enable_breadcrumbs]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'enable_breadcrumbs' ), true ); ?> />
                            <?php esc_html_e( 'Show breadcrumb navigation', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Displays breadcrumb trail for better navigation and SEO.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Reading Time', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[enable_reading_time]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'enable_reading_time' ), true ); ?> />
                            <?php esc_html_e( 'Show estimated reading time', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Displays estimated reading time for posts and pages.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Share Buttons', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[enable_share_buttons]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'enable_share_buttons' ), true ); ?> />
                            <?php esc_html_e( 'Show social share buttons', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Adds social sharing buttons to posts.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Table of Contents', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[enable_toc]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'enable_toc' ), true ); ?> />
                            <?php esc_html_e( 'Auto-generate table of contents', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Automatically creates TOC from headings in long posts.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Sticky Header', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[enable_sticky_header]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'enable_sticky_header' ), true ); ?> />
                            <?php esc_html_e( 'Make header sticky on scroll', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Header stays visible when scrolling down the page.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <?php
}

/**
 * Render Integrations tab
 */
function versana_render_integrations_tab() {
    ?>
    <div class="versana-tab-content">
        <h2><?php esc_html_e( 'Third-Party Integrations', 'versana' ); ?></h2>
        <p class="description">
            <?php esc_html_e( 'Connect your site with third-party services.', 'versana' ); ?>
        </p>
        
        <table class="form-table" role="presentation">
            <tbody>
                <tr>
                    <th scope="row">
                        <label for="google_analytics_id">
                            <?php esc_html_e( 'Google Analytics', 'versana' ); ?>
                        </label>
                    </th>
                    <td>
                        <input type="text" 
                               id="google_analytics_id" 
                               name="versana_theme_options[google_analytics_id]" 
                               value="<?php echo esc_attr( versana_get_option( 'google_analytics_id' ) ); ?>" 
                               class="regular-text" 
                               placeholder="<?php esc_attr_e( 'G-XXXXXXXXXX or UA-XXXXXX-X', 'versana' ); ?>" />
                        <p class="description">
                            <?php esc_html_e( 'Enter your Google Analytics Measurement ID or Tracking ID.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <label for="google_tag_manager_id">
                            <?php esc_html_e( 'Google Tag Manager', 'versana' ); ?>
                        </label>
                    </th>
                    <td>
                        <input type="text" 
                               id="google_tag_manager_id" 
                               name="versana_theme_options[google_tag_manager_id]" 
                               value="<?php echo esc_attr( versana_get_option( 'google_tag_manager_id' ) ); ?>" 
                               class="regular-text" 
                               placeholder="<?php esc_attr_e( 'GTM-XXXXXX', 'versana' ); ?>" />
                        <p class="description">
                            <?php esc_html_e( 'Enter your Google Tag Manager container ID.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <label for="facebook_pixel_id">
                            <?php esc_html_e( 'Facebook Pixel', 'versana' ); ?>
                        </label>
                    </th>
                    <td>
                        <input type="text" 
                               id="facebook_pixel_id" 
                               name="versana_theme_options[facebook_pixel_id]" 
                               value="<?php echo esc_attr( versana_get_option( 'facebook_pixel_id' ) ); ?>" 
                               class="regular-text" 
                               placeholder="<?php esc_attr_e( 'XXXXXXXXXXXXXXXX', 'versana' ); ?>" />
                        <p class="description">
                            <?php esc_html_e( 'Enter your Facebook Pixel ID for conversion tracking.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <?php if ( current_user_can( 'unfiltered_html' ) ) : ?>
                <tr>
                    <th scope="row">
                        <label for="header_scripts">
                            <?php esc_html_e( 'Header Scripts', 'versana' ); ?>
                        </label>
                    </th>
                    <td>
                        <textarea id="header_scripts" 
                                  name="versana_theme_options[header_scripts]" 
                                  rows="5" 
                                  class="large-text code"><?php echo esc_textarea( versana_get_option( 'header_scripts' ) ); ?></textarea>
                        <p class="description">
                            <?php esc_html_e( 'Scripts added here will be inserted into the <head> section. Include <script> tags.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <label for="footer_scripts">
                            <?php esc_html_e( 'Footer Scripts', 'versana' ); ?>
                        </label>
                    </th>
                    <td>
                        <textarea id="footer_scripts" 
                                  name="versana_theme_options[footer_scripts]" 
                                  rows="5" 
                                  class="large-text code"><?php echo esc_textarea( versana_get_option( 'footer_scripts' ) ); ?></textarea>
                        <p class="description">
                            <?php esc_html_e( 'Scripts added here will be inserted before </body>. Include <script> tags.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                <?php else : ?>
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Custom Scripts', 'versana' ); ?>
                    </th>
                    <td>
                        <p class="description">
                            <?php esc_html_e( 'Custom script fields are only available to administrators for security reasons.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                <?php endif; ?>
            </tbody>
        </table>
    </div>
    <?php
}

/**
 * Render Advanced tab
 */
function versana_render_advanced_tab() {
    ?>
    <div class="versana-tab-content">
        <h2><?php esc_html_e( 'Advanced Options', 'versana' ); ?></h2>
        <p class="description">
            <?php esc_html_e( 'Advanced settings for developers and power users.', 'versana' ); ?>
        </p>
        
        <table class="form-table" role="presentation">
            <tbody>
                <tr>
                    <th scope="row">
                        <label for="custom_css">
                            <?php esc_html_e( 'Custom CSS', 'versana' ); ?>
                        </label>
                    </th>
                    <td>
                        <textarea id="custom_css" 
                                  name="versana_theme_options[custom_css]" 
                                  rows="10" 
                                  class="large-text code"><?php echo esc_textarea( versana_get_option( 'custom_css' ) ); ?></textarea>
                        <p class="description">
                            <?php esc_html_e( 'Add custom CSS that will be applied to your site. Do not include <style> tags.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Developer Mode', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[enable_developer_mode]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'enable_developer_mode' ), true ); ?> />
                            <?php esc_html_e( 'Enable developer mode', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'Shows additional debugging information. Only enable during development.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
                
                <tr>
                    <th scope="row">
                        <?php esc_html_e( 'Gutenberg CSS', 'versana' ); ?>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox" 
                                   name="versana_theme_options[disable_gutenberg_css]" 
                                   value="1" 
                                   <?php checked( versana_get_option( 'disable_gutenberg_css' ), true ); ?> />
                            <?php esc_html_e( 'Disable default Gutenberg block CSS', 'versana' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'For advanced users who want complete control over block styles.', 'versana' ); ?>
                        </p>
                    </td>
                </tr>
            </tbody>
        </table>
        
        <div class="versana-reset-section">
            <h3><?php esc_html_e( 'Reset Options', 'versana' ); ?></h3>
            <p class="description">
                <?php esc_html_e( 'Reset all theme options to their default values. This action cannot be undone.', 'versana' ); ?>
            </p>
            <button type="button" class="button button-secondary versana-reset-options">
                <?php esc_html_e( 'Reset to Defaults', 'versana' ); ?>
            </button>
        </div>
    </div>
    <?php
}

Step 5: Create Frontend Output

Create: `inc/theme-options/options-output.php`

<?php
/**
 * Versana Theme Options Frontend Output
 *
 * Outputs theme option values to the frontend.
 *
 * @package Versana
 * @since 1.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Output custom CSS to head
 */
function versana_output_custom_css() {
    $custom_css = versana_get_option( 'custom_css' );
    
    if ( empty( $custom_css ) ) {
        return;
    }
    
    ?>
    <style id="versana-custom-css" type="text/css">
        <?php echo wp_strip_all_tags( $custom_css ); ?>
    </style>
    <?php
}
add_action( 'wp_head', 'versana_output_custom_css', 99 );

/**
 * Output Google Analytics tracking code
 */
function versana_output_google_analytics() {
    $analytics_id = versana_get_option( 'google_analytics_id' );
    
    if ( empty( $analytics_id ) || ! versana_validate_analytics_id( $analytics_id ) ) {
        return;
    }
    
    // Google Analytics 4 (G-XXXXXXXXXX)
    if ( strpos( $analytics_id, 'G-' ) === 0 ) {
        ?>
        <!-- Google Analytics -->
        <script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo esc_attr( $analytics_id ); ?>"></script>
        <script>
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '<?php echo esc_js( $analytics_id ); ?>');
        </script>
        <?php
    }
    // Universal Analytics (UA-XXXXXXX-X)
    else {
        ?>
        <!-- Google Analytics -->
        <script async src="https://www.googletagmanager.com/analytics.js"></script>
        <script>
            window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
            ga('create', '<?php echo esc_js( $analytics_id ); ?>', 'auto');
            ga('send', 'pageview');
        </script>
        <?php
    }
}
add_action( 'wp_head', 'versana_output_google_analytics', 10 );

/**
 * Output Google Tag Manager head code
 */
function versana_output_gtm_head() {
    $gtm_id = versana_get_option( 'google_tag_manager_id' );
    
    if ( empty( $gtm_id ) ) {
        return;
    }
    
    ?>
    <!-- Google Tag Manager -->
    <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','<?php echo esc_js( $gtm_id ); ?>');</script>
    <!-- End Google Tag Manager -->
    <?php
}
add_action( 'wp_head', 'versana_output_gtm_head', 5 );

/**
 * Output Google Tag Manager body code
 */
function versana_output_gtm_body() {
    $gtm_id = versana_get_option( 'google_tag_manager_id' );
    
    if ( empty( $gtm_id ) ) {
        return;
    }
    
    ?>
    <!-- Google Tag Manager (noscript) -->
    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo esc_attr( $gtm_id ); ?>"
    height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
    <!-- End Google Tag Manager (noscript) -->
    <?php
}
add_action( 'wp_body_open', 'versana_output_gtm_body' );

/**
 * Output Facebook Pixel code
 */
function versana_output_facebook_pixel() {
    $pixel_id = versana_get_option( 'facebook_pixel_id' );
    
    if ( empty( $pixel_id ) ) {
        return;
    }
    
    ?>
    <!-- Facebook Pixel Code -->
    <script>
    !function(f,b,e,v,n,t,s)
    {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
    n.callMethod.apply(n,arguments):n.queue.push(arguments)};
    if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
    n.queue=[];t=b.createElement(e);t.async=!0;
    t.src=v;s=b.getElementsByTagName(e)[0];
    s.parentNode.insertBefore(t,s)}(window, document,'script',
    'https://connect.facebook.net/en_US/fbevents.js');
    fbq('init', '<?php echo esc_js( $pixel_id ); ?>');
    fbq('track', 'PageView');
    </script>
    <noscript><img height="1" width="1" style="display:none"
    src="https://www.facebook.com/tr?id=<?php echo esc_attr( $pixel_id ); ?>&ev=PageView&noscript=1"
    /></noscript>
    <!-- End Facebook Pixel Code -->
    <?php
}
add_action( 'wp_head', 'versana_output_facebook_pixel', 10 );

/**
 * Output header scripts
 */
function versana_output_header_scripts() {
    $header_scripts = versana_get_option( 'header_scripts' );
    
    if ( empty( $header_scripts ) ) {
        return;
    }
    
    echo $header_scripts;
}
add_action( 'wp_head', 'versana_output_header_scripts', 100 );

/**
 * Output footer scripts
 */
function versana_output_footer_scripts() {
    $footer_scripts = versana_get_option( 'footer_scripts' );
    
    if ( empty( $footer_scripts ) ) {
        return;
    }
    
    echo $footer_scripts;
}
add_action( 'wp_footer', 'versana_output_footer_scripts', 100 );

/**
 * Disable emojis if option is enabled
 */
function versana_disable_emojis() {
    if ( ! versana_get_option( 'disable_emojis' ) ) {
        return;
    }
    
    remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
    remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
    remove_action( 'wp_print_styles', 'print_emoji_styles' );
    remove_action( 'admin_print_styles', 'print_emoji_styles' );
    remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
    remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
    remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
}
add_action( 'init', 'versana_disable_emojis' );

/**
 * Disable embeds if option is enabled
 */
function versana_disable_embeds() {
    if ( ! versana_get_option( 'disable_embeds' ) ) {
        return;
    }
    
    global $wp;
    $wp->public_query_vars = array_diff( $wp->public_query_vars, array( 'embed' ) );
    remove_action( 'rest_api_init', 'wp_oembed_register_route' );
    remove_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10 );
    remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
    remove_action( 'wp_head', 'wp_oembed_add_host_js' );
}
add_action( 'init', 'versana_disable_embeds', 9999 );

Step 6: Create Admin Assets

Create: assets/css/admin.css

/**
 * Versana Admin Styles
 *
 * @package Versana
 * @since 1.0.0
 */

/* Main Wrapper */
.versana-options-wrap {
    max-width: 1200px;
    margin: 20px 0;
}

/* Header Section */
.versana-options-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin: 20px 0 30px;
    padding: 20px;
    background: #f8f9fa;
    border-radius: 8px;
    border-left: 4px solid #1A73E8;
}

.versana-options-header .description {
    margin: 0;
    flex: 1;
}

.versana-options-header .button-primary {
    margin-left: 20px;
}

/* Tab Navigation */
.versana-options-wrap .nav-tab-wrapper {
    margin: 20px 0 30px 0;
    border-bottom: 1px solid #ccd0d4;
}

.versana-options-wrap .nav-tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 16px;
}

.versana-options-wrap .nav-tab .dashicons {
    font-size: 18px;
    width: 18px;
    height: 18px;
}

/* Tab Content */
.versana-tab-content {
    background: #fff;
    padding: 20px;
    border: 1px solid #c3c4c7;
    border-radius: 4px;
    margin-bottom: 20px;
}

.versana-tab-content h2 {
    margin-top: 0;
    padding-bottom: 10px;
    border-bottom: 2px solid #1A73E8;
}

.versana-tab-content > .description {
    font-size: 14px;
    margin: 10px 0 20px;
    padding: 12px;
    background: #e7f5ff;
    border-left: 4px solid #2196F3;
    border-radius: 4px;
}

/* Form Table */
.versana-options-wrap .form-table {
    margin-top: 20px;
}

.versana-options-wrap .form-table th {
    width: 250px;
    padding: 20px 10px 20px 0;
    vertical-align: top;
    font-weight: 600;
}

.versana-options-wrap .form-table td {
    padding: 15px 10px;
}

.versana-options-wrap .form-table label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
}

.versana-options-wrap .form-table input[type="checkbox"] {
    margin: 0;
}

/* Field Descriptions */
.versana-options-wrap .description {
    font-style: italic;
    color: #646970;
    margin-top: 8px;
    display: block;
    line-height: 1.5;
}

/* Input Fields */
.versana-options-wrap input[type="text"],
.versana-options-wrap input[type="number"] {
    padding: 8px 12px;
    border-radius: 4px;
}

.versana-options-wrap .regular-text {
    width: 400px;
    max-width: 100%;
}

/* Code Editor */
textarea.code {
    font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
    font-size: 13px;
    line-height: 1.6;
    background: #f6f7f7;
    border: 1px solid #c3c4c7;
    border-radius: 4px;
    padding: 12px;
}

/* Submit Button */
.versana-options-wrap .submit {
    margin-top: 30px;
    padding-top: 20px;
    border-top: 1px solid #dcdcde;
}

/* Reset Section */
.versana-reset-section {
    margin-top: 40px;
    padding: 20px;
    background: #fff3cd;
    border: 1px solid #ffc107;
    border-radius: 4px;
}

.versana-reset-section h3 {
    margin-top: 0;
    color: #856404;
}

.versana-reset-section .description {
    color: #856404;
}

.versana-reset-options {
    margin-top: 10px;
}

/* Settings Saved Message */
.versana-options-wrap .updated {
    margin: 20px 0;
}

/* Color Picker */
.wp-picker-container {
    display: inline-block;
}

/* Responsive */
@media screen and (max-width: 782px) {
    .versana-options-header {
        flex-direction: column;
        align-items: flex-start;
    }
    
    .versana-options-header .button-primary {
        margin-left: 0;
        margin-top: 15px;
    }
    
    .versana-options-wrap .form-table th,
    .versana-options-wrap .form-table td {
        display: block;
        width: 100%;
        padding: 10px 0;
    }
    
    .versana-options-wrap .form-table th {
        padding-bottom: 5px;
    }
    
    .versana-options-wrap .regular-text {
        width: 100%;
    }
}

Create: assets/js/admin.js

/**
 * Versana Admin JavaScript
 *
 * @package Versana
 * @since 1.0.0
 */

(function($) {
    'use strict';
    
    $(document).ready(function() {
        
        /**
         * Initialize WordPress Color Pickers
         */
        if (typeof $.fn.wpColorPicker !== 'undefined') {
            $('.versana-color-picker').wpColorPicker();
        }
        
        /**
         * Reset to Defaults
         */
        $('.versana-reset-options').on('click', function(e) {
            e.preventDefault();
            
            if (!confirm('Are you sure you want to reset all theme options to their default values? This action cannot be undone.')) {
                return;
            }
            
            // Submit form with reset flag
            var form = $(this).closest('form');
            $('<input>').attr({
                type: 'hidden',
                name: 'versana_reset_options',
                value: '1'
            }).appendTo(form);
            
            form.submit();
        });
        
        /**
         * Validation for analytics IDs
         */
        $('#google_analytics_id').on('blur', function() {
            var value = $(this).val().trim();
            if (value && !value.match(/^(UA|G)-[0-9]+-[0-9]+$/) && !value.match(/^G-[A-Z0-9]+$/)) {
                alert('Please enter a valid Google Analytics ID (format: G-XXXXXXXXXX or UA-XXXXXX-X)');
            }
        });
        
        /**
         * Tab switching with localStorage
         */
        $('.nav-tab').on('click', function() {
            var tab = $(this).attr('href').split('tab=')[1];
            if (tab) {
                localStorage.setItem('versana_active_tab', tab);
            }
        });
        
        // Restore active tab on page load
        var savedTab = localStorage.getItem('versana_active_tab');
        if (savedTab) {
            var tabUrl = window.location.href.split('&tab=')[0] + '&tab=' + savedTab;
            if (window.location.href.indexOf('tab=') === -1) {
                // Don't auto-navigate on first load
                localStorage.removeItem('versana_active_tab');
            }
        }
        
    });
    
})(jQuery);

Step 7: Update functions.php

Update your functions.php to load all the files:

<?php
/**
 * Versana Theme Functions
 *
 * @package Versana
 * @since 1.0.0
 */

// Theme setup
add_action( 'init', 'versana_register_pattern_categories' );
function versana_register_pattern_categories() {
    register_block_pattern_category(
        'versana-sections',
        array( 'label' => __( 'Versana Sections', 'versana' ) )
    );
}

/**
 * Load theme options system
 *
 * Files are loaded in specific order for dependencies.
 * Only admin page loads in admin area (conditional loading).
 */
$theme_options_path = get_template_directory() . '/inc/theme-options/';

// Load in order of dependency
if ( file_exists( $theme_options_path . 'options-defaults.php' ) ) {
    require_once $theme_options_path . 'options-defaults.php';
}

if ( file_exists( $theme_options_path . 'options-sanitize.php' ) ) {
    require_once $theme_options_path . 'options-sanitize.php';
}

if ( file_exists( $theme_options_path . 'options-init.php' ) ) {
    require_once $theme_options_path . 'options-init.php';
}

// Admin page (conditional - only in admin)
if ( is_admin() && file_exists( $theme_options_path . 'options-page.php' ) ) {
    require_once $theme_options_path . 'options-page.php';
}

// Frontend output
if ( file_exists( $theme_options_path . 'options-output.php' ) ) {
    require_once $theme_options_path . 'options-output.php';
}

// Google Fonts integration (will be built in Episode 20)
if ( file_exists( $theme_options_path . 'options-google-fonts.php' ) ) {
    require_once $theme_options_path . 'options-google-fonts.php';
}

Understanding the Professional Architecture

Let’s break down why this approach is superior:

1. Clear Separation of Concerns

theme.json handles:

{
  "settings": {
    "color": {
      "palette": [...] // Users choose from these
    },
    "typography": {
      "fontSizes": [...] // Users choose from these
    }
  }
}

Theme Options handles:

// Loading Google Fonts (theme.json can't do this)
'google_fonts_enabled' => true,
'heading_font_google' => 'Inter',

// Performance (theme.json can't do this)
'lazy_load_images' => true,
'preload_fonts' => true,

// Integrations (theme.json can't do this)
'google_analytics_id' => 'G-XXXXXXXXXX',

Result: No duplication, no confusion, clear purposes.

2. Future-Proof Design

This architecture aligns with WordPress’s long-term vision:

  • Site Editor is the PRIMARY customization interface
  • theme.json is the design system structure
  • Theme options provide ADVANCED features

As WordPress evolves, this architecture will adapt easily.

3. User Experience

For Beginners:

  • Use Site Editor for colors, fonts, spacing
  • Visual, drag-and-drop interface
  • No code required

For Advanced Users:

  • Use Theme Options for features and integrations
  • Technical settings in one place
  • Developer-friendly options

4. Professional Credibility

This is how top themes work:

  • Kadence: Site Editor for design, theme options for features
  • GeneratePress: Same approach
  • Blocksy: Same approach

It’s the industry standard for a reason.

Testing Your Implementation

Step 1: Verify File Structure

Ensure all files are in place:

versana/
├── theme.json (updated)
├── functions.php (updated)
├── inc/theme-options/
│   ├── options-init.php ✓
│   ├── options-defaults.php ✓
│   ├── options-sanitize.php ✓
│   ├── options-page.php ✓
│   └── options-output.php ✓
└── assets/
    ├── css/admin.css ✓
    └── js/admin.js ✓

Step 2: Access Theme Options

  1. Go to Appearance → Theme Options
  2. You should see 5 tabs with icons
  3. Header shows link to Site Editor

Step 3: Test Each Tab

Google Fonts Tab:

  1. Enable Google Fonts
  2. Enter font names (e.g., “Inter”, “Roboto”)
  3. Save changes

Performance Tab:

  1. Enable lazy loading
  2. Disable emojis
  3. Save and check frontend source code

Features Tab:

  1. Enable breadcrumbs (placeholder for now)
  2. Save changes

Integrations Tab:

  1. Enter Google Analytics ID
  2. Save and view page source
  3. You should see the tracking code

Advanced Tab:

  1. Add custom CSS
  2. Enable developer mode
  3. Save and verify output

Step 4: Verify Site Editor Integration

  1. Go to Appearance → Editor
  2. Click “Styles” → “Colors”
  3. You should see your professional color palette
  4. Click “Typography”
  5. You should see the complete font size scale

Step 5: Test Frontend Output

View your site’s source code and verify:

  • Custom CSS appears in <head>
  • Analytics tracking code is present
  • No emoji scripts if disabled
  • Clean, optimized code

What We’ve Accomplished

  • Professional theme.json with complete design system
  • Smart Theme Options page for advanced features only
  • Clear separation of design vs. features
  • Future-proof architecture aligned with WordPress vision
  • 5 organized tabs for different settings
  • Complete integrations (Analytics, GTM, Facebook Pixel)
  • Performance optimizations built-in
  • Security-first approach with proper sanitization
  • Extensibility through hooks and filters
  • Professional code structure following WordPress standards

Key Architectural Principles

1. WordPress’s Vision First

We embrace Full Site Editing:

  • theme.json for design system
  • Site Editor for customization
  • Theme options for enhancements

2. Don’t Duplicate Functionality

  • Colors → theme.json (not theme options)
  • Typography → theme.json (not theme options)
  • Spacing → theme.json (not theme options)
  • Features → Theme options (not theme.json)

3. User Experience Hierarchy

Beginner Users → Site Editor (visual, intuitive)
       ↓
Power Users → Theme Options (technical, advanced)
       ↓
Developers → Code/Hooks (maximum control)

4. Extensibility Layer

Every level can be extended:

// Child theme can modify defaults
add_filter( 'versana_default_options', 'child_modify_defaults' );

// Plugin can add custom tabs
add_action( 'versana_after_options_tabs', 'plugin_add_tab' );

// Developer can override output
add_filter( 'versana_custom_css_output', 'custom_css_function' );

What’s Coming in Episode 20

Now that we have the correct architecture, we’ll enhance it with:

  1. Google Fonts Selector
    • Browse 900+ Google Fonts
    • Live preview
    • Font pairing suggestions
    • Weight and style selection
  2. Advanced Typography System
    • Font loading optimization
    • Subset selection
    • Preload configuration
    • Display swap settings
  3. Performance Dashboard
    • PageSpeed insights integration
    • Optimization recommendations
    • Before/after comparisons
  4. Import/Export Settings
    • Backup theme options
    • Share configurations
    • Quick setup for new sites

Why This Approach Wins

Compared to Other Approaches

❌ Bad: Duplicate theme.json in Theme Options

  • Confusing for users
  • Conflicts between systems
  • Not future-proof

❌ Bad: Ignore Site Editor Entirely

  • Miss WordPress’s vision
  • Poor user experience
  • Not competitive

✅ Good: Our Approach

  • Embrace WordPress’s vision
  • Clear separation of concerns
  • Best of both worlds
  • Industry standard

Real-World Benefits

For Theme Developers:

  • Easier maintenance
  • Clear code organization
  • Future-proof architecture

For Users:

  • Intuitive design customization (Site Editor)
  • Powerful advanced features (Theme Options)
  • No confusion about where to go

For Child Themes:

  • Clear extension points
  • Can enhance either layer
  • Won’t break with updates

Best Practices We’re Following

1. Security

// Always check capabilities
if ( ! current_user_can( 'edit_theme_options' ) ) {
    wp_die();
}

// Always sanitize
$sanitized = sanitize_text_field( $input );

// Always escape
echo esc_html( $value );

// Limit powerful features
if ( current_user_can( 'unfiltered_html' ) ) {
    // Only then allow scripts
}

2. Performance

// Conditional loading
if ( is_admin() ) {
    require_once 'options-page.php';
}

// Conditional asset loading
if ( 'appearance_page_versana-options' !== $hook ) {
    return;
}

// Efficient database queries
$options = get_option( 'versana_theme_options', array() );

3. Extensibility

// Provide filters
return apply_filters( 'versana_default_options', $defaults );

// Provide actions
do_action( 'versana_before_options_save' );

// Clear naming
function versana_get_option( $key ) { }

4. User Experience

  • Clear descriptions for every option
  • Logical grouping in tabs
  • Visual feedback
  • Link to Site Editor
  • Icon-enhanced navigation

Conclusion

We’ve built the professional foundation for Versana theme that:

Aligns with WordPress’s vision for block themes
Separates design from features clearly
Provides flexibility for all user types
Follows industry standards from top themes
Is future-proof and maintainable
Extensible through hooks and filters

This is the architecture that will make Versana competitive with best-selling themes while staying true to WordPress’s Full Site Editing vision.

In Episode 20, we’ll add the Google Fonts integration with a beautiful font selector, live preview, and optimization features that will make Versana stand out!

Resources


Have questions about the architecture? Drop them in the comments!

Leave a Reply