Skip to content

Template structure

This section is about the file structure. You will learn how the different files are organized, but most important of all, how to manage your project with Gulp. Let's dive into the folder structure.

Global structure

bash
my-listkit/
├── src/
   ├── font/
   ├── img/
   ├── js/
   ├── libs/
   ├── main.js
   ├── sass/
   ├── scss/
   ├── abstracts/
   ├── components/
   ├── forms/
   ├── layout/
   ├── pages/
   ├── utilities/
   └── main.scss
   ├── layouts/
   ├── pages/
   └── partials/
├── gulpfile.js
├── package.json
└── pnpm-lock.yaml

The directory structure can seem unusual for someone who is not used to build tools. We will break each part of it, so you understand how everything works.

Assets

bash
my-listkit/
├── src/
   ├── font/
   ├── img/
   ├── js/
   ├── libs/
   └── main.js
   ├── sass/
   ├── scss/
   ├── abstracts/
   ├── components/
   ├── forms/
   ├── layout/
   ├── libs/
   ├── pages/
   ├── utilities/
   └── main.scss

Bulma folder

bash
my-listkit/
├── sass/
   ├── sass/
   └── bulma.sass

When you installed the project by doing pnpm install, you also installed Bulma 0.9.3 as a project dependency. All Bulma framework Sass source files are there. You can customize Bulma by changing the values of the default Bulma variables. You do not have to edit the source directly. Just declare the Bulma variables you want to override in scss/abstracts/_variables.scss.

WARNING

Edit the Bulma source files only if you know what you are doing as any faulty customization may completely break the layout.

Layouts folder

bash
my-listkit/
├── src/
   └── layouts/

All the HTML layouts resides in this folder. For a more concise code, this project uses a flat file compiler named Panini, by Zurb. Panini makes it easy to break your layouts into several reusable components, preventing you to go through every page to make your changes. You can find more information about zurb/panini by visiting the official repository. The project follows the panini pattern for the HTML file structure.

The layouts folder holds all the template layouts. It is mandatory to have at least one layout, and it should always be named default.html. Additional layouts can have the names you want, but don't forget the .html extension. A layout acts as container providing the same base for a set of similar pages. Finally notice the {{> body}} call that is present in each layout file. This is a reference to append the rest of the page content.

Layout files

The layouts/ folder generally holds 1 layout file, but there are cases where there can be more than 1:

  • default.html : The default layout generally used by all the project pages.

Pages folder

bash
my-listkit/
├── src/
   └── pages/

The pages folder holds all the template pages. Pages are focused on content. They are related to a layout and automatically appended to it by panini when the page is served. You will always find the same statement at the top of each page's code :

yaml
---
layout: default
title: This is the page title
---

The layout statement tells panini which layout to use to serve the page. The title element is a string that gets inserted as the page title when panini has finished assembling the page parts together. Now that we had a look to panini's basic features let's dive into the other HTML files :

Page files

All HTML pages live in this folder. Each one of this pages is linked to one of the existing layouts and makes use of partials living in the src/partials/ folder.

Partials folder

bash
my-listkit/
├── src/
   └── partials/

The partials folder holds all your HTML partials. Partials are chunks of code that you want to reuse as is across your application : it can be a button, a navbar, a content section or whatever you want. Note that you can create as many sub-folders as you want to organize your partials. You simply have to make sure that your partial names are unique. Partials are named like HTML files : navbar.html. When you want to call a partial in one of your layouts or pages use the following expression : {{> partial-name}}. You don't have to mention the path, even if it is nested in several sub-folders, panini will find it. Also note that you don't have to add the HTML extension in your partial call.

Partial files

bash
my-listkit/
├── src/
   ├── partials/
   ├── partial1/
   ├── partial2/
   └── partial3/

Images

my-listkit/
├── src/
│   └── img/

All the project images live in this folder. It can have as many sub-folders as you want. When you build the projects, all these images are automatically transferred to the right location by Gulp.

JS

my-listkit/
├── src/
│   ├── js/
│   │   ├── libs/
│   │   ├── store/
│   │   └── main.js

All javascript files live in the js folder. We recently dropped jQuery support and are now using the ES6 syntax to handle javascript functions. You should be familiar with the bare bones of ES6 when working with this template. Dropping jQuery doesn't mean we didn't replace it with something else. In fact we did, we are now using Alpine JS, a powerful dependency free javascript declarative framework, similar to Vue, but with less complexity. You can learn the basics of Alpine JS by reading the documentation. Alpine works very well in a simple setup where you declare your scripts in the same HTML page. However, since we are not importing the library from a CDN, but rather from node modules, we use a little more complex but significantly more solid setup for alpine inside an ES 6 compatible environment. In the following example, we instantiate Alpine and load some additional plugins. The Intersect plugin makes it easy to interact with DOM elements when they go in / out of the viewport. The Fern plugin builds upon Alpine native stores and make them session persistent.

WARNING

In the Envato version, because of template guidelines, we had to prefix Alpine special attributes like x-data or x-on:click with the data-* prefix, to make them HTML compliant. Therefore using x-data will revert to using data-x-data or data-x-on:click. This can be a little annoying, so if you want a non prefixed version, please feel free to contact us and we'll grant you access to the private GitHub repository.

javascript
//Alpine JS and plugins import
import Alpine from 'alpinejs'
import intersect from '@alpinejs/intersect'
import Fern from '@ryangjchandler/fern'
window.Alpine = Alpine

//Init intersect plugin
Alpine.plugin(intersect)

//Init Fern plugin
Alpine.plugin(Fern)

//Init Fern persisted store
Alpine.persistedStore('app', {
    isLoggedIn: false,
})

//Start Alpine JS
Alpine.start()

Like we said earlier, It's a little bit more complex in this project since we are importing functions from separate JS files. The only thing you need to now when importing an Alpine JS function in your main.js file is that you need to add it to the window object for it to work properly. Otherwise, you'll get undefined. Here is a practical example:

javascript
//Import the function you need from the target file
import { initLandingNavbar } from './navbar/navbar-landing';

//Bind it to the window object to access it from anywhere
window.initLandingNavbar = initLandingNavbar;

This can quickly become very verbose in your main file. Therefore, we decided to make those imports in nested index.js barrel files. An example is src/js/libs/components/index.js. In this file, every component JS file is imported and bound to the global window object. Here is the sample code from that file:

javascript
//Global imports
import { initLandingNavbar } from './navbar/navbar-landing';
import { initNavbar, initNavbarLight } from './navbar/navbar';
import { initNavbarMobile } from './navbar/navbar-mobile';
import { initNavbarBottom } from './navbar/navbar-bottom';
import { initBackToTop } from './backtotop/backtotop';

//Accordions imports
import { initAccordion } from './accordion/accordion';

//Global bind to window object
window.initLandingNavbar = initLandingNavbar;
window.initNavbar = initNavbar;
window.initNavbarLight = initNavbarLight;
window.initNavbarMobile = initNavbarMobile;
window.initNavbarBottom = initNavbarBottom;
window.initBackToTop = initBackToTop;

//Accordions bind to window object
window.initAccordion = initAccordion;

Then, inside the main.js you can easily reimport barrel files to have all your functions loaded in:

javascript
import './libs/components'
import './libs/forms'
import './libs/sections'

You know everything about how to structure your Javascript files in this project.

SCSS

my-listkit/
├── src/
│   ├── scss/
│   │   ├── abstracts/
│   │   ├── components/
│   │   ├── forms/
│   │   ├── layout/
│   │   ├── sections/
│   │   └── main.scss

This template relies on the powerful Sass features, letting you handle complex styles in a breeze. The project relies on a modular SCSS structure. You need to import all the SCSS partials into your main SCSS stylesheet.

File Types

There are two main types of SCSS files : Core and Partials.

Partial files

Partial SCSS file names always start with an underscore like this: _variables.scss . They act as chunks of code that are imported into a core SCSS file. Not only this gives you great control over your code, but it also greatly enhances it's maintainability.

Core file

The main.scss file centralizes all the project styles. Every time you make a change in a partial file, it impacts the outputted dist/css/main.css file resulting from the compilation process (which is handled by Gulp, as we saw above). The main SCSS file starts by importing all the needed SCSS partials. Here is an example statement:

scss
/*! main.scss | ListKit | CSS Ninja 2021-2022 */

/* ==========================================================================
SCSS Imports
========================================================================== */

//1. Base
@import "abstracts/variables";
@import "abstracts/brands";
@import "abstracts/shadows";
@import "abstracts/typography";
@import "abstracts/animations";

//2. Bulma
@import "../sass/bulma";

//3. Custom Helpers
@import "abstracts/helpers";

//4. Layout
@import "layout/all";

//5. Elements
@import "elements/all";

//6. Components
@import "components/all";

//7. Sections
@import "sections/all";

Notice how partials are imported. Whereas the actual partial file name starts with an underscore and ends with a .scss extension, when you write imports inside your main.scss file, you have to remove the underscore and the .scss extension.

Root files

There also a few files sitting at the root of the project that we need to discuss before getting into serious business:

  • gulpfile.js : Gulp will use this file to perform all the tasks it is supposed to accomplish.
  • package.json : Lists all your project's dependencies and gives useful metadata.
  • pnpm-lock.yaml : Automatically generated for any operations where pnpm modifies either the node_modules tree, or package.json. It describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates.

All Rights Reserved