This tutorial article, titled Building a Basic Theme with UIkit for Scriptor CMS, will guide you through the process of creating a theme for Scriptor CMS or customizing an existing one to suit your specific requirements. In this tutorial, we will focus on creating a simple theme using the powerful UIkit framework.

Like most CMS platforms, Scriptor utilizes HTML and PHP tags within its templates to generate the desired content. Let's begin by setting up the directory structure for our theme. Start by creating a new folder called uikit-theme within the /site/themes/ directory. Next, download or clone the UIkit framework and organize the files in your themes directory as shown in the diagram below:

uikit-theme/
└───template.php
└───home.php
└───basic.php
└───_navigation.php
└───_functions.php
└───_head.php
└───_footer.php    
└───404.php    
└───scripts/
│  └───uikit.min.js
│  └───uikit-icons.min.js
│  └───main.js
└───css/
   └───uikit.min.css
   └───styles.css

Activate the theme

Now open or create the template.php file located in the root directory of your uikit-theme theme using a text editor. This file serves as the entry point for our theme and is the first theme file that gets loaded. We will use this file as a switch for our template types:

<?php defined('IS_IM') or die ('You cannot access this page directly');

include '_functions.php';

$template = $site->sanitizer->templateName($site->template);

if (file_exists(__DIR__."/$template.php")) { 
    include __DIR__."/$template.php";
} else {
    echo 'The template you specified was not found: '.__DIR__."/$template.php";
}

Every time a page is loaded, the above code determines which template is assigned to the page and attempts to load a template file with that name. If no matching template file is found, a message is displayed stating that the specified template was not found.

As you can see in the folder structure diagram above, we use two different template styles in our theme: home and basic. The home template is used to display the content of the home (start) page, while the basic template represents the layout of the default pages.

Furthermore, we have included the _functions.php file at the top of the code. It contains some functions that we will use later in our template to create specific template areas, such as the navigation. More details about this will be explained later.

Create shared areas

Since both the home and basic templates use the same head, footer, and navigation areas, we will start by creating those files. Let's begin with the _head.php file, which, as you can see, is also located in the root directory of our theme:

Note the underscore (_) at the beginning of the file name. Files starting with an underscore are prevented from direct access by .htaccess.

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title><?php echo $site->title; ?></title>
    <meta name="description" content="Simple template for Scriptor CMS">
    <!-- Mobile-friendly viewport -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="<?php echo $site->themeUrl; ?>css/uikit.min.css">
    <link rel="stylesheet" href="<?php echo $site->themeUrl; ?>css/styles.css">
</head>

Just a quick explanation of the PHP variables in the _head.php file above. The following line outputs the title of the current page:

    <title><?php echo $site->title; ?></title>

To retrieve the URL of the current theme, including the scheme and hostname, you can use $site->themeUrl:

    <link rel="stylesheet" href="<?php echo $site->themeUrl; ?>css/uikit.min.css">

Next, let's continue with the _footer.php file, which will be used in our templates to display the footer area:

<footer class="uk-section uk-section-secondary uk-padding-remove-bottom">
    <div class="uk-container uk-container-small">
        <div class="uk-grid uk-grid-large" data-uk-grid>
            <div class="uk-width-1-2@m">
                <h5>OUR COMPANY</h5>
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
                    incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 
                    nostrud cillum dolore eu fugiat nulla contact to: 
                    <a href="#" title="">info@company.com</a></p>
                <div>
                    <a href="" class="uk-icon-button" data-uk-icon="twitter"></a>
                    <a href="" class="uk-icon-button" data-uk-icon="facebook"></a>
                    <a href="" class="uk-icon-button" data-uk-icon="instagram"></a>
                </div>
            </div>
            <div class="uk-width-1-5@m">
                <h5>PRODUCTS</h5>
                <ul class="uk-list">
                    <li>Big Data</li>
                    <li>Marketing</li>
                    <li>Analytics</li>
                    <li>AI Lab</li>
                </ul>
            </div>
            <div class="uk-width-1-5@m">
                <h5>OUR COMPANY</h5>
                <ul class="uk-list">
                    <li>Team</li>
                    <li>Work</li>
                    <li>Culture</li>
                    <li>Contact Us</li>
                </ul>
            </div>
        </div>
    </div>
    <div class="uk-text-center uk-padding uk-padding-remove-horizontal uk-text-small">
        <span class="uk-text-muted">&copy; <?php echo date('Y'); ?> My Layout - 
        <a href="#">Created by My Company</a> | Built with <a href="https://scriptor-cms.info" 
        title="Simple flat-file CMS" target="_blank" data-uk-tooltip>SCRIPROR 
        <?php echo $site->version; ?></a></span>
    </div>
</footer>
<!-- OFFCANVAS -->
<div id="offcanvas-nav" data-uk-offcanvas="flip: true; overlay: false">
    <div class="uk-offcanvas-bar uk-offcanvas-bar-animation uk-offcanvas-slide">
        <button class="uk-offcanvas-close uk-close uk-icon" type="button" data-uk-close></button>
        <ul class="uk-nav uk-nav-default">
            <?php echo render_offcanvas_navi(['maxLevel' => 2]); ?>
        </ul>
    </div>
</div>
<!-- /OFFCANVAS -->
<script src="<?php echo $site->themeUrl; ?>scripts/uikit.min.js"></script>
<script src="<?php echo $site->themeUrl; ?>scripts/uikit-icons.min.js"></script>

Note the off-canvas sidebar below in the source code. It slides in and out of the page when viewed on a mobile device. The function render_offcanvas_navi() is responsible for rendering the navigation elements. This function is essentially identical to render_navi_bar(), and you can find more information about it here.

Since we use the same navigation across all template types, we also create this source code in a separate _navigation.php file:

<div class="nav" data-uk-sticky="uk-background-secondary uk-box-shadow-medium; 
    top: 100vh; animation: uk-animation-slide-top">
    <div class="uk-container uk-container-small">
        <nav class="uk-navbar uk-navbar-container uk-navbar-transparent" data-uk-navbar>
            <div class="uk-navbar-left">
                <div class="uk-navbar-item uk-padding-remove-horizontal">
                    <a class="uk-logo" title="Logo" href="/"><img src="<?php echo $site->siteUrl; 
                        ?>/data/uploads/simple-template/your-company-logo.png" alt="Logo"></a>
                </div>
            </div>
            <div class="uk-navbar-right">
                <ul class="uk-navbar-nav uk-visible@s">
                    <?php echo render_navi_bar(['maxLevel' => 2]); ?>
                </ul>
                <a class="uk-navbar-toggle uk-navbar-item uk-hidden@s" 
                    data-uk-toggle data-uk-navbar-toggle-icon href="#offcanvas-nav"></a>
            </div>
        </nav>
    </div>
</div>

Template functions

Take note of the functions render_navi_bar() and render_offcanvas_navi(), which will be added to our _functions.php file. Let's examine the specific parameters used in both functions:

<?php echo render_navi_bar([
    'parent' => 0, 
    'maxLevel' => 2, 
    'sortBy' => 'position',
    'order' => 'asc',
    'exclude' => []
]); ?>

Parameters:

  • parent – (integer) (optional) Parent Page ID. The default value is 0, which includes all pages with no parent.
  • maxLevel – (integer) (optional) The maximum depth level. The default is 1.
  • sortBy – (string) (optional) The page field or attribute to sort by. The default value is position.
  • order – (string) (optional) The sort order, either 'asc' for ascending or 'desc' for descending. The default value is asc.
  • exclude – (array) (optional) An array of page IDs to exclude from the navigation. By default, it is an empty array.

Now, add the render_navi_bar() function shown above to the _functions.php file:

<?php

use Scriptor\Core\Scriptor;
use Scriptor\Core\Site;

/**
 * Function for rendering the navigation 
 * elements of the navigation bar.
 * 
 * @param array $options Extra options to customize the rendering of the elements.
 *   - parent (integer) (optional) Parent Page ID. Default value is '0' i.e. all pages with no parent.
 *   - maxLevel (integer) (optional) The down-most level. Default is '1'.
 *   - sortBy (string) (optional) The sort by page field or attribute. Default value position.
 *   - order (string) (optional) Descending or ascending sort order. Default value 'asc'.
 *   - exclude (array) (optional) An array of page ID's to be excluded from the navigation. Is empty by default.
 * 
 * @return string
 */
function render_navi_bar($options = [])
{
    $site = Scriptor::getSite();
    $navigation = '';
    $defaults = [
        'parent' => 0,
        'maxLevel' => 1,
        'exclude' => []
    ];
    $configs = array_merge($defaults, $options);
    $pageLevels = $site->getPageLevels($configs);

    if($pageLevels) {
        foreach($pageLevels[$configs['parent']] as $page) {
            if(in_array($page->id, $configs['exclude'])) continue;

            if(empty($pageLevels[$page->id])) {
                $navigation .= '<li><a href="'.$site->siteUrl.'/'.
                    Site::getPageUrl($page, $site->pages) .'">'.$page->name.'</a></li>';
            } else {
                $configs['parent'] = $page->id;
                $configs['maxLevel'] = $configs['maxLevel'] - 1;
                $navigation .= '<li><a href="#">'.$page->name.'</a>';
                $navigation .= '    <div class="uk-navbar-dropdown">';
                $navigation .= '        <ul class="uk-nav uk-navbar-dropdown-nav">';
                $navigation .=              render_navi_bar($configs);      
                $navigation .= '        </ul>';
                $navigation .= '    </div>';
                $navigation .= '</li>';
            }
        }
    }
    return $navigation;
}

/**
 * Function for rendering the navigation 
 * elements of the offcanvas navigation.
 * 
 * @param array $options Extra options to customize the rendering of the elements.
 *   - parent (integer) (optional) Parent Page ID. Default value is '0' i.e. all pages with no parent.
 *   - maxLevel (integer) (optional) The down-most level. Default is '1'.
 *   - sortBy (string) (optional) The sort by page field or attribute. Default value position.
 *   - order (string) (optional) Descending or ascending sort order. Default value 'asc'.
 *   - exclude (array) (optional) An array of page ID's to be excluded from the navigation. Is empty by default.
 * 
 * @return string
 */
function render_offcanvas_navi($options = []) 
{
    $site = Scriptor::getSite();
    $navigation = '';
    $defaults = [
        'parent' => 0,
        'maxLevel' => 1,
        'exclude' => []
    ];
    $configs = array_merge($defaults, $options);
    $pageLevels = $site->getPageLevels($configs);

    if($pageLevels) {
        foreach($pageLevels[$configs['parent']] as $page) {
            if(in_array($page->id, $configs['exclude'])) continue;

            if(empty($pageLevels[$page->id])) {
                $navigation .= '<li><a href="'.$site->siteUrl.'/'.
                    Site::getPageUrl($page, $site->pages) .'">'.$page->name.'</a></li>';
            } else {
                $configs['parent'] = $page->id;
                $configs['maxLevel'] = $configs['maxLevel'] - 1;
                $navigation .= '<li class="uk-parent"><a href="#">'.$page->name.'</a>';
                $navigation .= '    <ul class="uk-nav-sub">';
                $navigation .=          render_navi_bar($configs);      
                $navigation .= '    </ul>';
                $navigation .= '</li>';
            }
        }
    }
    return $navigation;
}

Templates

Let's now examine the actual template files where we will include the shared template areas we have created. We'll start with the home.php template:

<?php defined('IS_IM') or die('You cannot access this page directly'); ?>
<!DOCTYPE html>
<html lang="en">
<?php include '_head.php'; ?>
<body>
    <div class="top-wrap uk-position-relative uk-background-secondary">
        <?php include '_navigation.php' ?>
        <div class="uk-cover-container uk-light uk-flex uk-flex-middle top-wrap-height">
            <div class="uk-container uk-container-small uk-flex-auto top-container 
                uk-position-relative uk-margin-medium-top" data-uk-parallax="y: 0,50; easing:0; opacity:0.2">
                <div class="uk-width-1-2@s" data-uk-scrollspy="cls: uk-animation-slide-right-medium; 
                    target: > *; delay: 150">
                    <h6 class="uk-text-primary uk-margin-small-bottom">RESEARCH</h6>
                    <h1 class="uk-margin-remove-top">Innovation in your hands.</h1>
                    <p class="subtitle-text uk-visible@s">Tempor incididunt ut labore et dolore magna aliqua. 
                        Ut enim ad minim veniam, quis nostrud exercitation ullamco </p>
                    <a href="#" title="Learn More" class="uk-button uk-button-primary uk-border-pill" 
                        data-uk-scrollspy-class="uk-animation-fade">LEARN MORE</a>
                </div>
            </div>
        </div>
        <section id="content" class="uk-section uk-section-default uk-background-default">
            <div class="uk-container uk-container-small">
                <h2 class="uk-margin-small uk-margin-remove-top"><?php echo $site->title; ?></h2>
                <?php echo $site->render('content'); ?>
            </div>
        </section>  
    </div>
    <?php include '_footer.php'; ?>
</body>
</html>

Next, create the basic.php template file to display all pages associated with the basic template:

<?php defined('IS_IM') or die('You cannot access this page directly'); ?>
<!DOCTYPE html>
<html lang="en">
<?php include '_head.php'; ?>
<body>
    <div class="top-wrap uk-position-relative">
        <?php include '_navigation.php' ?>
        <section id="content" class="uk-section uk-section-default uk-padding-large uk-background-default">
            <div class="uk-container uk-container-small uk-margin-top uk-padding-top">
                <h1 class="uk-margin-top"><?php echo $site->title; ?></h1>
                <?php echo $site->render('content'); ?>
            </div>
        </section>  
    </div>
    <?php include '_footer.php'; ?>
</body>
</html>

What is missing now are the files 404.php and a styles.css stylesheet. You can simply copy the first file from the default basic theme, and the content of the styles.css file might look something like this:

body {
    margin: 0;
    padding: 0;
    font-size: 1em;
    text-rendering: optimizeLegibility;
    font-smoothing: antialiased;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    -webkit-overflow-scrolling: touch;
    -webkit-tap-highlight-color: transparent;
    -webkit-text-size-adjust: none;
    -webkit-touch-callout: none;
    -webkit-font-smoothing: antialiased;
}
.nav {
    position: absolute;
    z-index: 99;
    top: 0;
    left: 0;
    right: 0;
    background: #222;
}
.uk-sticky-placeholder {
    height: 0 !important;
}
.top-wrap::before, .overlay-wrap::before {
    position: absolute;
    top:0;
    right:0;
    left: 0;
    bottom: 0;
    content: '';
    z-index: 1;
}
.top-wrap-height {
    height: 80vh;
    min-height: 460px;
    transition: height 0.25s;
}
.uk-subnav-pill > * > :first-child {
    padding: 0.5rem 1rem;
    background-color: rgba(0, 0, 0, 0.04);
}
.top-container {
    z-index: 2;
}
.subtitle-text {
    font-size: 1.2em;
    opacity: 0.7;
    font-weight: 300;
}
.uk-navbar-dropdown {
    background: #3a3838;
    color: #c5c5c5;
}
.uk-navbar-dropdown-nav>li>a:focus, .uk-navbar-dropdown-nav>li>a:hover {
    color: #d8d8d8;
}

Finally, I created the following page structure in the Scriptor editor for demonstration purposes:

Demo image

Where Home is the only page assigned to the home template, and all other pages are assigned the basic template.

Check out the demo page.

If you want to explore the theme, you can download it here.