Get started

You can use this starter as a template for your blog and you are ready to go! But there are some adjustments you have to make.

If you are working with VS Code I recommend installing the “Tailwind CSS IntelliSense” addon, as it works also for our custom utility classes. That said, you will find that Tailwind CSS does not work as you might expect. I explain this in one of the blog posts.

The blog posts also explain / show some features that aren’t covered here.

“Docs” lol

CSS

Add and delete your globally available custom block stylesheets in src/assets/css/global/blocks/*.css.

The methodology used is CUBE CSS.

The CSS system of this starter was invented by Andy Bell. If you want to know exactly how it all works, and have a look at the (further elaborated) original, read this article on piccalil.li.

New in version 3.0: Inline CSS

The main CSS file is now inline in production to improve performance, see .src/_includes/head/css-inline.njk.

You can add per-page or component bundles of CSS. Instead of adding your CSS file to the src/assets/css/global/blocks/ directory, you can place them in src/assets/css/bundle/. All CSS files in there will be stored alongside global.css in .src/_includes/css/. You can now include them in the “inline” bundle only on pages or components where you need them:

{% css "inline" %}
  {% include "css/your-stylesheet.css" %}
{% endcss %}

New in version 3.0: Component CSS

All CSS files placed in src/assets/css/components/ will be sent to the output folder, where components can reference them: /assets/css/components/*.css.

New in version 3.0: Debugging CSS

In src/assets/css/global.css you can decomment @import-glob 'tests/*.css'; to include CSS for debugging.

It makes visible when your code wrapped in <is-land> elements is being hydrated, where things might overflow and many other warnings and errors that Heydon Pickering came up with.

Card

Previous to version 3, the card component was a Nunjucks include. There are a number of things that had to be set before being able to use it, like the level of the heading or whether tags shall be displayed. WebC makes this easier, as you can now use the custom element and opt in to the different slots.

Available slots:

  • image: the image shortcode has the slot="image" assigned to its picture or figure wrapper by default!
  • headline: display the card's main title
  • date and tag: Grouped within the classes meta and cluster for date and tagging information.
  • content
  • footer: for links or whatever footer information

I added some variants, avaliable via attribute selectors:

  • img-square: Enforces a square aspect ratio for images.
  • clickable: Makes the whole card clickable.
  • no-padding: Removes padding and background modifications.

Usage


  
  <custom-card>
	{% image "path-to-img", "alt-text" %}
	<span slot="date"></span>
	<span slot="tag" class="button post-tag"></span>
	<h2 slot="headline"></h2>
	<p slot="content"></p>
	<footer slot="footer"></footer>
	</custom-card>

Example

Close-up of a delicate white flower with a yellow center, surrounded by green leaves

Utopia

1516

Among them, there is no sort of traffic, no knowledge of letters, no understanding of numbers, no name of magistrates, nor of politics, only of virtues; and they measure all things by barleycorns; their money, plate, and other ornaments they so diligently polish that no rust can stick to them.

Close-up of a delicate white flower with a yellow center, surrounded by green leaves

The order does not matter

18.02.1984

Just title and content

They have no lawyers among them, for they consider them as a sort of people whose profession it is to disguise matters and to wrest the laws [...].

Close-up of a delicate white flower with a yellow center, surrounded by green leaves

This card has no padding

Red Hat's first logo appeared on an early invoice. It was a simple, bright red brimmed top hat placed above the words "Red Hat Software."

Config

I like to divide things into small thematic areas, it helps me orient myself better. Configurations are structured into separate modules in src/_config and are then imported into the main configuration file.

Each configuration category (filters, plugins, shortcodes, etc.) is modularized. or example, dates.js within the filters folder contains date-related filters.

import dayjs from 'dayjs';

export const toISOString = dateString => dayjs(dateString).toISOString();
export const formatDate = (date, format) => dayjs(date).format(format);

These individual modules are then imported and consolidated in a central filters.js file, which exports all the filters as a single default object.

import {toISOString, formatDate} from './filters/dates.js';
// more imports

export default {
toISOString,
formatDate,
// more exports
};

Integration in Eleventy Config

In the main Eleventy configuration file (eleventy.config.js), these modules are imported:

import filters from './src/_config/filters.js';
import shortcodes from './src/_config/shortcodes.js';

They are then used to register filters and shortcodes with Eleventy, using this nice concise syntax:

eleventyConfig.addFilter('toIsoString', filters.toISOString);
eleventyConfig.addFilter('formatDate', filters.formatDate);
// More filters...
eleventyConfig.addShortcode('svg', shortcodes.svgShortcode);

This method hopefully keeps the Eleventy config clean and focused, only concerning itself with the registration of functionalities, while the logic and definition remain abstracted in their respective modules.

Some time ago I wrote a blog post about how to organize the Eleventy configuration file where I go a little bit deeper into this topic.

Design tokens

Edit all your preferences (colors, fluid text sizes etc.) in src/_data/designTokens/*.json.

Additional colors, variants and gradients for custom properties are automatically created in src/assets/css/global/base/variables.css based on the colors set in colors.json. If you change names you should edit variables.css as well and check if the names are used elsewhere in the template.
Admittedly, I went a little bit crazy there with the new CSS color syntax stuff.

In the style guide you can see how everything turns out.

Details

The <custom-details> WebC component has a corresponding Nunjucks include.
It uses the <details> and <summary> elements to create a collapsible section and enhances them aesthetically and functionally.

The JavaScript for the <custom-details> component adds functionality to buttons to expand and collapse the sections with one action. When JavaScript is disabled, the sections are still accessible and collapsible, but the extra buttons are hidden.

On page load, it checks if a hash corresponding to a details ID exists in the URL. If such an ID is found, the corresponding details section is programmatically opened, allowing direct navigation to an open section from a shared URL.

The sorting is set by default on “alphabetic”, but you can also pass in “shuffle” or “reverse” as a parameter (directly in the details.njk partial).

Usage

{% set itemList = collections.docs %}{% include 'partials/details.njk' %}

Example

You are in the middle of a custom details component!

Easteregg

The <custom-easteregg> component is by default in the base layout in src/_layouts/base.njk. Just delete the two lines if you don’t want to use it. The component is
designed to trigger a confetti effect when a user types a specific keyword sequence. It uses the dynamic import of the canvas-confetti library to render custom-shaped particles based on user input.

Defaults:

  • Keywords: "eleventy", "excellent"
  • Shape: "⭐️"
  • Particle Count: 30

Customizable Attributes:

  • keyword: custom keyword
  • shape: custom shape for the confetti particles using emojis or text
  • particle-count: number of particles to release during the effect
<script type="module" src="/assets/scripts/components/custom-easteregg.js"></script>
<custom-easteregg keyword="yay" shape="🌈" particle-count="50"></custom-easteregg>
Favicons

All “necessary” favicons are in src/assets/images/favicon, and copied over to the root of the output folder.

I chose the sizes based on the recommendations from the How to Favicon article on evilmartians.com.

You can place them in that directory manually, or use the script to autmate the process:

npm run favicons

In this case define the SVG icon on which all formats are based on in meta.js:

export const pathToSvgLogo = 'src/assets/svg/misc/logo.svg'; // used for favicon generation

Regardless of whether you generate the icons automatically or create them manually, it is best to keep the names so as not to break any reference to them.

Fonts

This starter uses two custom fonts, Red Hat Display and Inclusive Sans. You can add or delete fonts in src/assets/fonts.

I often create font subsets using the Fontsquirrel Webfont Generator.

Next, edit src/assets/css/global/base/fonts.css.

Add your new font aliases in src/_data/designTokens/fonts.json.

Finally, in src/_includes/head/preloads.njk edit the font preloads.

JavaScript

This starter has no real JS dependency. If JavaScript is not available, components that rely on it – like the theme switcher – will be hidden. If you opted in for the drawer menu, pills will be shown instead.

There are two kinds of bundles for JavaScript in this starter, see .src/_includes/head/js-inline.njk and .src/_includes/head/js-defer.njk.
By default, I include Eleventy’s is-land framework and the theme toggle inline.

You can include more scripts like so:

{% js "inline" %}
 {% include "scripts/your-inline-script.js" %}
{% endjs %}

Same goes for scripts that should be defered:

{% js "defer" %}
 {% include "scripts/your-defered-script.js" %}
{% endjs %}

Scripts stored in src/assets/scripts/components/ are sent to the output folder, while scripts in src/assets/scripts/bundle/ are sent to .src/_includes/scripts/, from where you can include them in the respective bundle.

Some components are enhanced with JavaScript.

Masonry

Masonry layout is not yet a native part of CSS grid. There is a debate if using grid-template-rows: masonry; is actually the best way to implement it.

It should be used carefully so we don’t create confusion with the tabbing order. In version 3 of the starter I made the masonry layout a web component, and no longer a opt-in feature (was: masonry: true in the front matter).

<custom-masonry> is designed to function as a masonry grid by dynamically adjusting item positions based on the available column space and the size of its content. The necessary JavaScript (custom-masonry.js) is loaded only once per component usage due to the data-island="once" attribute.
Optional: pass in layout="50-50" to set a 50% width for each column.

If no JavaScript is available, the grid will fall back to the regular grid layout defined in src/assets/css/global/compositions/grid.css.

<custom-masonry> (children) </custom-masonry>
<custom-masonry layout="50-50"> (children) </custom-masonry>
Open Graph images

You can see a preview of the OG images in a blog post.

They are referenced in meta-info.njk:

<meta
  property="og:image"
  content="{{ meta.url }}
  {% if (layout == 'post') %}/assets/og-images/{{ title | slugify }}-preview.jpeg
  {% else %}{{ meta.opengraph_default }}
  {% endif %}"
/>

To change the look and behaviour of those images and replace the SVG background edit src/common/og-images.njk.

The implementation is based on Bernard Nijenhuis article.

If you want to be inspired, have a look at what Lea is doing here.

Previously, the images were created at build time, but this leads to not rendering the font – if the system executing the build has not installed the font, it will not be used.

A solution would be to always build the page yourself and then place it on the server directly.
Or, as Sophie Koonin does, explicitly specify the system to be used for the build, and select a font that this system has installed by default.

At the moment I have relocated the creation of the images in the development process, so that the font only needs to be installed on your own system. The images are located in src/assets/og-images and are comitted.

This is fine as long as you only work with markdown and the font is always installed on your system. How this works if a CMS is involved remains to be seen 😅.

Consider that the domain is a hard coded value in the front matter in src/common/og-images.njk.

Let me know if you encounter any problems.

Pagination

The blog posts use Eleventy’s pagination feature. The logic for this can be found in tha partial src/_includes/partials/pagination.njk, the layout src/_layouts/blog.njk includes it, how many entries should be on a page is defined in src/pages/blog.md.

If you do not want any pagination at all, it is easiest to set a very high number for the pagination size, for example:

pagination:
  data: collections.posts
  size: 10000

In src/_data_/meta.js you can set some values for the visible content (previous / next buttons) and the aria labels.

You can also hide the number fields between the previous and next buttons by setting paginationNumbers to false.

blog: {
	// other adjustments
	paginationLabel: 'Blog',
	paginationPage: 'Page',
	paginationPrevious: 'Previous',
	paginationNext: 'Next',
	paginationNumbers: true
}

If you want to change the collection that is paginated (by default collections.posts), you must do so in two places: the front matter of the template, src/pages/blog.md:

pagination:
  data: collections.posts

and where the pagination component is included: src/_layouts/blog.njk:

<!-- set collection to paginate -->
{% set collectionToPaginate = collections.posts %}
<!-- if the number of items in the collection is greater than the number of items shown on one page -->
{% if collectionToPaginate.length > pagination.size %}
<!-- include pagination -->
{% include 'partials/pagination.njk' %}
{% endif %}
Platforms (icons)

Find and set your platform icons in src/assets/svg, in the “platform” directory.

In src/_data/personal.yaml you can edit the platforms. The key should be the same as the name of the icon. For example: mastodon: 'https://front-end.social/@lene' and src/assets/svg/platform/mastodon.svg.

https://simpleicons.org/ features a great variety of free SVG icons for popular platforms.

SVG

All SVG icons used in the starter are in src/assets/svg. There is a directory dedicated to the dividers, the platform icons and a general folder called “misc”.

Shortcode

The svg.js shortcode, introduced in version 3, allows for the seamless inclusion of SVG files. Located in src/_config/shortcodes/svg.js, this shortcode requires only the folder and file name of the SVG, omitting the file extension. By default, SVGs are injected with an aria-hidden="true" attribute. The SVGs should be stored in the src/assets/svg directory, and referenced using the format "folder/svg-name".

{% svg "path", "aria-name", "class-name", "inline-style" %}
{% svg "misc/star", "A yellow star icon", "spin", "block-size: 4ex; fill: var(--color-tertiary);" %}

The star icon resoves to:

<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24" aria-label="A yellow star icon" style="block-size: 4ex; fill: var(--color-tertiary)" class="spin"><path> (...) </path></svg>

Tags

This was probably the most opinionated decision: tags have been integrated since version 2.0.

The tags are placed in the front matter of the posts, using the syntax

tags:
  - image
  - feature

or

tags: ['markdown', 'feature']

If you generally do not want any tags to show, it is probably easiest to not create any at all.

Theme (dark / light mode)

With version 2.0 I introduced dark and light mode. This is not intended to be a gimmick, but to support the accessibility of the site. And I tend to read blog posts at night, and if there is no dark mode then I leave the tab for later, and we all know how that ends.

Dark and light mode respects the user agent or operating system settings using the prefers-color-scheme CSS media feature. But there is also a switch in the <footer>.

If you want to change something here, for example replace the button with a sun/moon icon, make sure to preserve accessibility.

The accessible name (“Theme dark” or “Theme light”) is put together by aria-labelledby="theme-switcher-label" resolving to “Theme” and the <button>s “light” and “dark”.

If your site is in another language, you can adjust those values in _data/meta.js, for both the content of the <button>s and their aria-label.

If you remove the “light” and “dark” <button> and you don’t use the heading with the ID theme-switcher-label, the value for the accessible name must be obtained differently.

If you find any accessibility issues, please let me know.

What can be deleted

Everything, of course
This is still meant to be a starter, even though it grew to be more like a template.
If you want to keep the defaults, but get rid of the example content, delete the following files and archives:

  • github.js in src/_data/
  • builtwith.json in src/_data/
  • all files in src/posts
  • the directory and all files in src/docs
  • all pages in src/pages, though you might want to keep index.njk as a starting point
  • You can delete screenshots, blog and gallery in src/assets/images.
    Keep the favicon and template folders though.

If you don’t want to feature any code examples, you may delete the whole stylesheet for syntax highlighting: src/assets/css/global/blocks/code.css.
In general, any CSS block in there is optional.

Youtube

The @slug attribute is used to pass the video ID to the <lite-youtube> element.
@label ist used for the nested <custom-youtube-link> which adds a link to watch on YouTube. This also serves as a fallback in case JS is deactivated.
Uses the Lite YouTube Embed repository.

<custom-youtube @slug="Ah6je_bBSH8" @label="Alberto Ballesteros - Artista Sin Obra"> </custom-youtube>
Alberto Ballesteros - Artista Sin Obra