Skip to content

Theme Architecture

BuddyX Pro uses a modern, component-based architecture with PSR-4 autoloading for organized, maintainable code.

The theme follows object-oriented principles with a modular component system. Each feature is encapsulated in its own component that implements the Component_Interface.

Key Features:

  • PSR-4 autoloading with fallback
  • Component-based architecture
  • Template tag system
  • Namespace: BuddyxPro\BuddyxPro
  • Minimum requirements: WordPress 6.5+, PHP 8.0+ (BUDDYXPRO_MINIMUM_WP_VERSION / BUDDYXPRO_MINIMUM_PHP_VERSION in functions.php)
buddyx-pro/
├── assets/ # CSS, JS, images, fonts
│ ├── css/
│ ├── js/
│ ├── images/
│ └── svg/
├── external/ # Optional third-party shim files (legacy, kept empty in 5.1.0+)
├── inc/ # Theme components (PSR-4 autoloaded)
│ ├── compatibility/ # Plugin integrations
│ │ ├── buddypress/
│ │ ├── woocommerce/
│ │ ├── learndash/
│ │ ├── fluentcart/
│ │ └── ...
│ ├── Customizer/ # WP_Customize bootstrap component
│ ├── Customizer_Framework/ # First-party field API (Field, Panel, Section, Output_Builder)
│ ├── Customizer_Settings/ # Per-section field definitions
│ ├── Tokens/ # --bx-* CSS custom-property emitter
│ ├── Color_Mode_Toggle/# Dark / light toggle
│ ├── Styles/ # CSS enqueue manifest + inline CSS
│ ├── Helpers/ # Procedural helper files (Login_Forms, Dark_Mode, ...)
│ ├── widgets/ # Custom widgets
│ ├── Component_Interface.php
│ ├── Theme.php # Main theme class
│ ├── Template_Tags.php # Template tag system
│ └── functions.php # Entry point function
├── template-parts/ # Reusable template parts
├── vendor/ # Composer autoloader
├── functions.php # Theme initialization
└── style.css # Theme stylesheet
// Setup autoloader (Composer or custom fallback)
if ( file_exists( get_template_directory() . '/vendor/autoload.php' ) ) {
require get_template_directory() . '/vendor/autoload.php';
} else {
spl_autoload_register( '_buddyxpro_autoload' );
}
// Load entry point function
require get_template_directory() . '/inc/functions.php';
// Initialize theme
call_user_func( 'BuddyxPro\BuddyxPro\buddyxpro' );

All theme components must implement Component_Interface:

namespace BuddyxPro\BuddyxPro;
interface Component_Interface {
/**
* Gets the unique identifier for the theme component.
*
* @return string Component slug.
*/
public function get_slug(): string;
/**
* Adds the action and filter hooks to integrate with WordPress.
*/
public function initialize();
}

The Theme class orchestrates all components:

namespace BuddyxPro\BuddyxPro;
class Theme {
protected $components = array();
protected \BuddyxPro\BuddyxPro\Template_Tags $template_tags;
public function __construct( array $components = array() ) {
// Register components
foreach ( $components as $component ) {
$this->components[ $component->get_slug() ] = $component;
}
}
public function initialize() {
// Initialize all components
array_walk( $this->components, function( $component ) {
$component->initialize();
});
}
}

Theme::get_default_components() (inc/Theme.php) registers these components, in this order:

  • Localization - Text domain and translations
  • Migration - Version-to-version data migrations
  • Base_Support - Core WordPress theme features
  • Editor - Block editor support
  • Accessibility - ARIA labels, skip links, theme-color AJAX
  • Image_Sizes - Custom image sizes
  • AMP - AMP support
  • PWA - Progressive Web App support
  • Comments - Comment markup and callbacks
  • Nav_Menus - Menu locations
  • Sidebars - Widget areas
  • Custom_Background - Custom background support
  • Custom_Header - Custom header support
  • Custom_Logo - Custom logo support
  • Post_Thumbnails - Featured image support
  • Customizer - WP_Customize bootstrap
  • Fonts - Font registration and Google Fonts
  • Styles - Stylesheet enqueue manifest
  • Scripts - JavaScript enqueue manifest
  • Excerpts - Excerpt handling
  • Options - Theme option helpers
  • Blocks - Gutenberg block support
  • Block_Patterns - Block pattern registration
  • Customizer_Settings - Per-section customizer field definitions
  • Starter_Content - Starter content
  • Tokens - Emits --bx-color-* / --bx-radius-* custom properties on <html> from color / dimension theme mods
  • Color_Mode_Toggle - Dark / light color-mode toggle
  • Page_Settings - Per-page settings meta box
  • Welcome - Theme welcome / onboarding screen
  • Plugin_Installer - Recommended-plugin installer

Conditional component:

// Jetpack integration (only when Jetpack is active).
if ( defined( 'JETPACK__VERSION' ) ) {
$components[] = new Jetpack\Component();
}

Customizer_Framework is not a registered component. It is a first-party static-class library (Field, Panel, Section, Output_Builder) that replaced Kirki in 5.1.0; the Customizer_Settings component consumes it. There is no Dynamic_Style component or inc/Dynamic_Style/ directory - dynamic CSS is produced by Tokens/Component.php (custom properties), Customizer_Framework/Output_Builder.php (per-field output rules), and Styles/Component.php (inline CSS at wp_head).

Plugin integrations (BuddyPress, WooCommerce, LearnDash, Dokan, FluentCart, bbPress, BuddyNext, etc.) are not components - they are procedural files loaded conditionally from functions.php (see “Plugin Integration Pattern” below).

Class namespace maps directly to file path:

Namespace: BuddyxPro\BuddyxPro\Customizer\Component
File: inc/Customizer/Component.php
Namespace: BuddyxPro\BuddyxPro\Tokens\Component
File: inc/Tokens/Component.php

If Composer is unavailable:

function _buddyxpro_autoload( $class_name ) {
$namespace = 'BuddyxPro\BuddyxPro';
if ( 0 !== strpos( $class_name, $namespace . '\\' ) ) {
return false;
}
// Convert namespace to file path
$parts = explode( '\\', substr( $class_name, strlen( $namespace . '\\' ) ) );
$path = get_template_directory() . '/inc';
foreach ( $parts as $part ) {
$path .= '/' . $part;
}
$path .= '.php';
if ( file_exists( $path ) ) {
require_once $path;
return true;
}
return false;
}
spl_autoload_register( '_buddyxpro_autoload' );

Template tags are accessible via the buddyxpro() function:

// In template files
buddyxpro()->display_header();
buddyxpro()->posted_on();
buddyxpro()->get_version();

The Template_Tags class provides magic method access:

class Template_Tags {
protected $template_tags = array();
public function __call( string $method, array $args ) {
if ( ! isset( $this->template_tags[ $method ] ) ) {
throw new BadMethodCallException(
sprintf( 'The template tag %s does not exist.', $method )
);
}
return call_user_func_array(
$this->template_tags[ $method ]['callback'],
$args
);
}
}

Components implement Templating_Component_Interface:

namespace BuddyxPro\BuddyxPro\MyComponent;
use BuddyxPro\BuddyxPro\Component_Interface;
use BuddyxPro\BuddyxPro\Templating_Component_Interface;
class Component implements Component_Interface, Templating_Component_Interface {
public function template_tags(): array {
return array(
'my_custom_tag' => array( $this, 'render_custom_tag' ),
);
}
public function render_custom_tag() {
// Template tag logic
}
}

Plugin integrations are isolated in inc/compatibility/:

inc/compatibility/
├── buddypress/
│ ├── buddypress-functions.php
│ └── README.md
├── woocommerce/
│ ├── woocommerce-functions.php
│ └── README.md
├── fluentcart/
│ ├── fluentcart-functions.php
│ ├── HOOKS-REFERENCE.md
│ └── README.md
└── learndash/
├── learndash-functions.php
└── README.md

In functions.php:

// Load WooCommerce functions
if ( class_exists( 'WooCommerce' ) ) {
require get_template_directory() . '/inc/compatibility/woocommerce/woocommerce-functions.php';
}
// Load FluentCart functions
if ( defined( 'FLUENTCART_PLUGIN_FILE_PATH' ) ) {
require get_template_directory() . '/inc/compatibility/fluentcart/fluentcart-functions.php';
}
// Load LearnDash functions
if ( class_exists( 'SFWD_LMS' ) ) {
require get_template_directory() . '/inc/compatibility/learndash/learndash-functions.php';
}
class BuddyXPro_FluentCart_Support {
private static $instance = null;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_hooks();
}
private function init_hooks() {
// Add hooks for plugin integration
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) );
add_filter( 'body_class', array( $this, 'add_body_classes' ) );
}
}
// Initialize
BuddyXPro_FluentCart_Support::get_instance();

BuddyX Pro 5.1.0 replaced the external Kirki dependency with a first-party Customizer Framework that lives at inc/Customizer_Framework/. The public API matches Kirki’s add_*() calls 1:1 so existing field declarations continue to work; the framework owns sanitisation, output, and the preview JS.

// No bootstrap require - the framework is autoloaded as a Component.
use BuddyxPro\BuddyxPro\Customizer_Framework\Field;
// Panel (registered via WP_Customize_Manager directly - the framework
// only owns sections + fields).
add_action( 'customize_register', function ( $wp_customize ) {
$wp_customize->add_panel( 'my_panel', array(
'title' => __( 'My Panel', 'buddyxpro' ),
'priority' => 10,
) );
$wp_customize->add_section( 'my_section', array(
'title' => __( 'My Section', 'buddyxpro' ),
'panel' => 'my_panel',
) );
} );
// Field - framework picks the right control + sanitizer based on `type`.
\BuddyxPro\BuddyxPro\Customizer_Framework\Field::add( 'switch',
array(
'settings' => 'my_option',
'label' => __( 'Enable Feature', 'buddyxpro' ),
'section' => 'my_section',
'default' => 'off',
'choices' => array(
'on' => __( 'Yes', 'buddyxpro' ),
'off' => __( 'No', 'buddyxpro' ),
),
)
);

color, switch, radio_image, radio_buttonset, select, slider, dimension, typography, background, image, cropped_image, text, textarea, repeater, sortable, custom. See inc/Customizer_Framework/Field.php::$field_types for the canonical map.

When a field includes an output array (one or more { element, property, units } rules), the framework auto-resolves its transport to postMessage and wires customizer-preview.js to apply the new CSS rule live in the iframe. Set 'transport' => 'refresh' explicitly only when a setting genuinely needs a full reload.

Colour and dimension settings flow through inc/Tokens/Component.php, which writes one set of --bx-color-* / --bx-radius-* CSS custom properties on <html> plus a thin compat layer of legacy aliases (--color-theme-body, --global-border-radius, etc.). New CSS should consume the --bx-* token; aliases exist only for back-compat with pre-5.1.0 selectors. normalize_dimension() defaults bare numbers to px so customizer sliders that don’t emit a unit still produce valid CSS.

inc/Customizer_Framework/Output_Builder.php walks every field with an output rule and emits one combined inline CSS block at wp_head. Typography arrays expand into multi-declaration blocks; backgrounds expand into the 6-key background shorthand; scalar values use the field’s _type to pick a default CSS property.

Dynamic CSS in 5.1.x is produced by three cooperating pieces - there is no single “Dynamic_Style” component:

  1. inc/Tokens/Component.php maps color / dimension theme mods onto canonical --bx-color-* and --bx-radius-* custom properties (plus legacy aliases) and prints them on <html> at wp_head. Light and dark modes emit separate token blocks.
  2. inc/Customizer_Framework/Output_Builder.php walks every customizer field that declares an output rule and emits one combined inline CSS block.
  3. inc/Styles/Component.php enqueues the CSS manifest and adds any remaining inline CSS (via load_custom_styles() on wp_head).

Child themes should consume the --bx-* tokens rather than regenerating CSS. See the Customization Guide.

Widgets are in inc/widgets/:

// Load widgets
require get_template_directory() . '/inc/widgets/login-widget.php';
require get_template_directory() . '/inc/widgets/bp-profile-completion-widget.php';
Class File
BP_BUDDYX_BP_Login_Widget inc/widgets/login-widget.php
BP_BuddyxPro_Profile_Completion_Widget inc/widgets/bp-profile-completion-widget.php
LD_Course_Features_Widget inc/widgets/ld-featured-course-widget.php
BuddyxPro_WCV_Widget_Vendor_Profile inc/widgets/class-vendor-profile-widget.php (extends WC_Widget)
class BP_BUDDYX_BP_Login_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'buddyx_login_widget',
__( 'BuddyX Login', 'buddyxpro' ),
array( 'description' => __( 'Login form widget', 'buddyxpro' ) )
);
}
public function widget( $args, $instance ) {
// Widget output
}
}
// The login widget registers itself on widgets_init.
function buddyx_register_bp_login_widget() {
register_widget( 'BP_BUDDYX_BP_Login_Widget' );
}
add_action( 'widgets_init', 'buddyx_register_bp_login_widget' );
// Login and registration popups
require_once get_template_directory() . '/inc/Helpers/Login_Forms.php';
// BuddyPress activity enhancements
require_once get_template_directory() . '/inc/Helpers/Activity_Functions.php';
// Side panel functionality
require_once get_template_directory() . '/inc/Helpers/Side_Panel.php';
// Dark mode toggle
require_once get_template_directory() . '/inc/Helpers/Dark_Mode.php';

Core template functions:

// Content wrapper hooks
add_action( 'buddyx_before_content', 'buddyx_content_top' );
add_action( 'buddyx_after_content', 'buddyx_content_bottom' );
// Sub header
add_action( 'buddyx_sub_header', 'buddyx_sub_header' );
// Header menu-icon actions area (search / cart). This in turn fires the
// buddyx_header_actions hook - there is no buddyx_header hook.
buddyx_site_menu_icon();
// Get theme version
$version = wp_get_theme( get_template() )->get( 'Version' );
// Via template tags
buddyxpro()->get_version();
// Development: file modification time
// Production: theme version
buddyxpro()->get_asset_version( $filepath );
  • Use PSR-4 autoloading for new classes
  • Implement Component_Interface for new components
  • Use template tag system for reusable functions
  • Add plugin integrations to inc/compatibility/
  • Use BuddyxPro\BuddyxPro\Customizer_Framework\Field::add() for customizer options (no third-party dependency in 5.1.0+)
  • Prefix all functions with buddyx_ or buddyxpro_
  • Use namespace BuddyxPro\BuddyxPro for classes
  • Modify core theme files directly
  • Add business logic to templates
  • Use global variables without prefixing
  • Skip component initialization
  • Bypass the autoloader
  • Hardcode plugin paths