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:
.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
<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:
<!--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:
<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:
<!--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.
<!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:
<!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:
<!--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:
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:
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:
<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:
<!--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:
<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!
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.