Skip to content

Template Customization

You should now have a basic understanding of how Apollo works. You are now ready to run the project and start developing. We've been talking about tailwind and Vite for ages but you'll quickly see how fast it is to run your project. In your terminal window, change directory to the root of the project:

cd path/to/my/project/

Then start by building the project. Run the following command. It will also launch a development server in your browser window :

pnpm dev

When the dev server starts, open http://localhost:3000/ in your browser to see the frontend. That's it, your project is now running. Every change you make to the HTML, JS or PostCSS files will trigger a code compilation and a browser refresh. Really easy isn't it?

Template paradigm

Apollo is, before everything else, an HTML template. Since we are using Tailwind, we could develop this template following two different approaches:

The first one would be to abstract tailwind classes using CSS and alias them under more semantic names like .button or .btn for example. We would have to create a CSS file for each component type and start abstracting classes. let's continue on that button example. We would be creating a CSS file named button.postcss for the example. Inside that file, we could do something like this:

css
.button {
  @apply inline-flex items-center justify-center gap-x-1 py-2 px-4 font-sans text-sm text-muted-600;
}

and then use it in HTML

html
<button type="button" class="button">Button</button>

The problem with this approach is that it generates a lot of CSS and gives arbitrary class names that could conflict in already running project or simply not be appropriate with one's way of building apps or websites.

So we decided to stick to the simplest approach. We decided to use tailwind classes in the HTML templates, without having to write any additional CSS. This gives you quite some quality of life when developing:

  • You can copy and paste chunks of HTML from a page to another, from a partial to another and always get what you expect.
  • You can code faster without the need of constantly looking at an external stylesheet
  • You are not restricted to an arbitrary class naming system and are free to convert the template's HTML code to React components, Vue components, Angular components, Svelte components or any other JS frontend framework.
  • You can use Alpine JS declarative syntax to do your dynamic class changes without having to write any CSS.

Creating a new page

Let's start by creating our first new page. Open src/root and add a new file in there. You can name it whatever you want. In this tutorial, we will call it tutorial.html. After you've created it, open the file in your editor, add in the layout you want to use and copy that chunk of HTML code to make sure our page works. We are going to use the default layout:

html
<!--Start Layout-->
{{#> default }}

  <div class="w-full py-20 text-center font-sans text-bold text-3xl text-muted-800">
    <span>Our page is working!</span>
  </div>

<!--End Layout-->
{{/default}}

You can than navigate to http://localhost:3000/tutorial.html to see your newly created page. You can start adding content inside by copying some of the template existing sections and content.

Creating a new partial

We are now going to create our partial, a reusable chunk of HTML code. Let's open src/partials and create a tutorial/ folder in there. Inside that src/partials/tutorial folder, let's create our partial file and name it tutorial-partial.html. You can of course name it like you want, just make sure to reflect your changes in the path when you call it in your pages. Inside our src/partials/tutorial/tutorial-partial.html file, let's add some content:

html
<div class="py-12">
  <div class="flex justify-center items-center h-16 w-24 mx-auto bg-rose-500 font-sans font-semibold text-bold text-white">
    <span>My red partial</span>
  </div>
</div>

Let's get back to our src/root/tutorial.html page that we've just created a little bit earlier and add our partial inside using the handlebars syntax:

html
<!--Start Layout-->
{{#> default }}

  <div class="w-full py-20 text-center font-sans text-bold text-3xl text-muted-800">
    <span>Our page is working!</span>
  </div>

  {{> tutorial/tutorial-partial}}

<!--End Layout-->
{{/default}}

You can than navigate to http://localhost:3000/tutorial.html on your browser to see your page with the newly added partial. Easy isn't it? You can repeat this procedure to add as many partials as you want. Note that you can call partials from other partials.

Creating a new layout

Let's say that you don't want to use the same layout for the page we've just created and that you want to use a layout without the navbar and the footer. Open src/layouts and add a new file in there. You can name it whatever you want. In this tutorial, we will call it layout.html.

WARNING

make sure you always have a default layout named default.html. You can name additional layouts whatever you want but it is a solid convention to keep a default layout named default to avoid messing things up.

Let's put in what we need in our layout, like links to stylesheets, scripts (any partials you'd like) and other meta info.

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/png" href="/img/favicon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mistral - Tailwind CSS Landing Page</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Roboto+Flex:opsz,wght@8..144,300;8..144,400;8..144,500;8..144,600&display=swap"
      rel="stylesheet"
    />

    <link rel="stylesheet" type="text/css" href="/postcss/main.postcss" />
  </head>
  <body class="w-full h-full bg-white dark:bg-muted-900">

    <main class="w-full">
      <!-- Renders the page body -->
      {{> @partial-block }}
    </main>

    <script type="module" src="/scripts/main.js"></script>
  </body>
</html>

We now have a clean HTML doc type structure. The only thing we still need to add is to wire Alpine JS to be able to make the dark mode toggle when you hit the theme switcher. Add the theme() function to the <html /> tag using Alpine's x-data attribute:

html
<!DOCTYPE html>
<html
  lang="en"
  x-data="layout()"
  :class="{
    'dark': $store.app.isDark,
    '': !$store.app.isDark
  }"
>
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/png" href="/img/favicon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mistral - Tailwind CSS Landing Page</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Roboto+Flex:opsz,wght@8..144,300;8..144,400;8..144,500;8..144,600&display=swap"
      rel="stylesheet"
    />

    <link rel="stylesheet" type="text/css" href="/postcss/main.postcss" />
  </head>
  <body class="w-full h-full bg-white dark:bg-muted-900">
    <!-- prettier-ignore -->

    <main class="w-full">
      <!-- Renders the page body -->
      {{> @partial-block }}
    </main>

    <script type="module" src="/scripts/main.js"></script>
  </body>
</html>

Your layout is now ready to work with your pages. Let's get back to our src/root/tutorial.html page and change the layout to the new one we've created:

html
<!--Start New Layout-->
{{#> layout }}

  <div class="w-full py-20 text-center font-sans text-bold text-3xl text-muted-800">
    <span>Our page is working!</span>
  </div>

  {{> tutorial/tutorial-partial}}

<!--End New Layout-->
{{/layout}}

You should now see your page without any navigation and footer because we changed to a layout that doesn't have any. You are now fully ready to start creating new pages, partials and layouts for your app or website.

Creating a JS component

One of the last things we have to cover is how to create a component with Alpine JS and use it inside our HTML templates. For the sake of the example, let's create a simple dropdown component, with very basic styling, just so you can focus on how it works.

First things first, let's create our dropdown javascript file. Open src/root/scripts/components and create a new folder named dropdown. Inside that dropdown folder, create an index.js file that will hold our dropdown logic. Let's add in our dropdown function code:

javascript
export function dropdown() {
  return {
    isOpen: false,
    open() {
      this.isOpen = true
    },
    close() {
      this.isOpen = false
    }
  }
}

That's all we need for a dropdown. We have a first isOpen property that controls the component state and two methods, open() and close(), who interact with that state to make it change. Right now, this new function we've created is not available yet in our template. Trying to use it now will result in an error returning undefined. We need to register our function and bind it to the window object before using it. Inside src/root/scripts/components/index.js, add our dropdown file import and binding:

javascript
import { layout } from './layout'
import { navbar } from './navbar'
import { search } from './search'
import { backtotop } from './backtotop'
import { boxCarousel, cardCarousel } from './carousel'
import { video } from './video'
import { collapse } from './collapse'
import { gallery } from './gallery'
import { pricing } from './pricing'
import { comparison } from './comparison'
import { blog } from './blog'

//Our dropdown import
import { dropdown } from './dropdown'

window.layout = layout
window.navbar = navbar
window.search = search
window.backtotop = backtotop
window.boxCarousel = boxCarousel
window.cardCarousel = cardCarousel
window.video = video
window.collapse = collapse
window.gallery = gallery
window.pricing = pricing
window.comparison = comparison
window.blog = blog

//Our dropdown binding
window.dropdown = dropdown

That's it, our dropdown function is now available in the DOM and we can use it on our component templates. We still need to create a UI for this component. We don't have time to make a perfect UI, so we will remain as simple as possible, just enough to showcase how a component works. We are going to create a partial for our component to make things easier. Open src/partials/ and add a new folder named /dropdown inside. Inside that new folder, add a new file named tutorial-dropdown.html. Inside that file, paste in the following markup:

html
<div class="relative" >
  <button class="relative font-sans font-normal inline-flex items-center justify-center outline-none focus:outline-none leading-5 no-underline space-x-1 text-slate-700 bg-white border dark:text-white dark:bg-slate-700 dark:border-slate-600 h-10 px-4 py-2 text-base rounded hover:bg-slate-50 focus:outline-dashed focus:outline-gray-300 dark:focus:outline-gray-600 focus:outline-offset-2 transition-all duration-300"
  >
    <span>Dropdown</span>
    <i class="iconify h-4 w-4 transition-transform duration-300" data-icon="lucide:chevron-down"></i>
  </button>

  <div class="absolute top-0 left-0 mt-12 w-72 bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-md p-4 shadow-lg">
    <ul class="space-y-2 marker:text-slate-500 dark:marker:text-slate-400">
      <li class="flex items-center">
        <a href="#" class="flex items-center w-full p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-600 transition-colors duration-300">
          <p class="font-sans text-xs text-slate-500 dark:text-slate-400">Dropdown item 1</p>
        </a>
      </li>
      <li class="flex items-center">
        <a href="#" class="flex items-center w-full p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-600 transition-colors duration-300">
          <p class="font-sans text-xs text-slate-500 dark:text-slate-400">Dropdown item 2</p>
        </a>
      </li>
      <li class="flex items-center">
        <a href="#" class="flex items-center w-full p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-600 transition-colors duration-300">
          <p class="font-sans text-xs text-slate-500 dark:text-slate-400">Dropdown item 3</p>
        </a>
      </li>
    </ul>
  </div>
</div>

Then add your dropdown partial to the existing page we've been working on earlier:

html
<!--Start New Layout-->
{{#> layout }}

  <div class="w-full py-20 text-center font-sans text-bold text-3xl text-muted-800">
    <span>Our page is working!</span>

    <!--Add our dropdown here-->
    {{> dropdown/tutorial-dropdown}}
  </div>

  {{> tutorial/tutorial-partial}}

<!--End New Layout-->
{{/layout}}

Looking at the page, you can now notice we have a UI for our dropdown but that it doesn't work. It is just lying there, opened and static. Le's wire up the code we've writer with Alpine a little bit earlier:

html
<div x-data="dropdown()" class="relative" @click.away="close()">
  <button type="button" class="relative font-sans font-normal inline-flex items-center justify-center outline-none focus:outline-none leading-5 no-underline space-x-1 text-slate-700 bg-white border dark:text-white dark:bg-slate-700 dark:border-slate-600 h-10 px-4 py-2 text-base rounded hover:bg-slate-50 focus:outline-dashed focus:outline-gray-300 dark:focus:outline-gray-600 focus:outline-offset-2 transition-all duration-300"
    @click="open()"
  >
    <span>Dropdown</span>
    <i class="iconify h-4 w-4 transition-transform duration-300" :class="{'rotate-180': isOpen}" data-icon="lucide:chevron-down"></i>
  </button>

  <div x-show="isOpen" x-transition 
    class="absolute top-0 left-0 mt-12 w-72 bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-md p-4 shadow-lg">
    <ul class="space-y-2 marker:text-slate-500 dark:marker:text-slate-400">
      <li class="flex items-center">
        <a href="#" class="flex items-center w-full p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-600 transition-colors duration-300">
          <p class="font-sans text-xs text-slate-500 dark:text-slate-400">Dropdown item 1</p>
        </a>
      </li>
      <li class="flex items-center">
        <a href="#" class="flex items-center w-full p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-600 transition-colors duration-300">
          <p class="font-sans text-xs text-slate-500 dark:text-slate-400">Dropdown item 2</p>
        </a>
      </li>
      <li class="flex items-center">
        <a href="#" class="flex items-center w-full p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-600 transition-colors duration-300">
          <p class="font-sans text-xs text-slate-500 dark:text-slate-400">Dropdown item 3</p>
        </a>
      </li>
    </ul>
  </div>
</div>

After adding our Alpine attributes, you can now see that our dropdown perfectly works. We're using x-data to load in the function we've written earlier. We're then using @click events to bind our open() and close() methods to clickable elements. Finally we are using x-show and x-transition on the dropdown menu to be able to toggle it based on the current component state. That's all you need to know to start creating your own Alpine components!

Using purge icons

Apollo Vite configuration uses vite-plugin-purge-icons. This plugin let's you access a library of thousands of icons and only include in the production bundle the ones you've been using in the project. Pretty awesome, isn't it? You can learn more about the plugin advanced usage by looking at its GitHub repository. Let's cover the basic usage:
  • First, select an icon within the thousands of icons lying here: Icon Library.
  • When you click there on an icon, a details panel comes in from the bottom of the page, make sure that in the lower left side dropdown, the selected icon code to copy follows the following syntax: library-name:icon-name. For example: ph:heartbeat-duotone.
  • Inside your HTML template, open a <i></i> tag. This tag must have a class of iconify and a data-icon attribute where you paste in the code you've got from the link above. You can than add tailwind classes to style your icon. here is a practical example:
html
<i class="iconify w-6 h-6 text-primary-500" data-icon="ph:heartbeat-duotone"></i>

That's all you have to do. The above code snippet will render your icon with the additional styles you've set.

Building the project

After you've finished making you changes, creating your pages and your content, it is now time to build the project for production using Vite. No complexity in there, it's just about running a simple command from the root of your project:

cd path/to/my/project/

Then start by building the project. Run the following command :

pnpm build

Vite then starts building the project, assembling all pages, layouts, partials, JS and assets together. It generates a /dist folder located at the root of your project. This dist folder contains bundled javascript and CSS, as well as your final pages. If you want to host a static website, you can simply take the contents of that folder and upload them on your server. If you are building something more complex, you can export your HTML before building it into your other project you are working on.

All Rights Reserved