├── .gitattributes ├── src ├── views │ ├── robots.txt │ ├── img │ │ └── svg │ │ │ ├── mstile-70x70.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── mstile-144x144.png │ │ │ ├── mstile-150x150.png │ │ │ ├── mstile-310x150.png │ │ │ ├── mstile-310x310.png │ │ │ ├── Android Devices.png │ │ │ ├── Windows Devices.png │ │ │ ├── favicon-128x128.png │ │ │ ├── favicon-196x196.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon-76x76.png │ │ │ ├── apple-touch-icon-114x114.png │ │ │ ├── apple-touch-icon-120x120.png │ │ │ ├── apple-touch-icon-144x144.png │ │ │ ├── apple-touch-icon-152x152.png │ │ │ ├── apple-touch-icon-167x167.png │ │ │ ├── apple-touch-icon-180x180.png │ │ │ └── Apple Devices (iOS, iPadOS).png │ ├── pages │ │ ├── components.md │ │ ├── pages.11tydata.js │ │ └── features.md │ ├── _data │ │ └── site.js │ ├── category.njk │ ├── _includes │ │ ├── components │ │ │ ├── footer.njk │ │ │ ├── head.njk │ │ │ └── header.njk │ │ └── layouts │ │ │ ├── page.njk │ │ │ ├── base.njk │ │ │ └── single-post.njk │ ├── posts │ │ ├── what-is-accessibility.njk │ │ ├── what-do-you-want.njk │ │ ├── this-is-published-later.njk │ │ ├── posts.11tydata.js │ │ ├── auto-tag-archives.njk │ │ ├── index.njk │ │ └── handle-dates.njk │ ├── category-archive.njk │ ├── sitemap.njk │ ├── index.njk │ ├── manifest.json │ └── svg-previews.njk ├── main.js └── style.css ├── tailwind.config.js ├── package.json ├── .gitignore ├── README.md └── .eleventy.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/views/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | 3 | 4 | Sitemap: https://ta11y.app/sitemap.xml -------------------------------------------------------------------------------- /src/views/img/svg/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/mstile-70x70.png -------------------------------------------------------------------------------- /src/views/img/svg/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/favicon-16x16.png -------------------------------------------------------------------------------- /src/views/img/svg/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/favicon-32x32.png -------------------------------------------------------------------------------- /src/views/img/svg/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/favicon-96x96.png -------------------------------------------------------------------------------- /src/views/img/svg/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/mstile-144x144.png -------------------------------------------------------------------------------- /src/views/img/svg/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/mstile-150x150.png -------------------------------------------------------------------------------- /src/views/img/svg/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/mstile-310x150.png -------------------------------------------------------------------------------- /src/views/img/svg/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/mstile-310x310.png -------------------------------------------------------------------------------- /src/views/img/svg/Android Devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/Android Devices.png -------------------------------------------------------------------------------- /src/views/img/svg/Windows Devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/Windows Devices.png -------------------------------------------------------------------------------- /src/views/img/svg/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/favicon-128x128.png -------------------------------------------------------------------------------- /src/views/img/svg/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/favicon-196x196.png -------------------------------------------------------------------------------- /src/views/img/svg/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/views/img/svg/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/views/img/svg/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/views/img/svg/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /src/views/img/svg/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /src/views/img/svg/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /src/views/img/svg/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /src/views/img/svg/apple-touch-icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/apple-touch-icon-167x167.png -------------------------------------------------------------------------------- /src/views/img/svg/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /src/views/img/svg/Apple Devices (iOS, iPadOS).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkBuskbjerg/ta11y/HEAD/src/views/img/svg/Apple Devices (iOS, iPadOS).png -------------------------------------------------------------------------------- /src/views/pages/components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "A dream comin' true" 3 | --- 4 | 5 | I just dream of building out some really simple starter stuff, that I always use on my projects. 6 | -------------------------------------------------------------------------------- /src/views/_data/site.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "Ta11y.app – 11ty starter project", 3 | url: "https://ta11y.app", 4 | language: "en", 5 | description: "I am writing about my experiences as a naval navel-gazer.", 6 | author: { 7 | name: "Your Name Here", 8 | email: "youremailaddress@example.com", 9 | url: "https://example.com/about-me/", 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/views/category.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Alle kategorier 3 | layout: single-post 4 | seo: 5 | robots: noindex 6 | --- 7 | 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | // tailwind.config.js 2 | 3 | // const plugin = require("tailwindcss/plugin"); 4 | // const defaultTheme = require("tailwindcss/defaultTheme"); 5 | 6 | module.exports = { 7 | content: ['./_site/**/*.html', './src/**/*.{md,html,njk}'], 8 | purge: false, // This is handled through postcss.config.js 9 | theme: {}, 10 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')], 11 | }; 12 | -------------------------------------------------------------------------------- /src/views/_includes/components/footer.njk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Alpine from 'alpinejs'; 2 | // import persist from '@alpinejs/persist'; 3 | 4 | window.Alpine = Alpine; 5 | 6 | // Alpine.plugin(persist); 7 | 8 | // Start Alpine when the page is ready. 9 | window.addEventListener('DOMContentLoaded', () => { 10 | console.log('Dom is loaded'); 11 | Alpine.start(); 12 | }); 13 | 14 | window.addEventListener('alpine:init', () => { 15 | // Alpine.data('dropdown', dropdown) 16 | }); 17 | -------------------------------------------------------------------------------- /src/views/pages/pages.11tydata.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = async () => { 4 | return { 5 | layout: "page", 6 | tags: "page", 7 | eleventyComputed: { 8 | permalink: (data) => { 9 | const rootPath = path.join(__dirname, "../"); 10 | const pagePath = path.relative(rootPath, data.page.inputPath); 11 | return "/" + path.parse(pagePath).name + "/"; 12 | }, 13 | }, 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/views/posts/what-is-accessibility.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: What is accessibility? 3 | tags: ['A11Y', 'Tutorial'] 4 | draft: true 5 | date: 2023-03-27 6 | seo: 7 | title: "What is accessibility? And how to get better with every build" 8 | description: "Ta11y is an 11ty starter-project built with accessibility in mind. But what does that mean? And how do you get even better by every step of your build?" 9 | --- 10 | 11 | This is a post about A11Y on the web -------------------------------------------------------------------------------- /src/views/_includes/layouts/page.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 |
5 |

6 | {%- if draft -%} 7 | [ DRAFT ] 8 | {%- endif -%} 9 | {%- if scheduled -%} 10 | [ Scheduled ] 11 | {%- endif -%} 12 | 13 | {{- title -}} 14 |

15 | 16 | {{- content | safe -}} 17 | 18 |
-------------------------------------------------------------------------------- /src/views/posts/what-do-you-want.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Handle draft-posts within your 11ty-build 3 | tags: ['Test'] 4 | date: 2023-04-11 5 | seo: 6 | title: "Easily handle draft posts in 11ty (eleventy)" 7 | description: "With ta11y you can easily handle draft-posts out-of-the-box with 11ty (eleventy). Read more about the setup here." 8 | --- 9 | 10 |

Here comes an explanation of the setup. And how to use it.

11 | 12 |

Probably also gonna make some text about how to copy it into your own setup.

-------------------------------------------------------------------------------- /src/views/category-archive.njk: -------------------------------------------------------------------------------- 1 | --- 2 | pagination: 3 | data: collections 4 | size: 1 5 | alias: tag 6 | eleventyComputed: 7 | title: 'Articles in category: "{{ tag }}"' 8 | permalink: /category/{{ tag | slugify }}/ 9 | layout: page 10 | seo: 11 | robots: noindex 12 | --- 13 | 14 |
    15 | {%- set taglist = collections[tag] -%} 16 | {%- for post in taglist | reverse -%} 17 |
  1. 18 | {{- post.data.title -}} 19 |
  2. 20 | {%- endfor -%} 21 |
22 | 23 |

See all categories.

-------------------------------------------------------------------------------- /src/views/posts/this-is-published-later.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Scheduled posts will be published later 3 | date: 2099-12-12 4 | tags: ['Example', 'Dates', 'Two words'] 5 | seo: 6 | title: "How to handle scheduled posts in 11ty (eleventy)" 7 | description: "With the right setup you can easily handle scheduled posts in your 11ty-setup. Read along here." 8 | --- 9 | 10 |

If the date of the post is later than the current date for the build the value of draft will automatically be set to `true`.

11 | 12 |

The only downside

13 |

A downside to this approach is, that content is only handled on build. So if you only trigger your build manually scheduled posts won't be published.

-------------------------------------------------------------------------------- /src/views/_includes/layouts/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | {%- include 'components/head.njk' -%} 4 | 5 | 6 | {%- include 'components/header.njk' -%} 7 |
8 |
9 | {{- content | safe -}} 10 |
11 |
12 | {%- include 'components/footer.njk' -%} 13 | 14 | {# #} 15 | 16 | -------------------------------------------------------------------------------- /src/views/_includes/layouts/single-post.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 |
5 | {%- if scheduled -%} 6 | [ Scheduled ] 7 | {%- endif -%} 8 | {%- if draft -%} 9 | [ DRAFT ] 10 | {%- endif -%} 11 | 12 | {%- if tags -%} 13 | {%- for tag in tags -%} 14 | {{ tag }} 15 | {%- endfor -%} 16 | {%- endif -%} 17 |
18 |
19 |

20 | {{- title -}} 21 |

22 | 23 | {{- content | safe -}} 24 | 25 |
-------------------------------------------------------------------------------- /src/views/sitemap.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: ta11y.app - Sitemap 3 | description: A collection of all the content in XML format 4 | permalink: /sitemap.xml 5 | eleventyExcludeFromCollections: true 6 | --- 7 | 8 | 9 | 10 | {%- for page in collections.all -%} 11 | {%- if not page.data.draft -%} 12 | {%- if page.data.seo.robots == 'noindex' -%} 13 | {%- elif page.data.seo.sitemap == false -%} 14 | {%- else -%} 15 | 16 | {{- site.url -}}{{- page.url | url -}} 17 | {{- page.date.toISOString() -}} 18 | {{- page.data.changeFreq if page.data.changeFreq else "monthly" -}} 19 | 20 | {%- endif -%} 21 | {%- endif -%} 22 | {%- endfor -%} 23 | -------------------------------------------------------------------------------- /src/views/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Yo world - Ta11y is in tha' house 3 | layout: base 4 | seo: 5 | title: "ta11y.app – an 11ty starter template with TailwindCSS and AlpineJS" 6 | description: "A simple 11ty (eleventy) starter template with a lot of features already setup for you." 7 | --- 8 | 9 | 10 | 11 |
12 |
13 |

Easily build accessible websites with 11ty, TailwindCSS and AlpineJS

14 |
15 |
16 |

Ta11y is a simple 11ty template prepared to easily setup your next project with 11ty, TailwindCSS and AlpineJS.

17 | 18 |

This is a Level 2 Sample Heading

19 |

20 |

This is a Level 3 Sample Heading

21 |

22 |

This is a Level 4 Sample Heading

23 |

24 |
This is a Level 5 Sample Heading
25 |
26 |
-------------------------------------------------------------------------------- /src/views/posts/posts.11tydata.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | module.exports = async () => { 4 | return { 5 | layout: "single-post", 6 | eleventyComputed: { 7 | permalink(data) { 8 | const isScheduled = new Date(data.date) > new Date(); 9 | const isDraft = !data.draft; 10 | if (process.env.ELEVENTY_ENV !== "production") { 11 | // If NOT Production build, process all files in this directory 12 | return data.permalink || "/posts/{{ title | slug }}/index.html"; 13 | } else { 14 | // If production build, exclude drafts and scheduled posts 15 | if (isDraft || !isScheduled) { 16 | return data.permalink || "/posts/{{ title | slug }}/index.html"; 17 | } 18 | return false; 19 | } 20 | }, 21 | scheduled(data) { 22 | const isScheduled = new Date(data.date) > new Date(); 23 | return isScheduled; 24 | }, 25 | }, 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/views/posts/auto-tag-archives.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: How auto-generated tag-archives work in Ta11y 3 | tags: ['Guide'] 4 | date: 2023-04-28 5 | seo: 6 | description: "ta11y starter-project for 11ty comes with ready-set-go auto-generated tag-archives out-of-the-box. Read more about it here" 7 | --- 8 | 9 |

Ta11y uses tags to sort all content in posts and automatically generate tag-archives with eleventy pagination.

10 | 11 | {%- highlight "html" -%} 12 | // Sample YAML header from post 13 | --- 14 | title: "This is the first post" 15 | tags: ['JavaScript', 'Tutorial'] 16 | --- 17 | {%- endhighlight -%} 18 | 19 | {%- highlight "html" -%} 20 | // Sample YAML header from post 21 | --- 22 | title: "This is the second post" 23 | tags: ['UX Design', 'Tutorial'] 24 | --- 25 | {%- endhighlight -%} 26 | 27 |

This will generate three tag-archive pages:

28 | 29 |

/category/javascript/ 👉 including the first post

30 |

/category/ux-design/ 👉 including the second post

31 |

/category/tutorial/ 👉 including both posts

-------------------------------------------------------------------------------- /src/views/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ta11y", 3 | "short_name": "ta11y", 4 | "description": "This is the ta11y 11ty starter project", 5 | "start_url": "/", 6 | "display": "standalone", 7 | "display_override": ["window-control-overlay", "standalone", "browser"], 8 | "lang": "en", 9 | "background_color": "#ffffff", 10 | "theme_color": "#000000", 11 | "orientation": "portrait-primary", 12 | "categories": ["education", "accessibility"], 13 | "dir": "ltr", 14 | "shortcuts": [ 15 | { 16 | "name": "All articles", 17 | "url": "/posts/", 18 | "description": "Read the latest articles" 19 | }, 20 | { 21 | "name": "Features", 22 | "url": "/features/", 23 | "description": "See a list of all avaiable features" 24 | } 25 | ], 26 | "icons": [ 27 | { 28 | "src": "/img/svg/android-chrome-192x192.png", 29 | "sizes": "192x192", 30 | "type": "image/png" 31 | }, 32 | { 33 | "src": "/img/svg/android-chrome-512x512.png", 34 | "sizes": "512x512", 35 | "type": "image/png", 36 | "purpose": "maskable any" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/views/pages/features.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Features in ta11y" 3 | --- 4 | 5 | ## AlpineJS 6 | 7 | All interactive elements are handled with AlpineJS. AlpineJS is a lightweight alternative to VueJS. 8 | 9 | This gives you a double-whammy advantage. First of all you get to enjoy the speed of building your page with a lightweight framework such as Alpine.js. But it also means that you will easily be able to rewrite and replace AlpineJS with 10 | VueJS if you need to later on in your project. 11 | 12 | [Visit the docs for AlpineJS](https://alpinejs.dev/) 13 | 14 | ## TailwindCSS 3.3.2 15 | 16 | Ta11y is setup to use TailwindCSS 3.x.x and ready with Typography and Forms plugin out of the box. This means you get to use TailwindCSS for styling your website with everything handled, purged and optimized for a fast user experience. 17 | 18 | [Visit the docs for TailwindCSS](https://tailwindcss.com/) 19 | 20 | ## 11ty 2.0.1 21 | 22 | This project runs wild on 11ty 2.0. Enjoy. 23 | 24 | [Visit the docs for 11ty](https://www.11ty.dev/) 25 | 26 | ## Versatile templating options 27 | 28 | Use Nunjucks or Markdown interchangeably. 29 | 30 | ## Ready to boost your visibility in search engines (SEO) 31 | 32 | - YAML-structure to boost metadata is enabled 33 | - Preconfigured robots.txt 34 | - Preconfigured sitemap.xml 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ta11y", 3 | "version": "0.1.0", 4 | "description": "A simple 11ty starter project with complete TailwindCSS integration", 5 | "main": "index.js", 6 | "scripts": { 7 | "prod": "cross-env NODE_ENV=production ELEVENTY_ENV=production npm-run-all clean -p prod:*", 8 | "prod:11ty": "eleventy --quiet", 9 | "prod:css": "npx tailwindcss -i src/style.css -o _site/style.min.css --minify", 10 | "clean": "rimraf _site", 11 | "dev": "cross-env NODE_ENV=development ELEVENTY_ENV=development npm-run-all clean -p dev:*", 12 | "dev:11ty": "eleventy --serve --quiet" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/MarkBuskbjerg/ta11y.git" 17 | }, 18 | "keywords": [ 19 | "11ty", 20 | "tailwindcss", 21 | "alpine", 22 | "nunjucks", 23 | "ssg", 24 | "jamstack" 25 | ], 26 | "author": "Mark Buskbjerg", 27 | "license": "ISC", 28 | "bugs": { 29 | "url": "https://github.com/MarkBuskbjerg/ta11y/issues" 30 | }, 31 | "homepage": "https://github.com/MarkBuskbjerg/ta11y#readme", 32 | "devDependencies": { 33 | "@11ty/eleventy": "^2.0.1", 34 | "@11ty/eleventy-img": "^3.1.1", 35 | "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", 36 | "@tailwindcss/forms": "^0.5.3", 37 | "@tailwindcss/typography": "^0.5.9", 38 | "autoprefixer": "^9.7.6", 39 | "base-64": "^0.1.0", 40 | "cross-env": "^7.0.2", 41 | "date-fns": "^2.29.3", 42 | "eleventy-plugin-pwa-v2": "^1.0.0", 43 | "fs": "^0.0.1-security", 44 | "npm-run-all": "^4.1.5", 45 | "postcss": "^8.4.23", 46 | "request": "^2.88.2", 47 | "rimraf": "^3.0.2", 48 | "tailwindcss": "^3.3.2", 49 | "vite": "^4.2.2" 50 | }, 51 | "dependencies": { 52 | "alpinejs": "^3.12.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/views/svg-previews.njk: -------------------------------------------------------------------------------- 1 | --- 2 | pagination: 3 | data: collections.allPosts 4 | size: 1 5 | alias: preview 6 | permalink: "/img/social-preview-images/{{ preview.data.date | datefmt }}-{{ preview.data.title | slug }}-preview.svg" 7 | eleventyExcludeFromCollections: true 8 | --- 9 | 10 | 11 | 12 | {% set titleInLines = preview.data.title | splitlines %} 13 | {% set numberOfLines = titleInLines.length %} 14 | {% if numberOfLines == 1 %} 15 | {% set verticalStartingPoint = 340 %} 16 | {% elseif numberOfLines == 2 %} 17 | {% set verticalStartingPoint = 290 %} 18 | {% elseif numberOfLines == 3 %} 19 | {% set verticalStartingPoint = 250 %} 20 | {% elseif numberOfLines == 4 %} 21 | {% set verticalStartingPoint = 210 %} 22 | {% endif %} 23 | 24 | 25 | 26 | 27 | ta11y 28 | 29 | 30 | 31 | 32 | 33 | {{ preview.date | datefmt }} 34 | 35 | 36 | 37 | {% for line in titleInLines %} 38 | {{line}} 39 | {% endfor %} 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/views/_includes/components/head.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{- seo.title or title or site.title -}} 5 | 6 | 7 | 8 | {%- if seo.robots -%} 9 | 10 | {%- endif -%} 11 | 12 | {%- if seo.canonical -%} 13 | 14 | {%- else -%} 15 | 16 | {%- endif -%} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | {%- set css -%} 39 | {%- include "../../../style.css" -%} 40 | {%- endset -%} 41 | {# #} 42 | 43 | 46 | -------------------------------------------------------------------------------- /src/views/posts/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Enjoy all the articles 3 | layout: page 4 | permalink: /posts/ 5 | seo: 6 | robots: noindex 7 | --- 8 | 9 |
10 | {%- for post in collections.allPosts | reverse -%} 11 |
12 |
13 | {%- if post.data.date -%} 14 | 15 | {%- else -%} 16 | 17 | {%- endif -%} 18 | 19 | {%- if post.data.scheduled -%} 20 |

Scheduled

21 | {%- endif -%} 22 | 23 | {%- if post.data.draft -%} 24 |

Draft

25 | {%- endif -%} 26 | 27 | {%- for tag in post.data.tags -%} 28 | {{ tag }} 29 | {%- endfor -%} 30 |
31 |
32 |

33 | 34 | {{- post.data.title -}} 35 |

36 |

Illo sint voluptas. Error voluptates culpa eligendi. Hic vel totam vitae illo. Non aliquid explicabo necessitatibus unde. Sed exercitationem placeat consectetur nulla deserunt vel iusto corrupti dicta laboris incididunt.

37 |
38 |
39 | {%- endfor -%} 40 |
41 |
42 |

See all categories.

43 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | _site 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Microbundle cache 60 | .rpt2_cache/ 61 | .rts2_cache_cjs/ 62 | .rts2_cache_es/ 63 | .rts2_cache_umd/ 64 | 65 | # Optional REPL history 66 | .node_repl_history 67 | 68 | # Output of 'npm pack' 69 | *.tgz 70 | 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | # dotenv environment variables file 75 | .env 76 | .env.test 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | 81 | # Next.js build output 82 | .next 83 | 84 | # Nuxt.js build / generate output 85 | .nuxt 86 | dist 87 | 88 | # Gatsby files 89 | .cache/ 90 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 91 | # https://nextjs.org/blog/next-9-1#public-directory-support 92 | # public 93 | 94 | # vuepress build output 95 | .vuepress/dist 96 | 97 | # Serverless directories 98 | .serverless/ 99 | 100 | # FuseBox cache 101 | .fusebox/ 102 | 103 | # DynamoDB Local files 104 | .dynamodb/ 105 | 106 | # TernJS port file 107 | .tern-port 108 | 109 | # public folder 110 | public/ 111 | public/assets/main.bundle.css 112 | public/assets/main.bundle.css 113 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /** 6 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML 7 | * Based on https://github.com/chriskempson/tomorrow-theme 8 | * @author Rose Pritchard 9 | */ 10 | 11 | code[class*='language-'], 12 | pre[class*='language-'] { 13 | color: #ccc; 14 | background: none; 15 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 16 | font-size: 1em; 17 | text-align: left; 18 | white-space: pre; 19 | word-spacing: normal; 20 | word-break: normal; 21 | word-wrap: normal; 22 | line-height: 1.5; 23 | 24 | -moz-tab-size: 4; 25 | -o-tab-size: 4; 26 | tab-size: 4; 27 | 28 | -webkit-hyphens: none; 29 | -moz-hyphens: none; 30 | -ms-hyphens: none; 31 | hyphens: none; 32 | } 33 | 34 | /* Code blocks */ 35 | pre[class*='language-'] { 36 | padding: 1em; 37 | margin: 0.5em 0; 38 | overflow: auto; 39 | } 40 | 41 | :not(pre) > code[class*='language-'], 42 | pre[class*='language-'] { 43 | background: #2d2d2d; 44 | } 45 | 46 | /* Inline code */ 47 | :not(pre) > code[class*='language-'] { 48 | padding: 0.1em; 49 | border-radius: 0.3em; 50 | white-space: normal; 51 | } 52 | 53 | .token.comment, 54 | .token.block-comment, 55 | .token.prolog, 56 | .token.doctype, 57 | .token.cdata { 58 | color: #999; 59 | } 60 | 61 | .token.punctuation { 62 | color: #ccc; 63 | } 64 | 65 | .token.tag, 66 | .token.attr-name, 67 | .token.namespace, 68 | .token.deleted { 69 | color: #e2777a; 70 | } 71 | 72 | .token.function-name { 73 | color: #6196cc; 74 | } 75 | 76 | .token.boolean, 77 | .token.number, 78 | .token.function { 79 | color: #f08d49; 80 | } 81 | 82 | .token.property, 83 | .token.class-name, 84 | .token.constant, 85 | .token.symbol { 86 | color: #f8c555; 87 | } 88 | 89 | .token.selector, 90 | .token.important, 91 | .token.atrule, 92 | .token.keyword, 93 | .token.builtin { 94 | color: #cc99cd; 95 | } 96 | 97 | .token.string, 98 | .token.char, 99 | .token.attr-value, 100 | .token.regex, 101 | .token.variable { 102 | color: #7ec699; 103 | } 104 | 105 | .token.operator, 106 | .token.entity, 107 | .token.url { 108 | color: #67cdcc; 109 | } 110 | 111 | .token.important, 112 | .token.bold { 113 | font-weight: bold; 114 | } 115 | .token.italic { 116 | font-style: italic; 117 | } 118 | 119 | .token.entity { 120 | cursor: help; 121 | } 122 | 123 | .token.inserted { 124 | color: green; 125 | } 126 | -------------------------------------------------------------------------------- /src/views/posts/handle-dates.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Easily handle date-formatting 3 | layout: single-post 4 | date: 2023-04-26 5 | tags: ['Tutorial', 'Dates'] 6 | seo: 7 | title: "Easily handle date formatting in 11ty (eleventy)" 8 | description: "With ta11y you can easily handle date-formatting out-of-the-box with 11ty (eleventy). Read more about the setup here." 9 | --- 10 | 11 |

Out of the box ta11y use date-fns filter to format dates across the template. In this article I'll dive into how you can change the format globally or introduce new unique format filters.

12 | 13 |

To learn more about formatting dates with date-fns visit the date-fns docs 14 |

15 | 16 |

How dates are formatted as default

17 |

By default dates are formatted as December 12th - 2023.

18 | 19 |

Initialize the filter

20 |

First we need to Initialize the filter in our 11ty setup. This is done in the file .eleventy.js.

21 | 22 | {%- highlight "js" -%} 23 | // In .eleventy.js 24 | 25 | const { format } = require("date-fns"); 26 | 27 | module.exports = function (eleventyConfig) { 28 | // Date filter 29 | // ----------------------- 30 | eleventyConfig.addFilter("datefmt", (contentDate) => { 31 | return format(contentDate, "LLL d'th' - yyyy"); 32 | }); 33 | } 34 | {%- endhighlight -%} 35 | 36 |

Now we can use this filter on all date values in our eleventy-install. Like in this snip of code from the collection of posts:

37 | 38 | {%- highlight "html" -%} 39 | {%- raw -%} 40 | {%- if post.data.date -%} 41 | <time> datetime="{{- post.data.date -}}">{{- post.data.date | datefmt -}}</time> 42 | {%- else -%} 43 | <time> datetime="{{ page.date }}">{{- page.date | datefmt -}}</time> 44 | {%- endif -%} 45 | {%- endraw -%} 46 | {%- endhighlight -%} 47 | 48 | {%- highlight "html" -%} 49 | {%- raw -%} 50 | {{- page.date | datefmt -}} 51 | {% endraw %} 52 | {%- endhighlight -%} 53 | 54 |

What if you wanted to include the weekday?

55 |

So ... let's say you wanted to display the weekday in the formatted date?

56 |

Easy enough:

57 | 58 |

Just jump straight into .eleventy.js and alter this line:

59 | 60 | {%- highlight "js" -%} 61 | // From this 62 | eleventyConfig.addFilter("datefmt", (contentDate) => { 63 | return format(contentDate, "LLL d'th' - yyyy"); 64 | }); 65 | 66 | // To this 67 | eleventyConfig.addFilter("datefmt", (contentDate) => { 68 | return format(contentDate, "eeee, LLL d'th' - yyyy"); 69 | }); 70 | 71 | {%- endhighlight -%} 72 | 73 |

That is all it takes

-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A simple 11ty startersite using Tailwind CSS and AlpineJS 2 | 3 | A simple boilerplate built with Eleventy and TailwindCSS. 4 | 5 | This is a personal project and I'm still developing and tailoring it to my exact needs. I use it as my personal boilerplate for new projects. Hopefully you'll find inspiration or a good use for it yourself. 6 | 7 | I found a lot of inspiration from other open starter packs on 11ty.dev. Special thanks to [Skeleventy](https://github.com/josephdyer/skeleventy) and [Ta11ls](https://github.com/danfascia/tai11s). A lot of my structure is inspired by those 8 | starters. 9 | 10 | I try have it setup with a nice and simple starter design. But my main focus is to implement more technical parts like easy draft, scheduled post and navigation functionality. 11 | 12 | ## Features 13 | 14 | - Get started with a simple build process. 15 | - Easily handle drafts and scheduled posts both in production and development mode. 16 | - Built with accessibility (A11Y) in mind - feel free to suggest optimizations. 17 | - Layouts are build with Search Engine Optimization (SEO) in mind. 18 | - Blog / article section (will be ready with categories). 19 | - Templated with Nunjucks (.njk). 20 | - Free to use Nunjucks (.njk), Markdown (.md) or Liquid (.liquid) for all 21 | - Handle dates easily (use date-fns filters) 22 | - Category structure based on tags 23 | - Syntax highlighting 24 | 25 | ## Installation 26 | 27 | Download to your preferred local folder and run `npm install` from your terminal. 28 | 29 | To **start developing** type: 30 | 31 | `npm run dev` 32 | 33 | Your new Ta11y site will build and run in your preferred browser. 34 | 35 | To **run in production** type: 36 | 37 | `npm run prod` 38 | 39 | This displays everything totally purged and without any drafts and scheduled posts visible. 40 | 41 | ## Folder structure 42 | 43 | Everything is contained inside the 'src' folder. Organized like this 44 | 45 | - views 46 | -- pages 47 | -- posts 48 | - \_includes 49 | 50 | ## Why did I build Ta11y? 51 | 52 | Well. I needed something specifically tailored for my own projects. I've open-sourced it for others to use for either their own projects or just as inspiration. 53 | 54 | If you find it helpful in any way => I'm happy. If you have questions => Don't be a stranger :) If you find a bug => Please open an issue. 55 | 56 | ## Customize quickly from site.js data-file 57 | 58 | ## Roadmap and ideas 59 | 60 | Well. Just stuff I'd love to discover and get a handle on with this starter project. 61 | 62 | - Handling image and SVG-optimization 63 | - Better template-data in the of the page. Right now it is lacking a lot. 64 | - Custom 404 (ready for Netlify). 65 | - Write about how the site is setup. 66 | - Add better webmanifest-data. 67 | -------------------------------------------------------------------------------- /src/views/_includes/components/header.njk: -------------------------------------------------------------------------------- 1 |
2 |
3 | 54 |
55 |
-------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const fs = require('fs'); 3 | const Image = require('@11ty/eleventy-img'); 4 | const { format } = require('date-fns'); 5 | const syntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight'); 6 | const tailwind = require('tailwindcss'); 7 | const postCss = require('postcss'); 8 | const pluginPWA = require('eleventy-plugin-pwa-v2'); 9 | 10 | module.exports = function (eleventyConfig) { 11 | // Registering a async filter to all nunjuck templates 12 | // unfortunally it didn't saw the possiblity to register 13 | // 'global' async filters (say for all other templating engines 14 | // but I'm fine with this solution 15 | eleventyConfig.addNunjucksAsyncFilter('postcss', (cssCode, done) => { 16 | // Here is where the magic will happen 17 | postCss([ 18 | tailwind({ 19 | // config for tailwind goes here 20 | content: ['./_site/**/*.html', './src/**/*.{md,html,njk}'], 21 | // purge: false, // This is handled through postcss.config.js 22 | theme: {}, 23 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')], 24 | }), 25 | ]) 26 | .process(cssCode) 27 | .then( 28 | (r) => done(null, r.css), 29 | (e) => done(e, null) 30 | ); 31 | }); 32 | 33 | // Publish later 34 | // This code adds a custom Eleventy collection named 'posts' that filters blog posts based on their frontmatter data and the current environment (production or development). 35 | eleventyConfig.addCollection('posts', (collection) => { 36 | // Check if draft is true in frontmatter - used to exclude from the collection 37 | const isLive = (post) => !post.data.draft; 38 | 39 | // Check if date of a post is set in the 40 | // TODO: This could probably be handled through a check on the frontmatter post.data.scheduled due to schedule(data) function in posts.11tydata.js 41 | const isScheduled = (post) => post.date <= new Date(); 42 | 43 | // Removes the index.njk from the collection 44 | const isIndex = (post) => !post.inputPath.includes('index.njk'); 45 | 46 | // If the current environment is not production (i.e., it is in development), return all posts, including drafts. 47 | if (process.env.ELEVENTY_ENV !== 'production') { 48 | return collection.getFilteredByGlob('**/*.njk').filter(isIndex).reverse(); 49 | } else { 50 | // If the current environment is production, return only live, scheduled (not future-dated), and non-index posts. 51 | return collection.getFilteredByGlob('**/*.njk').filter(isLive).filter(isScheduled).filter(isIndex).reverse(); 52 | } 53 | }); 54 | 55 | // Pass through static assets 56 | eleventyConfig.addPassthroughCopy('src/views/robots.txt'); 57 | eleventyConfig.addPassthroughCopy('src/views/manifest.json'); 58 | eleventyConfig.addPassthroughCopy('src/views/img/svg/**.*'); 59 | 60 | // Collections 61 | // ----------------------------------------------------- 62 | eleventyConfig.addCollection('allPosts', (collection) => { 63 | return collection.getFilteredByGlob('src/views/posts/*').filter((item) => item.inputPath !== './src/views/posts/index.njk'); 64 | }); 65 | 66 | // Return all the tags used in a collection - with heavy inspiration from eleventy-base-blog 67 | eleventyConfig.addFilter('getAllTags', (collection) => { 68 | let tagSet = new Set(); 69 | for (let item of collection) { 70 | (item.data.tags || []).forEach((tag) => tagSet.add(tag)); 71 | } 72 | return Array.from(tagSet); 73 | }); 74 | 75 | // Filters 76 | // ----------------------------------------------------- 77 | // A filter for the tag list – with heavy inspiration from eleventy-base-blog 78 | eleventyConfig.addFilter('filterTagList', function filterTagList(tags) { 79 | return (tags || []).filter((tag) => ['page', 'post'].indexOf(tag) === -1); 80 | }); 81 | 82 | // A filter to split the lines in the Open Graph generated SVG preview image 83 | eleventyConfig.addFilter('splitlines', function (input) { 84 | const parts = input.split(' '); 85 | const lines = parts.reduce(function (prev, current) { 86 | if (!prev.length) { 87 | return [current]; 88 | } 89 | 90 | let lastOne = prev[prev.length - 1]; 91 | 92 | if (lastOne.length + current.length > 19) { 93 | return [...prev, current]; 94 | } 95 | 96 | prev[prev.length - 1] = lastOne + ' ' + current; 97 | 98 | return prev; 99 | }, []); 100 | 101 | return lines; 102 | }); 103 | 104 | // Date filters 105 | // ----------------------------- 106 | eleventyConfig.addFilter('datefmt', (contentDate) => { 107 | return format(contentDate, "LLL d'th' - yyyy"); 108 | }); 109 | 110 | // Convert SVG previews of posts to JPG 111 | eleventyConfig.on('afterBuild', () => { 112 | const socialPreviewImagesDir = '_site/img/social-preview-images/'; 113 | fs.readdir(socialPreviewImagesDir, function (err, files) { 114 | if (files.length > 0) { 115 | files.forEach(function (filename) { 116 | if (filename.endsWith('.svg')) { 117 | let imageUrl = socialPreviewImagesDir + filename; 118 | Image(imageUrl, { 119 | formats: ['jpeg'], 120 | outputDir: './' + socialPreviewImagesDir, 121 | filenameFormat: function (id, src, width, format, options) { 122 | let outputFilename = filename.substring(0, filename.length - 4); 123 | 124 | return `${outputFilename}.${format}`; 125 | }, 126 | }); 127 | } 128 | }); 129 | } 130 | }); 131 | }); 132 | 133 | // BrowserSync configuration 134 | eleventyConfig.setBrowserSyncConfig({ 135 | files: ['_site/**/*'], 136 | open: true, 137 | }); 138 | 139 | // Plugins 140 | eleventyConfig.addPlugin(syntaxHighlight); 141 | eleventyConfig.addPlugin(pluginPWA); 142 | 143 | // Simplify directories 144 | return { 145 | dir: { 146 | input: 'src/views', 147 | output: '_site', 148 | layouts: '_includes/layouts', 149 | }, 150 | }; 151 | }; 152 | --------------------------------------------------------------------------------