Skip to main content
Recent Posts
1
Theme development / Theme Developers Guide for ElkArte 2.0.x
Last post by Spuds -
ElkArte Theme Developer Guide: Customizing Theme Functions

Overview
This guide shows theme developers how to customize ElkArte's new modular theme system. The theme system is now organized into logical groups that are easier to understand and modify.
Quote Think of this like CSS overrides - you can customize specific parts without breaking the whole theme. Each section below handles a different aspect of your theme.

Understanding the New Structure
ElkArte themes are now organized into these main areas:

  • :art: Template Rendering - How pages are displayed (header, footer, layout)
  • :bust_in_silhouette: Context Management - User information and theme data 
  • :zap: Feature Integration - Special features (videos, code highlighting, etc.)
  • :package: Asset Management - CSS, JavaScript, and theme variants
  • :globe_with_meridians: Headers Management - Browser communication settings


Getting Started: Your Theme File
Your theme file is located at: themes/your_theme_name/Theme.php

Here's the basic structure you'll work with:
Code: [Select]
<?php
namespace ElkArte\Themes\YourThemeName;

use ElkArte\Themes\Theme as BaseTheme;

class Theme extends BaseTheme
{
    public function getSettings()
    {
        return [
            // Your theme settings here
            'theme_version' => '2.0',
            'theme_variants' => ['light', 'dark'],
            // ... more settings
        ];
    }
   
    // Add your custom functions here
}

:art: Customizing Template Rendering

What This Controls
  • How the page header and footer are displayed
  • Loading of template layers
  • Admin warnings and notices
  • Copyright display


Common Customization's

Adding Custom CSS to Every Page
Code: [Select]
public function template_header(): void
{
    // First, do the normal header stuff
    parent::template_header();
   
    // Now add your custom CSS
    global $context;
    $context['html_headers'] .= '
        <link rel="stylesheet" href="' . $settings['theme_url'] . '/css/my_custom.css">
        <style>
            .my_custom_class {
                background-color: #your_color;
            }
        </style>';
}

Adding Custom JavaScript to Every Page
Code: [Select]
public function template_header(): void
{
    parent::template_header();
   
    // Add your custom JavaScript
    $this->addInlineJavascript('
        document.addEventListener("DOMContentLoaded", function() {
            console.log("My custom theme is loaded!");
            // Your custom JavaScript code here
        });
    ');
}

Customizing the Footer
Code: [Select]
public function template_footer(): void
{
    // Add custom content before the normal footer
    echo '<div class="my_custom_footer_content">
            <p>Powered by My Amazing Theme</p>
          </div>';
   
    // Then show the normal footer
    parent::template_footer();
}

Custom Copyright Message
Code: [Select]
public function theme_copyright(): void
{
    global $forum_copyright;
   
    // Replace the default copyright with your own
    $forum_copyright = 'My Custom Forum © 2026 | Powered by ElkArte';
   
    echo '<span class="my_copyright">' . $forum_copyright . '</span>';
}

Customizing User Context

What This Controls
  • User information display
  • Guest vs logged-in user handling
  • Forum statistics
  • News display
  • Page titles and metadata


Common Customizations

Adding Custom User Information
Code: [Select]
public function setupLoggedUserContext(): void
{
    global $context;
   
    // Do the normal user setup first
    parent::setupLoggedUserContext();
   
    // Add your custom user information
    $context['user']['custom_title'] = 'VIP Member'; // Example
    $context['user']['theme_preference'] = 'dark'; // Example
}

Customizing Page Titles
Code: [Select]
public function setContextThemeData(): void
{
    global $context, $mbname;
   
    // Do normal setup first
    parent::setContextThemeData();
   
    // Customize the page title format
    $context['page_title'] = $context['page_title'] . ' | ' . $mbname . ' - Your Custom Tagline';
   
    // Add custom meta tags
    $context['html_headers'] .= '
        <meta name="description" content="Your custom forum description">
        <meta name="keywords" content="forum, community, your, keywords">';
}

Adding Custom News Display
Code: [Select]
public function setupNewsLines(): void
{
    global $context;
   
    // Do the normal news setup
    parent::setupNewsLines();
   
    // Add your custom news processing
    if (!empty($context['news_lines'])) {
        foreach ($context['news_lines'] as $key => $news) {
            // Add custom styling to each news item
            $context['news_lines'][$key] = '<span class="my_news_style">' . $news . '</span>';
        }
    }
}

Customizing Special Features

What This Controls
  • Video embedding appearance
  • Code highlighting themes
  • Relative time display
  • Scheduled tasks


Common Customization's

Customizing Video Embedding
Code: [Select]
public function autoEmbedVideo(): void
{
    global $txt, $modSettings;

    if (!empty($modSettings['enableVideoEmbeding'])) {
        // Load the video embedding script
        loadJavascriptFile('elk_jquery_embed.js', ['defer' => true]);

        // Customize the video embedding settings
        $this->addInlineJavascript('
            if (typeof oEmbedtext === "undefined") {
                var oEmbedtext = ({
                    embed_limit : 10, // Limit to 10 videos per page
                    preview_image : "▶️ Click to play video",
                    // Add your custom text
                    youtube : "YouTube Video",
                    vimeo : "Vimeo Video",
                });

                document.addEventListener("DOMContentLoaded", () => {
                    if ($.isFunction($.fn.linkifyvideo)) {
                        $().linkifyvideo(oEmbedtext);
                    }
                });
            }
        ', true);
    }
}

Adding Custom Code Highlighting
Code: [Select]
public function addCodePrettify(): void
{
    global $modSettings;

    if (!empty($modSettings['enableCodePrettify'])) {
        // Load your custom prettify theme
        $this->loadVariant('prettify');
        loadJavascriptFile('ext/prettify.min.js', ['defer' => true]);
       
        // Add custom styling
        loadCSSFile('my_code_theme.css');

        $this->addInlineJavascript('
            document.addEventListener("DOMContentLoaded", () => {
                if (typeof prettyPrint === "function") {
                    prettyPrint();
                   
                    // Add custom code block styling
                    document.querySelectorAll("pre").forEach(function(block) {
                        block.classList.add("my-custom-code-style");
                    });
                }
            });
        ', true);
    }
}

Customizing Assets (CSS/JS)

What This Controls
  • Loading CSS and JavaScript files
  • Theme variants (light/dark modes)
  • Custom styling
  • Progressive Web App features


Common Customizations

Adding Theme-Specific CSS

Create a method that loads additional CSS for your theme:
Code: [Select]
public function loadThemeJavascript(): void
{
    // Do the normal JavaScript loading
    parent::loadThemeJavascript();
   
    // Add your theme-specific files
    loadCSSFile('my_theme_extras.css');
    loadJavascriptFile('my_theme_extras.js', ['defer' => true]);
   
    // Add theme-specific JavaScript variables
    $this->addJavascriptVar([
        'my_theme_color' => '#your_primary_color',
        'my_theme_name' => 'My Awesome Theme'
    ]);
}

Creating Custom Theme Variants

First, create variant directories in your theme:
Code: [Select]
themes/your_theme/css/
├── _light/
│   ├── index_light.css
│   └── custom_light.css
├── _dark/
│   ├── index_dark.css
│   └── custom_dark.css

Then customize the variant loading:
Code: [Select]
public function loadThemeVariant(): void
{
    global $context, $settings;
   
    // Do normal variant loading
    parent::loadThemeVariant();
   
    // Add custom variant-specific CSS
    if (!empty($context['theme_variant'])) {
        $this->loadVariant('my_custom_styles', true);
       
        // Add variant-specific JavaScript
        $this->addInlineJavascript('
            document.body.classList.add("theme-variant' . $context['theme_variant'] . '");
        ');
    }
}

Advanced Customization's

Adding Custom Settings to getSettings()
Code: [Select]
public function getSettings()
{
    // Get the default settings first
    $settings = parent::getSettings();
   
    // Add your custom settings
    return array_merge($settings, [
        // Your color scheme
        'primary_color' => '#3498db',
        'secondary_color' => '#2c3e50',
       
        // Custom theme variants
        'theme_variants' => ['light', 'dark', 'purple', 'green'],
       
        // Custom avatar settings
        'avatars_on_indexes' => 3, // Show both first and last post avatars
       
        // Custom page index styling
        'page_index_template' => [
            'base_link' => '<li class="my-page-btn"><a href="{base_link}">%2$s</a></li>',
            'current_page' => '<li class="my-current-page"><strong>%1$s</strong></li>',
        ],
       
        // Enable custom features
        'my_theme_features' => [
            'custom_header' => true,
            'animated_buttons' => true,
            'gradient_backgrounds' => true,
        ]
    ]);
}

Complete Example: Custom Theme Class

Here's a complete example showing how to create a custom theme:
Code: [Select]
<?php
namespace ElkArte\Themes\MyAwesomeTheme;

use ElkArte\Themes\Theme as BaseTheme;

class Theme extends BaseTheme
{
    public function getSettings()
    {
        return [
            'theme_version' => '2.0',
            'theme_variants' => ['light', 'dark', 'colorful'],
            'primary_color' => '#e74c3c',
            'require_theme_strings' => false,
            'avatars_on_indexes' => 1,
        ];
    }
   
    public function template_header(): void
    {
        parent::template_header();
       
        // Add custom CSS
        global $context, $settings;
        $context['html_headers'] .= '
            <link rel="stylesheet" href="' . $settings['theme_url'] . '/css/awesome.css">
            <link rel="preconnect" href="https://fonts.googleapis.com">
            <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">';
       
        // Add theme initialization
        $this->addInlineJavascript('
            document.addEventListener("DOMContentLoaded", function() {
                document.body.classList.add("awesome-theme");
                console.log("🎨 Awesome Theme Loaded!");
            });
        ');
    }
   
    public function setContextThemeData(): void
    {
        global $context;
       
        parent::setContextThemeData();
       
        // Add custom meta tags
        $context['html_headers'] .= '
            <meta name="theme-color" content="#e74c3c">
            <meta name="description" content="An awesome forum powered by ElkArte">';
       
        // Customize page title
        if (isset($context['page_title'])) {
            $context['page_title'] .= ' 🎨';
        }
    }
   
    public function theme_copyright(): void
    {
        echo '<div class="awesome-copyright">
                <p>Powered by <strong>Awesome Theme</strong> &amp; ElkArte</p>
              </div>';
    }
}

Tips for Theme Developers

1. Always Call Parent First
Code: [Select]
public function template_header(): void
{
    // Good: Call parent first
    parent::template_header();

    // Then add your customizations
}

2. Use CSS Classes for Styling
Instead of inline styles, add CSS classes:
Code: [Select]
// Avoid inline styles
echo '<div style="color: red;">Content</div>';

// Better: Use CSS classes
echo '<div class="my-theme-highlight">Content</div>';

3. Test Your Customizations
  • Test with different user types (guest, member, admin)
  • Test with different theme variants
  • Test on mobile devices
4. Use Browser Developer Tools
  • Inspect Element to see what's happening
  • Check the Console tab for JavaScript errors
  • Use the Network tab to see if CSS/JS files are loading
Common Mistakes to Avoid

1. Forgetting parent:: calls - This breaks core functionality
2. Not using global variables - Use global $context, $settings; when needed
3. Not testing thoroughly - Test different pages and user types

Remember: You're not replacing the entire system, just customizing specific parts to make your theme unique!
2
Site Feedback / Re: EARLY Beta version of 2.0
Last post by Spuds -
And some more progress.  I found a couple of minor issues in the upgrade script.  It ran fine but the result had a couple of minor db value issues that I wanted to cleanup.  That work has been completed, of course there maybe other issues but I have run it on a 100K forum and the conversion worked fine.

While doing that I had to update our buddy repair_settings so it was 2.0 compatible.  Actually found some large inefficiency's and fixed those so it runs a LOT faster in certain situations.  While working on that I found some problems/inefficiency's in attachment maintenance so fixed those as well.

Lastly since I was stuck in attachment mode, I added support for AVIF and HEIC attachments (not smileys not avatars) .. AVIF is supported in recent GD and Imagick ... HEIC is ONLY supported in new Imagick (don't expect GD support)

AVIF is supported in most modern browsers, the forum support allows (if you allow the extension) resize/rotate per your admin panel settings, the format will remain AVIF.  Its not treated much different than a JPG or PNG file.

HEIC is a another Apple I-turd.  The software (if you have Imagick support) will allow uploading an HEIC image and then it will always convert it to a JPG.  After that t will treat that image like it always was a JPG and shrink/rotate/etc per you admin panel settings.  Only some versions of Safari will display a HEIC image,  nothing else knows what the heck that is.  You can allow the extension w/o server support but it will show as a dead bird on 95% of browsers.

I think I can do the initial 2.0B1 packages this weekend so they can be tested, and if they don't fail in spectacular fashion, I'll tag them as official.
8
Localization / 2.0 Language Files on Transifex
Last post by Spuds -
This ended up being a bit of a chore.  The upload is of-course easy, its standard push/pull commands.  The chore was that we changed the way lanaguage files are named and where they are storred in 2.0

In 1.1 it was /themes/languages/admin.lang1.php ... admin.langx.php
In 2.0 it is /sources/ElkArte/Languages/Admin/lang1.php ... langx.php

Renaming is not a big deal, but we had new strings, new area/files, changed strings, removed strings.  So I wrote a little best guess program that compared each area, like Admin 2.0 to 1.1. 
  • If the string key existed in both and the values were they the same use it,
  • If the string was missing drop it
  • If the string was only different by capitalization/punctuation, use as is,
  • If it they were close based on a levenshtein_ratio use as is, otherwise drop it as untranslated.
  • If the translated string is just English strings, drop them. 

So what is now in Transifex under ElkArte-2.0 is cleaner than what we have had before and the % translated is more accurate.

Also for fun, I wrote a small program that pulls untranslated strings from Transifex, runs them through an OpenAI model and stuffs them back into Transifex.  You specify the area, like Admin, the language like German/de and the program does the rest.  Of course those models are not free to run so I did it as an experiment ... and it works!

Still have to write the program that pulls the language files from Transifex and creates the packages.
9
Site Feedback / Re: EARLY Beta version of 2.0
Last post by Spuds -
Made some good progress over the last few days.

I do have install working correctly, the only tweaks were getting the base settings to something more appropriate.

The upgrade was a different story, but I have it working to the point I can finally check on some refinements.  The delay was really in updating the update.php script so it worked with 2.0, plus there was so much old cruft in that file.  Anyway it now will upgrade a 1.1 db to a 2.0 version. 

If upgrading there is still manual work that needs to be done, copying attachments and avatars over but the script is not going to do that for you, the overall directory structure is just too different.  I'll jot down the easy path to upgrade but that will not be the only way, nor will the upgrade to anything other than update the db.

SO closer we inch !