Get started

Vous pouvez utiliser ce starter comme modèle pour votre site et commencer immédiatement ! Mais quelques ajustements sont nécessaires.

Si vous travaillez avec VS Code je recommande d’installer l’extension “Tailwind CSS IntelliSense”, car elle fonctionne également pour nos classes utilitaires personnalisées. Cela dit, vous constaterez que Tailwind CSS ne fonctionne pas comme vous pourriez vous y attendre. J’explique cela dans un des articles.

Les articles expliquent/montrent également certaines fonctionnalités qui ne sont pas couvertes ici.

“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 and bundles

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 “local” bundle only on pages or components where you need them:

{% css "local" %}
  {% 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.

New in version 4.0: Cascade layers

We now use cascade layers! Up until now, I used the :where() pseudo-class to create low specificity selectors for the reset and compositions. Mayank inspired me to change to cascade layers. We have two major bundles of CSS: everything included in “global” In src/assets/css/global/global.css is now organized in cascade layers. The “local” bundle is for per-page or component CSS, and does not use cascade layers - it has thus a higher specificity, independent of any selector specificity in the global CSS.

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"></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

1516Default

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.1984clickablesquare image

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.

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

Special case: colors

As of version 4.0, you can create colors dynamically. Run npm run colors after setting your color values in src/_data/designTokens/colorsBase.json. This will create / overwrite the required colors.json file in the same directory. These colors become custom properties (e.g. --color-gray-100) and utility classes similar to the Tailwind CSS syntax (for example bg-gray-100, text-gray-900).

If you want to adjust how the colors turn out, edit src/_config/setup/create-colors.js.

Colors placed under shades_neutral or shades_vibrant are converted into scalable palettes. shades_neutral is better for grayish / monochromatic colors, while shades_vibrant is better for colorful palettes. Colors listed under standalone and light_dark are left as they are, light_dark items output a second “subdued” version optimized for dark themes.

// this creates a palette with shades of green, 100 to 900
  "shades_vibrant": [
    {
      "name": "green",
      "value": "#008000"
    }
  ],

Important: If you change the color names, you must edit src/assets/css/global/base/variables.css with your color names. The rest of the CSS files should only reference custom properties set in variables.css.

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. You can also use raster images instead of SVG.

Fonts

This starter uses two custom fonts, Red Hat Display and Atkinson Hyperlegible. 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.

Images

En utilisant le plugin Eleventy Image, il existe trois façons de gérer l’optimisation des images : HTML Transform, syntaxe Markdown et un shortcode Nunjucks. Voir l’article dédié pour approfondir.

Consultez les Attribute Overrides pour les méthodes HTML Transform (1 et 2) pour les remplacements par instance. Ajouter eleventy:ignore à un élément <img> par exemple, ignore cette image.

1. HTML Transform

The HTML Transform automatically processes <img> and <picture> elements in your HTML files as a post-processing step during the build.

<img src="./path/to/image.jpg" alt="alt text">

2. Markdown Syntax

The Markdown syntax creates the <img> element that the HTML Transform plugin is looking for, and then transforms it to the <picture> element (if more than one format is set).

![alt text](/path/to/image.jpg)

3. Nunjucks Shortcode

In Nunjucks templates you can also use a shortcode.

{% image '/path/to/image.jpg', 'alt text' %}
Images Open Graph

Vous pouvez voir un aperçu des images OG dans un article.

Elles sont référencées dans 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.

Consider that the domain is a hard coded value in the front matter.

Important: 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 locally with Markdown, and the font is always installed on your system. If you work with a CMS you must add the font cto where the site is built. Some CMS let you add fonts into the prebuild config. Otherwise it usually falls down to the Ubuntu Font Family. You still have to adjust the script src/_config/events/svg-to-jpeg.js to something like:

import fs from 'fs';
import Image from '@11ty/eleventy-img';
export const svgToJpeg = async function () {
  const ogImagesDir = 'dist/assets/og-images/';
  fs.readdir(ogImagesDir, (err, files) => {
    if (!!files && files.length > 0) {
      files.forEach(fileName => {
        if (fileName.endsWith('.svg')) {
          let imageUrl = ogImagesDir + fileName;
          Image(imageUrl, {
            formats: ['jpeg'],
            outputDir: './' + ogImagesDir,
            filenameFormat: function (id, src, width, format, options) {
              let outputFileName = fileName.substring(0, fileName.length - 4);
              return `${outputFileName}.${format}`;
            }
          });
        }
      });
    } else {
      console.log('⚠ No social images found');
    }
  });
};

Regenerating OG images

As you make changes, possibly adjust the title of your post or delete it, the images add up in src/assets/og-images. To delete this folder and regenerate all images, you can run npm run clean:og.

Let me know if you encounter any problems.

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>
Pagination

Les articles utilisent la fonctionnalité de pagination d’Eleventy. La logique se trouve dans le partial src/_includes/partials/pagination.njk, le nombre d’entrées par page est défini dans src/pages/articles.njk.

Si vous ne voulez pas de pagination du tout, le plus simple est de définir un nombre très élevé pour la taille de la pagination, par exemple :

pagination:
  data: collections.posts
  size: 10000

Dans src/_data/meta.js vous pouvez définir certaines valeurs pour le contenu visible (boutons précédent/suivant) et les labels aria.

Vous pouvez également masquer les champs numériques entre les boutons précédent et suivant en définissant paginationNumbers à false.

articles: {
	// autres ajustements
	pagnationLabel: 'Articles',
	paginationPage: 'Page',
	paginationPrevious: 'Précédent',
	paginationNext: 'Suivant',
	paginationNumbers: true
}

Si vous souhaitez modifier la collection paginée (par défaut collections.posts), vous devez le faire à deux endroits : le front matter du template, src/pages/articles.njk:

pagination:
  data: collections.posts

et là où le composant de pagination est inclus, src/pages/articles.njk:

<!-- définir la collection à paginer -->
{% set collectionToPaginate = collections.posts %}
<!-- définir les paramètres de pagination cible dans meta.js -->
{% set metaKey = "articles" %}
<!-- si le nombre d'éléments dans la collection est supérieur au nombre d'éléments affichés sur une page -->
{% if collectionToPaginate.length > pagination.size %}
<!-- inclure la 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>

Schema

Le balisage Schema fournit un contexte supplémentaire pour les moteurs de recherche et les lecteurs d’écran. Le template de schéma principal est inclus dans <head> via src/_includes/head/schema.njk. Les nouveaux schémas doivent être placés dans src/_includes/schemas/.

Pour utiliser le schéma “ArticlePosting”, définissez la clé schema dans le front matter :

---
schema: ArticlePosting
---

To use an Event schema for example, create a template at src/_includes/schemas/Event.njk, with something similar to:

<script type="application/ld+json">
  {
    "@context": "http://schema.org",
    "@type": "Event",
    "location": {
      "@type": "Place",
      "address": {
        "@type": "PostalAddress",
        "addressLocality": "{{ event.data.place.city }}",
        "postalCode": "{{ event.data.place.plz }}",
        "streetAddress": "{{ event.data.place.street }}"
      },
      "name": "{{ event.data.place.name }}"
    },
    "name": "{{ event.data.title }}",
    "description": "{{ event.data.description }}",
    "startDate": "{{ event.data.date }}",
    "performer": "{{ event.data.artist }}"
  }
</script>

And reference it in the front matter:

---
schema: Event
---
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 articles 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.

Video

YouTube

<custom-youtube> is a wrapper around Lite YouTube Embed, optimizing video playback for performance.

@slug video ID
@start (optional) start time in seconds
@label Used for accessibility and the <custom-youtube-link> fallback
@poster (optional) custom poster image URL (with https://v1.opengraph.11ty.dev/)
@posterSize (optional, default: ‘auto’) size passed in to custom poster
@jsapi (optional, default: ‘undefined’) Enables JavaScript API support.

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

PeerTube

<custom-peertube> is a wrapper around PeerTube’s embed system.

@instance PeerTube domain hosting the video
@embed-slug – The unique ID used in the embed URL.
@start (optional) start time
@slug unique video identifier for direct links in the <custom-peertube-link> fallback
@label Used for accessibility and the <custom-peertube-link> fallback.

<custom-peertube
  @instance="fair.tube"
  @slug="8opkviMc2iDUYMwJzG1FQ4"
  @embed-slug="3bd0b70e-7890-4216-a123-2052363645ff"
  @label='Back at the Herperduin 💦 - 28/09/2024'>
</custom-peertube>
PeerTube Back at the Herperduin 💦 - 28/09/2024
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, 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.