Helping the Civic Tech Field Guide with accessibility issues

The Civic Tech Field Guide (CTFG) claims to be "the world's biggest collection of projects using tech for the common good".

If you are looking for a civic-tech project to contribute to, this is a good starting place. They now have over 5000 projects in their directory! Some dead, though.

If something breaks my heart, it's seeing large, manually curated data sets or directories rot. The sword of Damocles is hanging over every database, so I decided to do a bit of dev volunteering for CTFG.

Being WCAG 2 (Web Content Accessibility Guidelines) compliant was one of their top priorities, as I found out. Matt Stempeck, among many things, the curator of CTFG, was quick to fill me in on the project's status.

I did not do a full accessibility audit with individuals using different assisted technologies, etc., but I fixed all the flagged errors by WAVE. It's not the same thing, but it's a step in the right direction.

Some of the changes involved adjusting the contrast ratio of colors, providing alt text, making sure things are labeled, and other fundamental stuff.

When it came to two legacy JavaScript libraries, I had to get creative.

Since I was completely new to the codebase, ripping them out and replacing them with something that matches today's standards seemed way out of scope. Thankfully, the libraries exposed initialization events, so I managed to do some light DOM manipulation to add the proper aria-related attributes right after the page load.

My other contribution was to provide minimal onboarding documentation.

The directory is using Laravel, and it had the default Somebody unfamiliar with this tech stack or less seasoned could have felt disoriented and might even be put off by the lack of proper instructions. Now, it's a bit easier for most.

Straightforward, minimal Docker Compose setup for Eleventy

Typical Docker use-case scenario: I was throwing together a blog with Eleventy, and the others did not have the required Node.js version for all functionality.

Eleventy doesn't require any fancy setup; I assume that's why there's no "official" or popular Docker image. The ones I quickly found looked half-backed or abandoned.

For this particular project, the CLI command for serving the site was:

npx @11ty/eleventy --input=*.njk --serve

Without any configuration file, as a one-line command, this for Docker would be:

docker run --rm -v ${PWD}:/app -w=/app node:16-alpine npx @11ty/eleventy --input=*.njk --serve

Eleventy when serving a website exposes a local and external URL:

[11ty] Watching…
[Browsersync] Access URLs:
Local: http://localhost:8080
[Browsersync] Serving files from: _site

Because the process is running inside the container, use the external URL. There's no real need for doing a port mapping.

Now that macOS uses zsh by default, this chokes with the zsh: no matches found: --input=*.njk error; but works well with bash.

We can get around this entire error by adding the 11ty command in package.json, which we would do it anyway:

"scripts": {
"build": "npx @11ty/eleventy --input=*.njk",
"serve": "npx @11ty/eleventy --input=*.njk --serve"

This works well on Unbutu (bash) and macOS (zsh):

docker run --rm -v ${PWD}:/app -w=/app node:16-alpine npm run serve

I prefer having a Docker config file, not just because it shortens the commands, but IDEs pick up on the usage and provide support for it.

The docker-compose.yml can be as simple as:

version: '3'

image: node:16-alpine
working_dir: /app
- ./:/app

With this file in place, the command is now:

docker-compose run --rm site npm run serve

This is still too much typing for my taste, so I usually add a helper script, so I can run my command like:

./bin/docker npm run serve

The script (bin/docker) does nothing more than forward the arguments passed to it:


if [[ -z $CMD ]]; then
echo "What command should we run inside the container? None provided."
exit 1

eval docker-compose run --rm site "$CMD"

Plans for FormBus 0.6, refactoring to be more modular and slim

When I coded together FormBus, I wanted to provide an out-of-the-box solution to all the typical things needed to submit a static form to an API. All this without the need for any external libraries.

The implementation is modular enough to use parts of it, and I even wrote an article about using just the directives.

But the more I used FormBus in real-world projects, the more it became clear that the directives were unnecessary to me. I used Alpine.js or React to handle other components, so I already had libraries that could do what the directives were supposed to.

In the end, I ended up creating integrations with these libraries, and I was mainly using only response sniffing utilities. While FormBus, in the current state, is not bloated, it's not as slim as the alternative solution I proposed in the Headless Form Submission With the WordPress REST API.

From the beginning, I decided to postpone the release of version 1.0 until I had the chance to use it in client works. Sometimes only time can surface the wiseness of decisions.

Since this was a secret public project, never shared with groups of people, thankfully, there are almost no weekly downloads. I don't feel bad completely refactoring it.

My plan for the 0.6 release is to remove the directives and form submission functionality and only keep the two plugins' response utility functions. But this time, provide tests for those sniffing helpers.

Later, I'll create an Alpine.js plugin that integrates with FormBus since that's the library I use the most lately.

Generating custom propreties from a configuration with Sass

It's possible to generate CSS using WordPress' theme.json.

Any values declared within the custom field will be transformed to CSS Custom Properties following this naming schema: --wp--custom--<variable-name>

If you have this in your JSON file:

"version": 1,
"settings": {
"custom": {
"line-height": {
"body": 1.7,
"heading": 1.3

then this is going to be interpreted and outputted like this:

body {
--wp--custom--line-height--body: 1.7;
--wp--custom--line-height--heading: 1.3;

The Global Settings & Styles (theme.json) page goes in detail about this functionality.

Even if you don't buy into "writing your CSS with a JSON file", there's something "nice" about declaring the values using an object rather than having a bunch of custom properties. It just gives a bit more structure.

Currently, I see little benefit in generating the CSS from a JSON file for bespoke, enterprise-size websites. However, I welcome this feature for themes that are used by thousands and are open to customization.

But I can see myself writing an object (map) and letting a function or mixin generate the custom properties. I want a Sass code like this:

$config: (
table: (
caption: (
color: gray
footer: (
typography: (
font-weight: bold

:root {
@include map-to-custom-propreties($config);

to be outputted in the CSS like this:

:root {
--table--caption--color: gray;
--table--footer--typography--font-weight: bold;

The configuration doesn't have to be even a giant map; it can come from multiple places and be combined, like so:

@use 'sass:map';
@use 'table/custom-propreties' as table-custom-propreties;
@use 'qoute/custom-propreties' as quote-custom-propreties;

:root {
@include map-to-custom-properties(


Two things have to be solved for the imagined map-to-custom-propreties utility. One is traversing the configuration map, which should be as deep and with as many elements as desired. The other part is generating the custom property's name.

Sass doesn't have a function that is similar to the join (JavaScript) method or the implode (PHP). There are a couple of implementation out-there, here's one from danielpchen and one from Kitty.

The one I implemented looks like this:

@function _implode-list($list, $separator: '--') {
$listLength: list.length($list);
$result: '';

@each $element in $list {
$nextAdd: $element;

@if list.index($list, $element) != $listLength {
$nextAdd: string.insert($element, $separator, string.length($element) + 1);

$result: string.insert($result, $nextAdd, string.length($result) + 1);

@return $result;

It's general-purpose enough for my current needs and uses the new built-in modules.

The traversing part is the core of the main mixin. It's recursive to handle any complex map, and its purpose is to accumulate the name's pieces. It outputs the custom property using the previous internal function when it reaches the value definition.

@mixin map-to-custom-properties($config, $list: (), $separator: '--') {
@each $key, $value in $config {
@if meta.type-of($value) == 'map' {
@include map-to-custom-properties($value, list.append($list, $key), $separator);
} @else {
--#{_implode-list(list.append($list, $key), $separator)}: #{$value};

With unit tests written for the function and mixin, I can see this become something that can be reused from project to project.

The forgotten Sent Messages for Sensei LMS

I had multiple plugins in the directory, but a few years ago, I closed them all except for one.

Sensei LMS was an LMS that I seriously considered for a project, and while investigating it, I even submitted a couple of minor fixes.

This was three years ago. Gutenberg wasn't a thing, and Sensei LMS was just becoming important again for Automattic.

Back then, I noticed that:

By default, there is no way to have access to previously sent messages from the course, lessons, or quizzes. The only option to see the messages is by going to the Inbox page. This might be cumbersome, especially if you sent a few messages and you are expecting an answer.

And for this reason, I wrote and released a plugin:

Sent Messages for Sensei LMS addresses this shortcoming by giving you quick access to previous messages.

These quotes are from the old announcement post titled Introducing Sent Messages for Sensei LMS.

Sensei LMS is at version 4 now, and it comes with a new theme called Learning Mode. It has a feature similar to what existed in LearnDash for years under the name of Focus Mode. Still, it's exciting news.

It's exciting enough to motivate me to update the Sent Messages for Sensei LMS to work with the block editor.