Normal view

There are new articles available, click to refresh the page.
Before yesterdayCSS In Real Life

Clutter

28 September 2025 at 00:00

I’ve been helping my mum fill out various forms for a house purchase for the past week or so. All of the forms are sent to her as PDFs by the solicitors, which are not the easiest or most intuitive to fill in electronically when you’re not a digital native. My first instinct was to open them in Adobe Acrobat, which I feel like has been the go-to PDF viewer since forever. But my goodness, the UI is a mess. A bunch of mystifying toolbars compete with big, shiny, distracting AI buttons and popups, encouraging you to try this feature you didn’t know you needed. In comparison, Apple’s Preview felt like a breath of fresh air, with just enough UI features to get the job done. It’s pretty crazy how a design company like Adobe, for years an industry leader for design tools, has allowed UI and UX design to take a backseat when it comes to what must be one of its most widely used products.

Screenshot of the Adobe Acrobat UI”
Acrobat
Screenshot of the Preview UI”
Preview

It’s unfortunately reminiscent of what a lot of websites have become, especially on mobile. When the experience of clicking a link, waiting for a Javascript-heavy page to load and dismissing a thousand pop-ups has become the norm, it’s hardly surprising that a good many users would rather bypass that experience altogether and are turning to AI and chatbots to do the browsing for them. Reading a simple, clean, digestible page of text in answer to a question is by far easier than playing click roulette with Google. And many are willing to overlook, or even ignore the trade-offs: that the information you’re receiving might not be accurate, and that its use presents a whole lot of ethical issues. When we’ve failed users this badly, I can’t say I blame them.

One of the points brought up in a talk by Anne Currie at Green IO conference was the idea that it’s virtually impossible to talk users out of a product they clearly want (referring to generative AI). But why do users want these products, despite their failings? The experience of browsing the web could be so much better than it is right now, without the huge social and environmental cost of AI. Perhaps there would be less demand for chatbots if the web itself was less hostile.

Reflections From Green IO 2025

25 September 2025 at 00:00

Yesterday I attended Green IO conference in London for the second year running, this time bringing along one of my Ada Mode colleagues. I only managed to attend Day 2, (the main conference day) and not the preceding day’s workshops, but there was plenty to take away and be inspired by from the talks. I’m not one for taking a lot of notes at conferences, so you’ll have to excuse me for the lack of precise detail in my recollections, but I wanted to jot a few things down to capture the general vibe and running themes of the day.

Green IO started out as a podcast by Gaël Duez, later branching out into conferences all over the world, with the aim of bringing together “responsible technologists”. As you might guess from the name, the focus is ostensibly on ”green” technology, but I would hazard a guess that most (maybe all) members of this community care about social issues beyond environmentalism, and are pretty clued into how all of these issues are interconnected. To paraphrase Gaël in his opening address, this is a community that cares about people and the planet. This is the third year the conference has run in London, and it was heartening to reflect on how much the attendance has grown, this year moving to a much bigger room at the venue. I loved Gaël’s emphasis (echoing that of a few other people involved in the green tech movement) that we should ”start from where we are, not where we should be”, and that there is no perfect plan that guarantees we are all beyond reproach. An inclusive mindset it vital to bringing more people into the community and taking action.

Anne Currie kicked of the day with what must be a record for the longest talk title: Forget SkyNet, will the energy consumption of AI destroy humanity? What can developers learn from Sarah Connor?. It set the tone for what would notably become a running theme of the day: AI. It’s impossible to talk about sustainable technology these days without talking about AI, which is outpacing the rest of the sector in its increasing consumption of energy. Anne’s talk pinpointed the pivotal moment when this realignment became seemingly inevitable, throwing industry climate targets into disarray: the release of Chat-GPT 3.5. Anne argues that AI efficiency measures are not going to be enough to negate the energy demands of hyperscalers’ pursuit of ever-larger models, and that trying to talk consumers out of using a product there’s clearly demand for is not a recipe for success. Instead she proposes the main solution is to vastly accelerate the renewable energy transition.

As much as I respect Anne’s work and felt her talk made a lot of good points, it also felt to me like a fairly pessimistic way to start the day — although arguably realistic: it’s hard to see how without government intervention (which is clearly not likely to happen any time soon) we could get these huge AI companies to use less energy and make their models smaller. But it feels problematic to be ramping up renewable energy primarily for the purpose of a technology that has a seemingly infinite demand. How much will be enough? Where is the green energy for the industries that are far more vital, and far less wasteful? After all, renewable energy is not without its own footprint, although of course far less impactful than oil and gas. There is no infinite source of energy, and at some point we have to use less of it, or at least get a lot more creative.

That segues nicely onto a talk that really piqued my interest by by Charlie Beharrell and Mark Buss, titled Good for the Planet, good for the wallet: how AI can heat our water. Charlie introduced the topic with a quote stating that electricity demand from data centres is forecast to increase six-fold in the next 10 years. Current data centres are doubly inefficient: energy is lost as heat, and water is needed to cool them. Meanwhile it is increasingly expensive to heat our homes with oil and gas. What if there was a way to heat homes using the heat from data centres? There is no heat network – we can’t pipe the heat from data centres to people’s homes. But one company, Heata is piloting moving data processing there instead. A server attached to a hot water tank heats the water for use in the home, and data is easily moved around the network. I thought this was a really cool approach, and it’s well worth checking out their slides, as they explained a lot more about how using CarbonRunner different CI/CD jobs could be shifted around the network.

Following this, Dryden Williams from CarbonRunner gave a great talk about the tool: Sustainable CI/CD: shifting compute tasks where CO2 intensity is the lowest. It was eye-opening how shifting computing tasks in real-time to available servers with low carbon intensity could save huge amounts of greenhouse gases. Their tool is definitely one to check out.

There were a few talks and a panel discussion from members of Gov.uk’s various digital teams on their approaches to digital sustainability, including their AI Playbook, which sets out guidelines for responsible AI use. Far too few organisations have anything like these detailed guidelines. Tom Parry from DEFRA also talked about their efforts to reduce e-waste within the department, including by issuing refurbished devices, using smart lockers, which save a surprising amount of carbon compared to home delivery, and by embedding sustainability into the procurement process. It’s great to see how people working in the public sector, which of course impacts millions of people, are prioritising this.

It also brought to mind an earlier talk by Natalie Pullin: Our digital sustainability journey at HSBC. I’m a little cynical about talks by corporate representatives, which will generally show their companies in the best possible light. But Natalie’s talk did show that working and advocating for sustainability at a big corporation might well be the best way to make an impact. At a company like HSBC, which employs hundreds thousands of people and has some ambitious climate target, making greener choices can create huge savings.

In the afternoon, Ben Tongue and Claire Robinson discussed climate resilience using a major power failure due to a heatwave at Guys and St Thomas’s hospital in London as a case study in their talk Climate change is here - using a systems thinking approach to keep NHS resilient. We might not want to think about how we need to adapt to the effects of climate change, but it’s clear it will be unavoidable. We also don’t think enough about how interdependent many of our vital systems are, and what the knock-on effects of a failure in one part of the system will be. The duo showcased a tool for visualising the effects of environmental risks to interconnected systems.

Later in the day Hannah Smith from the Green Web Foundation put forward their innovative yet simple idea for making reporting on carbon emissions more visible and searchable (How carbon.txt enables transparency across Tech companies). I’ve often lamented how difficult it it to find accurate sustainability data from companies. While many of them are required to produce impact reports, these are often buried and near-impossible to find for anyone searching for up-to-date, accurate data. Deploying a carbon.txt file (similar to a robots.txt) file on any website would provide a way to look for machine-readable sustainability data for that organisation. I would love to see this widely adopted, particularly as greater transparency is the only way we can put pressure on companies to reduce their environmental impact.

Finally, the last talk of the day by Ian Brooks – Green IT is good but it's not enough! was a good reminder that we have to think beyond just the products we’re building and consider what we’re building them for. After all, it’s all very well having an incredibly green website, but if it’s for a highly polluting, extractive industry (like the fossil fuel industry) then we can’t really say it’s for a net good. It’s something I’ve tried to get across in my talks as well.

I didn’t quite get to summarising all the talks here, but hopefully you’ll attend Green IO next time and see what responsible technology is all about. More than anything, it’s a great community of people trying to make a difference, and left me feeling optimistic, despite everything.

Creating CSS Theme Variables from a JS file

22 April 2025 at 00:00

For many projects I work on it’s useful to define all of our brand colours in a JavaScript file, particularly as I work on a lot of data visualisations that use them. Here’s an abridged example of how I define brand colours, as well as those used for data visualisations, and their variants:

// theme.js
const theme = {
  color: {
    brand: {
      primary: {
        DEFAULT: '#7B1FA2',
        light: '#BA68C8',
        dark: '#4A148C',
      },
      secondary: {
        DEFAULT: '#E91E63',
        light: '#F48FB1',
        dark: '#C2185B',
      },
    },
    data: {
      blue: '#40C4FF',
      turquoise: '#84FFFF',
      mint: '#64FFDA',
    },
  },
}

I also need those variables in my CSS, where they’re defined as custom properties. But I don’t want to have to maintain my colour theme in two places! That’s why I created a script to create a CSS file that defines custom properties from a JS source file. If you’re interested, here’s how it’s done.

Setup

For this walkthrough you’ll need Node and NPM installed. If you’re already familiar with setting up a project using NPM, you can skip over this part. Otherwise, assuming you’ve already installed NPM globally, you’ll need to run npm init in your project root and follow the prompts. This creates a package.json file in the root of your project directory.

Create a script file

We’ll need to create a JS file for our script so we can run it from the command line. For simplicity, let’s create a file called index.js in the project root, and add a single line:

// index.js
console.log('Hello world')

Now we should be able to run node index.js from the terminal and see our “Hello world” message, so we know our very basic script has run successfully.

Import the theme

Now let’s import the theme defined in the JS file from which we want to create our CSS custom properties. We’ll call this theme.js. You’ll need to make sure your file exports the theme so it can be imported elsewhere.

// theme.js

const theme = {
  // Theme colours as defined above...
}

export default theme
// index.js
import theme from './theme.js'

console.log(theme)

Running the script again with node index.js, we should see the theme object logged in the terminal. Now we need to actually do something with it!

Input and output

The aim here is to create CSS custom properties that correspond to the theme object keys. For example:

// theme.js
const theme = {
  color: {
    primary: 'red',
    secondary: 'blue',
  },
}

Would become:

/* styles.css */
:root {
  --color-primary: red;
  --color-secondary: blue;
}

However, our theme as defined in our JS file isn’t quite so simple. As you can see from the example at the beginning, some of our colour definitions include multiple lighter or darker variants, nested more than one level deep.

What we would like here is to map our colours so that their custom property names are prefixed with their ancestor property names. For example, we would use --color-brand-primary for the default primary brand colour, and --color-brand-primary-light for its lighter variant.

:root {
  --color-brand-primary: #7b1fa2;
  --color-brand-primary-light: #ba68c8;
}

We shouldn’t assume that all colour will have the same property names either. We should be able to define them using any names we like, as many levels as is required.

Note, I’m including color here as a property of theme. That’s because the actual theme configuration might include things like font families too. We’ll keep it simple and focus on colour here, but the script we’re going to write should (theoretically!) work for any object properties of the theme.

Writing a recursive function

We’ll write a function that looks at any key/value pair and returns the CSS custom property definition as a string.

The first part is easy enough:

// index.js
import theme from './theme.js'

const mapTheme = ([key, value]) => {
  // If value is a string, return the result
  if (typeof value === 'string') {
    return `--${key}: ${value}`
  }
}

This would work fine is we had a very simple theme, like this:

const theme = {
  purple: '#7B1FA2',
  pink: '#E91E63',
}

We could convert our theme object to an array using Object.entries() and map over the entries with this function:

// index.js
import theme from './theme.js'

const mapTheme = ([key, value]) => {
  // If value is a string, return the result
  if (typeof value === 'string') {
    return `--${key}: ${value}`
  }
}

console.log(Object.entries(theme).map(mapTheme))
// result: ['--purple: #7B1FA2', '--pink: #E91E63']

However, that’s not going to be enough for our nested theme variables. Instead we’ll amend the mapTheme() function so that if the value is not a string

//index.js
const mapTheme = ([key, value]) => {
  // If value is a string, return the result
  if (typeof value === 'string') {
    return `--${key}: ${value}`
  }

  // Otherwise, call the function again to check the next pair
  return Object.entries(value).flatMap(mapTheme)
}

console.log(Object.entries(theme).flatMap(mapTheme))

You might notice we’re using the flatMap() array method instead of map() as above. This is so that the result is output as a flat array, which is what we want, instead of nesting the custom properties.

If we check the result at this point, we’ll see it’s not quite what we want. We end up with custom property names that correspond to the nested object keys but don’t tell us anything about the parent groups. We also end up with duplicates:

[
  '--DEFAULT: #7B1FA2',
  '--light: #BA68C8',
  '--dark: #4A148C',
  '--DEFAULT: #E91E63',
  '--light: #F48FB1',
  '--dark: #C2185B',
  '--blue: #40C4FF',
  '--turquoise: #84FFFF',
  '--mint: #64FFDA',
]

If we want more useful custom property names we’ll need to append the name to its parent group name, unless the key is DEFAULT, in which case we’ll simply return the parent group key.

// index.js
import theme from './theme.js'

const mapTheme = ([key, value]) => {
  // If value is a string, return the result
  if (typeof value === 'string') {
    return `--${key}: ${value}`
  }

  return Object.entries(value).flatMap(([nestedKey, nestedValue]) => {
    // Append to the custom property name, unless default value
    const newKey = nestedKey === 'DEFAULT' ? key : `${key}-${nestedKey}`

    // Check the new key/value pair
    return mapTheme([newKey, nestedValue])
  })
}

console.log(Object.entries(theme).flatMap(mapTheme))

This results in far more helpful names:

[
  '--color-brand-primary: #7B1FA2',
  '--color-brand-primary-light: #BA68C8',
  '--color-brand-primary-dark: #4A148C',
  '--color-brand-secondary: #E91E63',
  '--color-brand-secondary-light: #F48FB1',
  '--color-brand-secondary-dark: #C2185B',
  '--color-data-blue: #40C4FF',
  '--color-data-turquoise: #84FFFF',
  '--color-data-mint: #64FFDA',
]

Codepen example

An alternative with a for loop

By the way, we could do this in a slightly different way with a for loop. It’s a similar amount of code, but we don’t need the nested flatMap, which might make for a slightly more elegant solution (you be the judge!):

// index.js
let result = []

const mapTheme = (obj, key = null) => {
  for (const property in obj) {
    let name = key || property

    if (property !== 'DEFAULT' && !!key) {
      name = `${key}-${property}`
    }

    if (typeof obj[property] === 'string') {
      result.push(`--${name}: ${obj[property]}`)
    } else {
      mapTheme(obj[property], name)
    }
  }
}

mapTheme(theme)

console.log(result)

Codepen example

Writing to a file

Now we can take these values and write them to a CSS file for use in our project. We could simply copy them from the console, but even better if we write a script that will do it for us.

We’ll import the writeFile method from the Node JS library and write a new async function called buildTheme, which we’ll export. (We’ll remove the console log from the previous example.)

// index.js
import { writeFile } from 'fs/promises'
import theme from './theme.js'

const mapTheme = ([key, value]) => {
  /* ... */
}

const buildTheme = async () => {
  try {
    console.log(Object.entries(theme).flatMap(mapTheme))
  } catch (e) {
    console.error(e)
  }
}

buildTheme()

We should now be able to run the script from the command line by typing node index.js and see the result logged.

Next we’ll convert the custom properties into a suitable format for our CSS file. We’ll want each custom property to be indented and set on its own line, which we can do with the escaped characters \n and \t respectively.

// index.js
import { writeFile } from 'fs/promises'
import theme from './theme.js'

const mapTheme = ([key, value]) => {
  /* ... */
}

const buildTheme = async () => {
  try {
    const result = Object.entries(theme).flatMap(mapTheme)

    // Indent each custom property and append a semicolon
    let content = result.map((line) => `\t${line};`)

    // Append and prepend brackets, and put each item on a new line
    content = [':root {', ...content, '}'].join('\n')

    console.log(content)
  } catch (e) {
    console.error(e)
  }
}

buildTheme()

All that remains is to write the result to a CSS file, using the writeFile() method. We’ll need to specify the location of the file we want to write to, and its character encoding, which will be 'utf-8'. We’re including a helpful console log informing the user that the file has been written, and ensuring we catch any errors by also logging them to the console.

// index.js
import { writeFile } from 'fs/promises'
import theme from './theme.js'

const mapTheme = ([key, value]) => {
  /* ... */
}

const buildTheme = async () => {
  try {
    const result = Object.entries(theme).flatMap(mapTheme)

    let content = result.map((line) => `\t${line};`)
    content = [':root {', ...content, '}'].join('\n')

    // Write to the file
    await writeFile('src/theme.css', content, { encoding: 'utf-8' })

    console.log('CSS file written')
  } catch (e) {
    console.error(e)
  }
}

buildTheme()

Running the script now outputs the CSS file we need.

@theme {
  --color-brand-primary: #7b1fa2;
  --color-brand-primary-light: #ba68c8;
  --color-brand-primary-dark: #4a148c;
  --color-brand-secondary: #e91e63;
  --color-brand-secondary-light: #f48fb1;
  --color-brand-secondary-dark: #c2185b;
  --color-data-blue: #40c4ff;
  --color-data-turquoise: #84ffff;
  --color-data-mint: #64ffda;
}

Here’s the complete file:

// index.js
import { writeFile } from 'fs/promises'
import theme from './theme.js'

const mapTheme = ([key, value]) => {
  if (typeof value === 'string') {
    return `--${key}: ${value}`
  }

  return Object.entries(value).flatMap(([nestedKey, nestedValue]) => {
    const newKey = nestedKey === 'DEFAULT' ? key : `${key}-${nestedKey}`

    return mapTheme([newKey, nestedValue])
  })
}

const buildTheme = async () => {
  try {
    const result = Object.entries(theme).flatMap(mapTheme)

    let content = result.map((line) => `\t${line};`)
    content = [':root {', ...content, '}'].join('\n')

    await writeFile('src/theme.css', content, { encoding: 'utf-8' })

    console.log('CSS file written')
  } catch (e) {
    console.error(e)
  }
}

buildTheme()

Wholegrain Digital’s Response to the BBC’s Web Sustainability Report

3 April 2025 at 00:00

Earlier this year the BBC published a report on digital sustainability, Does what you scroll burn coal? Mythbusting energy consumption on the web. The report seeks to address some of the ways our online activities consume energy but interestingly, is quite critical of some of the web sustainability guidelines that have been developed and published by the community.

What’s frustrating here is that the report’s authors have done little to reach out to members of the digital sustainability community, and don’t acknowledge the nuance and dialogue that is continually at work in this space.

The purpose of web sustainability guidelines (such as those published by Wholegrain, or the Sustainable Web Interest Group) is to help those working on the web design and build websites in a way that minimises their environmental impact. They are living, breathing documents. They are the product of collaboration, and are designed to evolve with the evidence, recognising that no one has the perfect model for measuring the results of adoption. When blanket criticism is levelled in this way it can have the effect of discouraging others to take measures to reduce the environmental impact of their websites, but also to get involved with the community and contribute.

Chris and Andy at Wholegrain Digital have published their own response to the report that adds some context to the claims made.

Read the article

Debating the Merits of LLMs

13 February 2025 at 00:00

A recently published post by the science fiction writer Robin Sloan (Is It Okay?, published 11th February 2025) ignited some examination and debate among my little corner of the web. The post asks the question of whether it’s ethical, from an individual moral standpoint to use an LLM (Large Language Model, such as Claude or GPT-4). Robin raises some important points about the trade-offs that come with LLMs, depending on their application.

If their primary application is to produce writing and other media that crowds out human composition, human production: no, it’s not okay.

He also offers an alternative view, where it could be supposed that LLMs will pave the way for “super science”, a common claim of AI advocates.

If super science is a possibility — if, say, Claude 13 can help deliver cures to a host of diseases — then, you know what? Yes, it is okay, all of it. I’m not sure what kind of person could insist that the maintenance of a media status quo trumps the eradication of, say, most cancers. Couldn’t be me. Fine, wreck the arts as we know them. We’ll invent new ones.

Here’s where I disagree with Robin’s reasoning: AI isn’t LLMs. Or not just LLMs. It’s plausible that AI (or more accurately, Machine Learning) could be a useful scientific tool, particularly when it comes to making sense of large datasets in a way no human could with any kind of accuracy, and many people are already deploying it for such purposes. This isn’t entirely without risk (I’ll save that debate for another time), but in my opinion could feasibly constitute a legitimate application of AI.

LLMs are not this. They synthesise text, which is not the same as data. Particularly when they are trained on the entire internet, which we all know includes a lot of incorrect, discriminatory and dangerous information. As Baldur Bjarnason points out:

There is no path from language modelling to super-science.

I don’t believe LLMs are entirely without utility. The company I work for designs and trains AI models for use in industrial processes and LLMs. But they are different things. In one application we (and by “we”, I mean my far cleverer colleagues) deploy models for analysing performance data from wind turbines to produce insights related to power output, deterioration and part failure, in order to enable operators to plan maintenance and optimise power generation. Here AI has the potential to help drive down costs and maximise clean energy production. It’s still early days, and it remains to be seen whether this kind of technology will be widely deployed and beneficial at a large scale, but this, to my mind, edges marginally towards the scientific potential that Robin refers to (while being a long way from, say, curing cancer). It’s not an LLM.

On the other hand, we do train LLMs for other applications, such as gleaning relevant information from thousands of disparate documents, which would be impossible to trawl through manually, and present findings in a user-friendly way. This is not a general-purpose LLM designed to regurgitate information from the entire internet, but is built from a set of highly specific training data that is relevant to the industry in which it is applied.

Both of these applications are interesting and potentially useful. But they are not the same. An LLM as described above, while useful, shouldn’t invent new information. It processes the text that already exists, not the science behind it, and if it appears to offer up something new then that should be met with the utmost scrutiny. And it remains to be seen whether they (and others like them) will be worth the extraordinary amount of energy and resources that AI demands.

By using Chat-GPT to write your essay, code or email, you are not contributing to “super science”. LLMs cannot do that. Maybe you’ll conclude that using an LLM is still worth it to make you more productive in writing code, or whatever. (And yes, I have Thoughts on this.) But once we discount “super science” from the equation, it seems to me there aren’t a whole lot of positives left.

What I learned from migrating a Vue project from Vuex to Pinia

6 February 2025 at 00:00

Recently I had the experience of migrating a Vue web app to a new state management library, Pinia, which was interesting from both a technical and non-technical point of view. In this article for Piccalilli I’ll share my thoughts and findings on when, why and how you might consider carrying out such a major technical migration, applicable to any large web project, and some tips and advice on migrating from Vuex to Pinia specifically.

Read the article

Creating Static SVGs from GeoJSON

17 January 2025 at 00:00

Recently I’ve been working with map data to create interactive visualisations. When working with maps it’s common to receive data as GeoJSON, a JSON format for encoding geographic features, which specifies the type of geometry and co-ordinates for the features we want to display on a map. Javascript mapping libraries such as Mapbox GL are designed to consume GeoJSON to render features on a canvas. I’m fairly accustomed to using GeoJSON in this way — for example, rendering geographic areas as different coloured polygons overlaid on a map to show varying values for different areas.

But for a recent project I decided to take a different approach. Mapbox GL is a great library that provides a lot of useful features out of the box, like zooming and panning. It’s also pretty hefty as far as libraries go, weighing in at 1.4MB un-minified. This project did not require any advanced map functionality, however, and only required the map display to be centred on a particular area, with interactive features on hover.

In the interests of minimising the JS payload for users, it made sense to render the map as a static SVG, with only minimal JS needed for interactivity. That meant I needed to convert the GeoJSON data I had been provided with to a static SVG file. In case you find yourself in a similar position, I’m going to show you how to do this using D3.js. There’s a pre-prepared example on Codepen, in case you want to skip straight to the code.

Fetching the data

We’ll use the Fetch API to fetch some hosted GeoJSON, which has the .json suffix. I’ve uploaded an example file to Codepen, which has a limit of 5MB for file assets. In a real project, the GeoJSON file might be much bigger.

We’ll use the json() response method to parse our response data, just like any other JSON response, then we’ll log it to the console. We should see our parsed data.

const geojsonUrl = 'https://assets.codepen.io/85648/map-example.json'

fetch(geojsonUrl)
  .then((response) => response.json())
  .then((data) => console.log(data))

Depending on our geographic data, our GeoJSON could take different formats. In my case, the data I want to display is a FeatureCollection, consisting of several geographic areas. Alternatively you might have a single Feature, or geographic area, and that could consist of one or more polygons.

Here is an example of a very simple GeoJSON feature. The geometry type is Point, which means it pinpoints a specific location — useful if you’re adding a marker to a map, for instance. For drawing geographic areas, the geometry type will likely be Polygon or MultiPolygon.

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [125.6, 10.1]
  },
  "properties": {
    "name": "Example location"
  }
}

Creating the SVG

Before rendering our data as an SVG path, we first need to create an empty SVG element. We could do this in HTML:

<svg width="600" height="400" viewBox="0 0 600 400"></svg>

However, since we’re going to be using D3 anyway, let’s create the SVG in Javascript, the D3 way. That makes it simple to set our SVG dimensions as variables, which we’ll use again shortly.

It also means we can wait until after the browser has successfully fetched our data and parsed the response before rendering the SVG — and gives us the option of showing a helpful error message to users in case our request fails.

We’ll set the SVG dimensions as a variable.

const dimensions = {
  width: 600,
  height: 400,
}

Then we’ll use D3’s select() method to select an element to which to append our SVG. This could be the <body>, or any other element. In this case, we’ll use a <div> with an ID of wrapper.

We’ll append an SVG element, then set the width, height and viewBox attributes.

fetch(geojsonUrl)
  .then((response) => response.json())
  .then((data) => {
    d3.select('#wrapper') // The element which to append our SVG to
      .append('svg')
      .attr('width', dimensions.width)
      .attr('height', dimensions.height)
      .attr('viewBox', `0 0 ${dimensions.width} ${dimensions.height}`)
  })

Converting GeoJSON to an SVG path

Now we’re going to use D3’s geographic path generator to generate SVG path strings from our data. We’ll append a path element to the SVG and set the d attribute (the instructions for how to draw the line) from our data. The geoPath() function can render a path from a single feature or geometry, or from multiple features combined into a FeatureCollection. If we have a single feature we can create a path generator, and call it with data:

const path = d3.geoPath()

d3.select('#wrapper')
  .append('svg')
  .attr('width', dimensions.width)
  .attr('height', dimensions.height)
  .attr('viewBox', `0 0 ${dimensions.width} ${dimensions.height}`)
  .append('path') // Append a 'path' element
  .attr('d', path(data)) // Draw the path from the data

If our data consists of a FeatureCollection, we might instead need to render multiple paths. We approach this slightly differently, by binding the dataset to our SVG, then appending a path for each of the features in the FeatureCollection.

d3.select('#wrapper')
  .append('svg')
  .attr('width', dimensions.width)
  .attr('height', dimensions.height)
  .attr('viewBox', `0 0 ${dimensions.width} ${dimensions.height}`)
  .selectAll()
  .data(data.features) // Bind the features in the FeatureCollection
  .join('path')
  .attr('d', path) // We don’t need to call `path` with an argument, as we’ve already bound the data

This should create paths from our data. It also works if our data contains “MultiPolygons” — multidimensional arrays of polygons. Note, we could alternatively use only the geometry data from our features instead of the entire Feature object.

Projection and scaling

Although inspecting the SVG element in the browser might show that we’ve rendered some SVG paths, it’s likely they’ll currently be invisible to the viewer. That’s because we haven’t yet scaled them to our SVG viewbox, so they may be rendered off-canvas. We need to tell D3 how to project our map elements onto the available space.

For this we’ll transform the projection, by calling geoIdentity(), using the fitSize() method to scale it to our SVG bounding box. Our revised projection is passed in as an argument to d3.geoPath(), overriding the default projection.

const projection = d3
  .geoIdentity()
  .fitSize([dimensions.width, dimensions.height], data)

const path = d3.geoPath(projection)

This assumes the top left SVG co-ordinates should be [0, 0] — otherwise you should use fitExtent() which allows us to specify all corners of the bounding box.

Flipping the path

Now our paths should render visibly. But you might notice there’s one more issue: they are upside-down. Be careful because this might not be totally obvious with unfamiliar paths. But it was certainly noticeable with a map of the UK!

The reason for this is that standard spatial reference systems treat the y axis as pointing upwards from 0, while in the SVG co-ordinate system the y axis points downwards, with 0 at the top. Luckily D3 provides a method for reflecting our projection in the y dimension.

const projection = d3
  .geoIdentity()
  .reflectY(true) // Flip the paths in the y dimension
  .fitSize([dimensions.width, dimensions.height], data)

const path = d3.geoPath(projection)

Result

Check out the Codepen demo below to see this in action. You can replace the geojsonUrl variable with your own GeoJSON data URL to create an SVG from your own data.

See the Pen GeoJSON to SVG by Michelle Barker (@michellebarker) on CodePen.

Once I created this I was able to copy the resulting SVG code and save the static file for use in my codebase.

Common issues drawing paths from GeoJSON

When working with GeoJSON polygon data (particularly with map libraries) I sometimes get an error along the lines of “Polygons and MultiPolygons should follow the right-hand rule”. This tends to occur in GeoJSON validators, or when using a library like Mapbox. (I didn’t have this issue with the above code.) This relates to the GeoJSON specification regarding how polygons are “drawn”. It states that “A linear ring MUST follow the right-hand rule with respect to the area it bounds, i.e. exterior rings are counterclockwise, and holes are clockwise.”

There are a couple of ways to fix this:

  1. In the browser, by uploading the file or pasting the code into the mapster-right-hand-rule-fixer tool.
  2. Using Mapbox’s rewind package,

Both of these will output the polygons in the correct format.

Server-side generation

After implementing this in the browser I got curious about generating SVGs from GeoJSON at build-time. This didn’t take too much work, and allows me to easily update the SVG if the data changes.

We need to do this slightly differently as there is no DOM, so we can’t select elements using D3. But we can still generate the paths easily, append them to an SVG element and write it to a file.

We can still use the geoPath() and geoIdentity() methods as previously. This time, however, we’ll map over the features and return a HTML string. In addition the the d attribute, I’m giving each path a unique ID based on its properties, which will be useful for interaction.

const projection = geoIdentity()
  .reflectY(true)
  .fitSize([dimensions.width, dimensions.height], data)

const path = geoPath(projection)

const paths = data.features.map((d) => {
  return `<path id="${d.properties.name}" d="${path(d)}" />`
})

Then we just need to append those paths to the SVG element and write to a file using Node JS’s writeFile() method.

const fileData = `<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${paths.join('')}</svg>`

writeFile('./src/map-svg.svg', fileData)

Here’s the full code:

import { geoPath, geoIdentity } from 'd3'
import { writeFile } from 'node:fs/promises'

const geojsonUrl = 'https://assets.codepen.io/85648/map-example.json'

const dimensions = {
  width: 600,
  height: 300,
}

fetch(geojsonUrl)
  .then((response) => response.json())
  .then(async (data) => {
    try {
      console.log('✨Generating SVG')

      const { width, height } = dimensions
      const projection = geoIdentity()
        .reflectY(true) // SVG co-ordinate system is the opposite way up, so we need to flip it
        .fitSize([dimensions.width, dimensions.height], data) // Scale to fit our SVG dimensions

      const path = geoPath(projection)

      const paths = data.features.map((d) => {
        return `<path id="${d.properties.name}" d="${path(d)}" />`
      })

      const fileData = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
        ${paths.join('')}
      </svg>`

      await writeFile('./src/map-svg.svg', fileData)

      console.log('Done!')
    } catch (error) {
      console.error('Error writing file')
    }
  })

See the Github gist with this code

Sustainable Hardware Choices

17 December 2024 at 00:00

I’m pretty proud that I managed to keep my iPhone 8 going for over five years (with one battery replacement in that time). But recently it’s been increasingly unreliable, switching itself off at random times, and spontaneously draining the battery after doing anything remotely taxing. This combined with the fact that software updates are no longer available for this model led me to the conclusion that buying a new phone was probably a good idea at this point.

Choosing a new phone

I’m not a person who needs the latest and greatest tech. It just needs to function, and let me complete the tasks I need to quickly and easily. Low friction is what I need.

But I also want my hardware choices to be as sustainable as possible. Lots of people who care about sustainability speak highly of the Fairphone, which is made with a high percentage of recycled materials and is designed to be repairable. The drawback is it’s Android-only. Right now I don’t really want to leave the Apple ecosystem, as everything syncs nicely across my phone, laptop, watch and iPad.

That left me with the option of buying a refurbished iPhone. This is actually a great option. By buying secondhand you’re helping give an old device a second lease of life, preventing hazardous materials going to landfill and polluting the environment. According to the Carbon Trust:

The most carbon intensive stage in a smartphone’s lifecycle is production and manufacturing (around 80% of its total footprint)...based on today’s production processes, adding just one year onto the lifetime of all smartphones in the world could save the same volume of carbon emissions by 2030 as taking 4.7 million cars off the road.

You can also save a lot of money, especially if you don’t mind an older model. I opted for the iPhone SE, billed as “good condition”, with a listing price of £125, and paid an additional £25 for a brand new battery upgrade. To be honest, I wouldn’t have minded paying slightly more for a more recent model, but I didn’t really want a phone that’s bigger than the one I already have! (Manufacturers, take note: not everyone has gigantic man-hands.)

The phone I received works great, and looks like new. So far I’m really happy with my purchase.

Recycling your old phone

Rather than leaving your old devices languishing in a drawer, it’s also pretty easy to get them valued and recycled. Many sites that sell refurbished phones will also buy back your old devices, or offer a part-exchange. Some high street stores also offer this service.

There are also comparison sites, like Compare and Recycle (for UK consumers) that show you the best buy-back price for your old phone. I was surprised that my old iPhone 8 was valued at £50, and the company sent me a free, prepaid postage box to mail my old device.

Even if you think your device is totally useless, it still contains rare and precious materials that can be re-used for manufacture and repair of new devices. If you can’t find a buyer for your old device you can take it to a household recycling centre for electronic goods. (Make sure you wipe your data first.) Some councils here in the UK even offer curb-side collection.

You might not need a new phone

The tech industry would have us believe that we need to be upgrading our devices every year. But really, what do any of the new models offer us, besides a slightly better camera? Now companies are falling over themselves to convince us that we need “AI-enabled” everything. But will any of it make us happier or more productive? (Gosh, I hate that word.) Or does it just provide another way to get us to buy more, while conveniently harvesting even more of our data? I know that having a brand new fancy phone every year will make not one ounce of difference to my happiness.

Takeaways

When it comes to making sustainable hardware choices remember:

  1. Holding onto your devices for as long as possible and maximising their useful life keeps e-waste out of landfill, and reduces embodied emissions and the raw materials needed to manufacture new devices.
  2. Secondhand can be just as good as new, and usually cheaper.
  3. If you really want to buy new, look for more sustainable, like the Fairphone.
  4. Sell, give away or recycle your old devices so they can be used by someone (or something) else.

Education Needs Teachers, Not More Technology

21 November 2024 at 00:00

Baldur Bjarnason shared an article in a recent edition of his newsletter about AI in education. As a parent of an 8-year-old, this is something that’s been on my mind a lot recently. Like most other parents I’ve encountered, I find the idea that teachers can be “replaced” by AI so preposterous that I’d imagine no one who’s ever had a child, met a child, or even been a child would take it seriously. Apparently though, there are some for whom the idea of taking an immeasurably complex, still-forming, constantly changing human brain, understanding, caring for and nurturing it, and imbuing it with the wisdom of the universe in a way that inspires excitement, passion and curiosity are the kind of tasks that should be done by a computer. Go figure.

Like just about every other profession, much has been made of AI “helping” teachers. Indeed, some teachers I know turn to ChatGPT to help plan lessons. They’re also forced to sift through pages of homework done by ChatGPT, and have developed their own methods for detecting plagiarism (i.e. when text has been lifted wholesale from an LLM). No, they don’t use an AI “plagiarism detector”, they use their own human brains.

Part of the reason teachers turn to tools like ChatGPT for help is because their own workloads are unmanageable, and only ever seem to increase. So too, it seems, does the work of parents. A few weeks ago I spent an evening with several mums with school-age children. At one point the talk turned to homework, and the constant battle to stay on top of it, which is made far harder by the plethora of apps schools now use to issue homework, and which in many cases children are required to use to complete it. At last count, my friend was aware of at least seven different apps her Year 7 daughter needs to use. That’s seven different log-ins to keep track of. My friend described having to “hack into” her daughter’s tablet when she’d forgotten her device password.

Among all these apps there’s no central place where she can see, at a glance, all of her homework requirements for the week. And that’s even when apps work at all: sometimes my friend will log in and see nothing, even when she knows that homework has been issued. I heard stories of children automatically (automatically!!) being issued with double-detentions because the software failed to register that they’d turned up for the first detention — a detention issued for failing to complete homework they didn’t realise they had.

All this seems incredibly stressful and cruel for children who are navigating their first year of secondary school, and that’s with supportive, tech-literate parents. Children with a less stable home life will undoubtedly have to deal with even more stress around this.

As my child is still at primary school, I count myself lucky I’ve only had to deal with a couple of apps, and they’re not usually compulsory. Each year we’re issued with a login for a new maths app and without fail, the following year it’s been abandoned in favour of another. Last year’s web app didn’t work with touch devices, which seems like an oversight: how many 7-years-olds are sitting at a laptop?

I’m sure many schools are handling technology better than my friend’s child’s. And I’m sure the teachers and school leaders foisting more technology onto children are doing it with the best of intentions — perhaps out of fear of failing children, who need to be equipped with vital digital skills, or (perhaps disingenuously) fear that their school won’t appear “forward-looking” enough. I absolutely advocate for children learning tech literacy skills at an early age — they’re essential for navigating the world, after all. Unfortunately many schools are ill-equipped to teach those skills, whether that’s through lack of funding or lack of qualified staff. But handing that task over to an AI is far from the answer.

CSS Masonry Layout Syntax

31 October 2024 at 00:00

Ahmad Shadeed has published a great article digging into the proposed CSS masonry layout syntax. In case you’re unaware, the term “masonry” for layout is used to describe the kind of grid layout where instead of items of various heights being aligned in neat horizontal rows, they are shifted to fill the leftover space, effectively creating a brickwork effect. It was popularised by the website Pinterest some years ago, and became a widespread UI design trend for a while.

Screenshot of photo grid with masonry layout
Masonry example from the Webkit blog

As it has always been impossible to build a true masonry layout in CSS, developers have traditionally reached for Javascript libraries to fill the gap. These libraries come with a performance cost though, and I would argue we really shouldn’t be using JS for layout in 2024. Although this type of layout has fallen out of fashion in recent years, it’s still popular enough that it’s worth considering how we could implement this in CSS. To me, it feels like this would plug one of the final holes and would mean we could throw away JS layout dependencies for good.

Jen Simmons’ post on the Webkit blog includes some really nice examples of different layouts for a photo grid that could be created with the new masonry proposal, and perhaps we’ll see a resurgence in these kind of designs once they become easier for developers to build.

At the moment where exactly masonry fits into CSS layout as a whole is a hotly contested topic. The two options alighted upon by the working group essentially boil down to:

  1. Part of Grid (with grid-template-rows: masonry, for example)
  2. Its own thing (e.g. display: masonry)

Of course, it’s not nearly as simple as that, as there are a whole host of other issues to consider. Partly this question arises because a masonry layout is effectively a grid, but in one direction. Making it part of Grid opens up a can of worms regarding how Grid’s other features should work alongside it. But is it really worthy of its own layout method?

Ahmad makes a great point about the name “masonry” too, arguing that it’s too analogous to the real world compared to other CSS properties. If we were talking about something entirely new here I would tend to agree, but “masonry” as a layout method has been around for a long while now, even though we couldn’t do it in CSS. It feels to me like it’s become somewhat divorced from its original meaning and is now common parlance for describing a particular type of web layout. (After all, the term “masonry” describes construction with brick or stone, which are generally staggered in the horizontal direction, unlike what we think of as masonry on the web, which tends more often to be vertically staggered.)

It’s well worth reading Ahmad’s full article, which is packed with examples and useful demos.

Read the article

AI Environmental Impact Report

23 October 2024 at 00:00

The Green Web Foundation have published a thorough and insightful report into the sustainability of AI, and the results are pretty damning. Over the couple of years I’ve been talking about web sustainability I haven’t really touched upon AI a whole lot, partly as there wasn’t a lot of publicly available hard data about its environmental impact. Now, however, it’s become an issue that’s too big to ignore. We can’t not talk about it.

Previously I was under the impression that the majority of AI’s energy use happens during the training stage. Although the vast amounts of energy and water resources used for model training is bad enough, recent evidence appears to show that a large proportion happens in the inference stage — that’s the actual use of AI models, via tools like ChatGPT and AI-powered search engines.

One example from the report highlights that a typical Google search with AI could use between 4 and 30 times more energy than a traditional search:

A good example comes from looking towards the impact of incorporating AI into search engines such as Google. A single generative AI query could use 4 to 5 times more energy than a regular search engine query. Others found that average energy consumption per search could be 6.9–8.9 Wh, compared to 0.3 Wh for a standard Google search. This gives us an enormous range of 4-30 times larger. Whichever end of the scale the figures land, it’s a significant increase.

It makes for pretty depressing reading that companies are determined to plough ahead with shoving AI into everything, and obfuscate their emissions figures rather than focus their considerable resources on a sustainable future.

The report makes a great point that when AI use cannot be avoided, there are ways to use it responsibly. As many developers may feel they have no choice, given that companies appear to view AI as the only thing that will make them relevant, it is important to be able to advocate for responsible AI use and insist upon transparency.

Read the report

A Versatile Markdown Shortcode for Eleventy

18 October 2024 at 00:00

When writing blog posts in Markdown files I often find myself needing to add HTML elements that aren’t accounted for in Markdown. Some common ones are <aside> elements, where I include content tangentially related to the post itself, or external references.

Having to include HTML in Markdown is kind of a pain, because once you start writing HTML, you can’t then use Markdown inside it: everything inside the HTML element must also be HTML. This is pretty annoying for elements where you might want to include multiple paragraphs or links, for example. The markdown writing experience is far more pleasant.

Shortcodes

I use the static site generator Eleventy for this blog, and I decided to make use of a great Eleventy feature: shortcodes.

Simple shortcodes

Shortcodes are created with a Javascript function in the Eleventy config file for a project. There are two types of shortcodes in Eleventy. The simpler of the two takes a number of arguments and can be rendered with a single line in a Markdown file. For example, if we created a shortcode called “aside”, which renders an <aside> element with a single line of text, it would work like this using the Nunjucks templating language, which this blog uses:


{% aside "This is an aside" %}

Other templating languages work differently, and the Eleventy docs include references for using shortcodes in various templating languages.

To create the simple shortcode in our eleventy.config.js file we can use Eleventy’s addShortcode function. Here’s how it looks:

eleventy.addShortcode("aside", (content) => {
	return `<aside>${content}</aside>
})

Here’s how that renders in HTML:

<aside>This is an aside</aside>

This isn’t all that different to just writing the HTML in the first place, so you might wonder why we’d bother with a shortcode. It’s far more useful however, when we want to render more verbose HTML, such as adding classes or nested elements. Here’s one called “hotlink”, which I use for rendering a link that looks like a button:


{% hotlink 'https://css-tricks.com', 'Read it on CSS Tricks' %}

It’s defined as follows:

eleventyConfig.addShortcode('hotlink', (url, title, target = '_blank') => {
  return `<a class="hotlink" href="${url}" target=${target}>${title}</a>`
})

Paired shortcodes for more verbose content

A simple shortcode probably isn’t going to cut it in all cases though. There’s a good chance that in our <aside> we might want to include links, multiple paragraphs, or other elements, which the simple shortcode doesn’t allow.

For this we can use a paired shortcode. This allows us to use a template tag with markdown content inside it, which means we get the superior experience of writing markdown instead of HTML. Here’s how we can include an <aside> with a heading and multiple paragraphs:


{% aside %}
## Heading

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
{% endaside %}

A paired shortcode is defined using the addPairedShortcode function in the Eleventy config file:

eleventyConfig.addPairedShortcode('aside', (content) => {
  return `<aside>${content}</aside>`
})

This renders the markdown content as HTML inside our <aside> element.

A more versatile shortcode

Although this example works just fine, it only gives us an <aside> element. We could improve the utility of this shortcode so that it enables us to render any HTML element, and with a custom class too. Let’s adjust the code in eleventy.config.js:

eleventyConfig.addPairedShortcode(
  'element',
  (content, el = 'aside', className) => {
    return `<${el} class="${className}">${content}</${el}>`
  }
)

Our function now takes three arguments: content is the markdown between our opening and closing template tags. The second and third arguments define the element and an optional class name respectively. We’ll include a default value of ’aside’ for the element, as we don’t want it to every be nothing.

This is how we’ll include it in our markdown file:


{% element 'aside', 'pink' %}
## Heading

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
{% endelement %}

This will render an <aside> element with a class of “pink” in our HTML.

I’ve Been Doing Blockquotes Wrong

16 October 2024 at 00:00

True to form, Heydon Pickering has written another blistering account of one of the most ubiquitous HTML elements, the <blockquote>. You’ve probably used a <blockquote> when writing HTML. I know I’ve used literally hundreds of them. What I didn’t know is that I’ve been using them wrong all these years.

To pilfer a few choice quotes from said article:

In HTML5, <blockquote> content “must be quoted from another source”. So that’s pull quotes completely out of the window, then.

Heydon rightly makes the distinction between block quotes, which (supposedly) quote another source, and pull-quotes, which highlight excerpts from the article you’re reading. I was surprised to learn that <blockquote> elements should not be used for pull-quotes. I’ve been using them all over the place. Whoops.

...the term “block quotation” precedes the <blockquote> element and the concept of block-level HTML elements. The Chicago Manual Of Style recommends block quotations are over 100 words in length, for example.

Once again, I’m doing block quotes wrong. I don’t think I’ve knowingly ever included a quote over 100 words long on a web page. Does anyone ever read that many words on the web? You’ll notice neither of the block quotes so far in this article are over 100 words in length. (To be fair, this isn’t part of the HTML spec. But might well influence how you define a “blockquote”.)

Different versions of different specifications agree the citation cannot be inside the <blockquote> element.

Oh dear. I’ve been putting a <cite> inside a <blockquote> for years. I distinctly remember reading that this was a good idea at some point in the last 10 years, and haven’t checked back since. I’m sure in part it relates to this idea (also from the article):

Related elements should generally be programmatically grouped so its clear what belongs to what.

Heydon suggests using a <figure> and <figcaption> to invoke this grouping, which seems the most sensible option.

I also learnt about the <q> element for the first time in this article:

For inline (or “text-level”) quotations, there is <q> instead.

OK. So...is it not enough to put actual quote marks in the text (“like this”, which I’ll wager is what happens in 99.999% of cases)? I mean, it has a cite attribute, but given that according to Heydon, “The <blockquote> cite attribute is generally useless since it’s invisible and most screen readers also ignore it”, I can’t see any real use cases. It it just a styling thing? In which case, wouldn’t a <span> suffice? I’ll eagerly await Heydon’s <q> article to find out.

HTML is Hard

All of which is to say, HTML is hard. Probably the hardest part of my job. In all the years I’ve been writing articles, I’ve received far more complaints/corrections about HTML than anything else, and I consider myself pretty OK at HTML. And despite writing it for years, I don’t feel like I really know it well (see above).

Conversely, it’s pretty easy to write bad HTML, because for most developers there are no consequences. If you write some bad Javascript, your application will probably crash and you or your users will get a horrible error message. It’s like a flashing light above your head telling the world you’ve done something bad. At the very least you’ll feel like a prize chump. HTML fails silently. Write bad HTML and maybe it means someone who doesn’t browse the web in exactly the same way as you do doesn’t get access to the information they need. But maybe you still get your pay rise and bonus.

So it’s frustrating to see the importance of learning HTML dismissed time and time again. Sorry to end on a bad note, but that’s where we’re at right now.

I recommend reading Heydon’s article, and the others from the series on HTML elements, which is far more comprehensive (and, I dare say, better written) than this one, despite the fact that I’ve lifted a load of quotes from it verbatim. And by the end of the series, hopefully we’ll all be experts in HTML.

Edit

Adrian Roselli has a great rundown of how different screenreaders announce various combinations of <blockquote> and <cite>. Reading though these, I don’t think nesting <cite> inside <blockquote> is a totally terrible choice, even if it’s technically wrong.

Notes From Green IO Conference

22 September 2024 at 00:00

Last week I attended Green IO, a conference in London all about sustainability in digital technology, organised by Gaël Duez (who also hosts the Green IO podcast). It was a fantastic conference, and amazing to see so many people who are passionate about this stuff all in one room!

I’m not great at taking notes during talks, but there were so many great takeaways I wanted to sum up a few while it’s fresh.

Consensus is key

Chris Adams of the Green Web Foundation gave the opening talk of the day, which set the tone for the conference: Digital sustainability relies on consensus, much like the internet itself.

The idea that practical, working systems that can be quickly implemented are a better starting point than waiting for the perfect design.

View Chris’s slides

So is working together

Working together to make change happen was another prevalent theme of the day (and arguably the conference itself was a prime example of this!). Alex Dawson gave an overview of the Sustainable Web Design Guidelines proposal, which he has been instrumental in drafting, along with members of the W3C Sustainably Web Design Community Group. These are a practical set of guidelines for web designers and developers to build greener websites. Alex pointed out that they can be implemented incrementally, allowing for gradual adoption.

The plan is for the community group to become to become a working group, and to push for the guidelines to be increasingly adopted by tech companies and the community as a whole. This is an example of how a group of people can come together to weald greater influence than working individually.

There’s a more user-friendly version of the guidelines on the Sustainable Web Design website.

Measuring is important, but not everything

Chris Adams also talked about the evolution of the Sustainable Web Design (SWD) model used for measuring the CO2e emissions of digital services, and how our understanding has evolved. Quantifying carbon emissions is important, and can be useful, but we don’t have to wait for perfect data (which may never arrive) before taking action.

Greenwashing is prevalent in Big Tech

Big tech companies’ reporting of emissions data and claims of “Net Zero” emissions cannot be trusted. Speakers highlighted the difference between companies’ reported figures, which are market-adjusted, and their location-based emissions, which are far higher and growing — particularly as a result of the AI boom. (A recent Guardian article puts the spotlight on tech firms’ “creative accounting” practices when it comes to reporting on carbon emissions.)

Cloud services are not necessarily greener than on-premises servers, and the claims of the big players (such and Amazon and Microsoft) came under scrutiny at this conference, particularly in Mark Butcher’s excellent talk, which pulled no punches. Where servers are located matters — in countries with a high proportion of fossil fuels, the electricity generated will be “dirtier”.

Efficiency isn’t enough (Jevon’s paradox)

More than one speaker mentioned Jevon’s paradox — the idea that increasing efficiency leads to increasing demand. Making our tech more efficient often results in increased usage rather than an overall reduction in energy use, much like building an extra lane on a highway doesn’t actually lead to a decrease in congestion: it simply leads to more cars on the road. This means that improving the efficiency of our tech isn’t enough alone.

More than carbon

Another common thread was that we need to think beyond carbon emissions. Although emissions are perhaps the most obvious impact of our digital tech — due to the clear link between energy demand and fossil fuels — they’re just one part of the story. We need to consider the entire lifecycle of our tech, which includes resource consumption — water use (a growing concern, exacerbated by the AI boom) and mining for the raw materials needed for digital devices — as well as pollution of the local environment (e.g. where data centres are located), disposal of hardware at the end of its useful life, and exploitation of labour, which underpins many aspects of the digital realm, just as in other industries. There can be no sustainability without equity.

The AI boom is (mostly) bad for the planet

It’s clear that the compute power required by AI is accelerating the demand for energy. We’re even seeing old fossil fuel plants brought back online to serve this increased demand. In the afternoon panel discussion with Chris Adams, Anne Currie, Maxime Fazilleau, Sandra Pallier it felt like there was a broad consensus that Generative AI doesn’t align well with sustainability.

It’s not all about model training either. According to James Martin from Scaleway, the inference (i.e. usage) phase of an LLM such as GPT4 can be “200 times more impactful than training“. A Google search with AI, for example, uses far more energy than a conventional search. AI can have some uses, including helping avoid waste and making certain processes more efficient. But many of the uses that Generative AI is being put to do more harm than good.

But if you have to use AI, make it as green as possible

There are certainly ways to make using AI greener, such as using more efficient models, and using servers located in countries with a higher proportion of green electricity. Anne Currie from Strategically Green shared a few tips during the panel, and she has also written a book on the subject.

Device use accounts for the most CO2e emissions

Streaming video is an area that unsurprisingly accounts for a great deal of energy use. Benjamin Schwartz shared insights from from Greening of Streaming’s three-year initiative exploring the end-to-end carbon impact of video streaming. This is a subject I don’t have much knowledge of, so it was great to hear that there are people working on (for example) more efficient ways to encode video. Most of the energy consumption happens on the end user’s device, so we should really question whether everything needs to be streamed in HD.

Greener defaults

Following Benjamin’s talk, a question from an audience member prompted a discussion on whether lower resolution should be the default for streaming platforms such as Netflix. As Benjamin pointed out, a button saying “click for a greener experience” looks pretty bad because it shows the default isn’t green. But a greener experience as a default, with a button to upgrade to enhanced experience (perhaps if you have friends round to watch a movie together) is better. For many people, streaming in standard definition will be no big deal.

This applies to many other aspects of web design too. In Thorsten Jonas’s talk on designing sustainable digital products (probably my favourite talk of the day) he emphasised that greener doesn’t have to mean inferior. You don’t have to remove all the images from your webpage, but replacing a background video with a static image will deliver huge savings, while likely making your website faster for users too.

Think beyond UX

I loved how Thorsten’s talk encouraged us to think beyond our products’ end users and take a humanity and environmental centred approach. Our digital products have an impact far wider than just the people who use them. As well as who we’re building for, we should think about what we’re building, how and why. We should start thinking of the planet as a key stakeholder in our product designs. There were so many quote-worthy moments in this presentation, but to single out one of Thorsten’s slides (a quote from Toby Fry):

Does what we create justify what we destroy?

Green measures lead to cost savings – but sustainability is a bigger motivator

Despite a lot of consensus on sustainability in tech, it can be difficult to convince others in an organisation to implement change. Unfortunately I forgot which presentation this was from (I’m bad at taking notes during conferences!), but one speaker referenced a study where teams were given different performance metrics. One team was given the target of only reducing cost, the other team was told to focus on sustainability. Surprisingly, the team with the sustainability focus succeeded in not only improving sustainability, but reducing costs too, and by an even greater amount than the one focused purely on costs. It demonstrated how much of a powerful motivator sustainability can be — and one we can all benefit from.

Limitation Breeds Creativity: A Study in Composition with Custom Properties

16 September 2024 at 00:00
A grid of 9 bright pink squares with black, white and lime green shapes arranges in different compositions

It’s been a little while since I’ve played around with making creative stuff with CSS just for fun. My trip to State of the Browser conference at the weekend reminded me why I love the web and the creative community around it. If you haven’t already, you should check out Hidde’s summary of the conference. Every talk was absolutely top quality (and some were downright jaw-dropping — Katie Fenn’s live Daft Punk performance in particular!).

Sophie has written a lovely post on why you should go to conferences, and I couldn’t agree more. She includes a list of great web conferences to suit any budget. Attending and speaking at conferences has been 100% worth it for me!

Now, on with the creative stuff. I often find myself thinking of creative ideas (for drawing, painting, writing — anything!) but find myself with a complete lack of time to execute them. So being able to create something super quick is highly appealing, and often serves as a catalyst for sparking new ideas and spending time on something bigger. No, I’m not talking about generative AI! Part of the reason I love the web is that, for me, it’s a medium that allows me space to experiment and make stuff quickly, and the result can easily be refactored, redesigned or thrown away if I don’t like it.

One thing I quite enjoy at home is making small collages with scrap paper — just gluing a few random shapes in a pleasing way to create different patterns and compositions. I enjoy being limited by whatever I happen to have to hand, and seeing whether I can make something that looks nice. I had the idea of redesigning my personal site, using compositions of simple geometric shapes as a motif (who knows whether I’ll get around to doing this!). As a bit of a brainstorm, and to scratch the creative itch, I came up with the idea of creating a simple composition using three CSS gradients, then remixing them with custom properties to see what different outcomes I could generate while keeping the same feel.

To prevent myself getting carried away and spending hours tweaking things, I decided to limit myself in a similar way to having just a few scraps of paper to play with. Each composition has the same base colour background, and same three background gradients that can be adjusted in a limited way with custom properties. The colours can’t be varied, only a few details like the size and position of the circle (made with a radial gradient), or the angle and width of the linear gradient. No additional elements or pseudo-elements are allowed.

Here’s the result!

See the Pen Gradient background composition exercise by Michelle Barker (@michellebarker) on CodePen.

This was a really fun exercise in composition and creativity within narrow limits. I’m definitely going to try out some more ideas.

Logical Properties in Size Queries

11 September 2024 at 00:00

I came across a post from Elad Shechter lamenting not being able to use logical properties in media queries. As he correctly notes, the following won’t work:

@media (max-inline-size: 1000px) {
  .main-content {
    max-inline-size: 800px;
    margin: 0 auto;
  }
}

Media queries now support a new range syntax. Instead of writing min-width or max-width, we can use the “greater than” or “less than” operators (> and <), or “greater than or equal to”/“less than or equal to” (>= and <=). So we could write the above example a different way:

/* Styles are applied when the inline-size is less than or equal to 1000px */
@media (width <= 1000px) {
  .main-content {
    max-inline-size: 800px;
    margin: 0 auto;
  }
}

This feels more consistent with other programming languages, and lends itself well to more concise code when dealing with multiple size conditions:

@media (min-width: 500px) and (max-width: 1000px) {...}

/* becomes: */
@media (500px <= width <= 1000px) {...}

It gives us a choice in terms of how to write it too. These two media conditions both mean that the styles should be applied when the width is greater than 500px. We can write it either way:

@media (500px < width) {...}

@media (width > 500px) {...}

To me, this syntax feels better suited to logical properties, and you can use logical properties in container queries. These both work:

@container (min-inline-size: 500px) {...}

/* becomes: */
@container (inline-size >= 500px) {...}

So why not in media queries? It seems like an oversight, but I’m also inclined to think that in the not too distant future we might not need media queries for querying size. We could just make the :root a container and query that instead:

:root {
  container-type: inline-size;
}

@container (500px < inline-size < 1000px) {
  body {
    background: blue;
  }
}

Except...

We can’t query the block-size (equivalent to the height in horizontal writing modes) with container queries. The following won’t work:

:root {
  container-type: block-size;
}

@container (block-size > 500px) {
  body {
    background: blue;
  }
}

Speaking personally, it’s very rare that I need to query the viewport height. CSS has viewport units of course, which are great for sizing things relative to, well, the viewport. But it’s not totally unheard of. So we may need size-based media queries for some time yet, and being able to use logical properties with them would certainly be useful!

The Problem With Surveys (and Why You Should Take This One)

30 August 2024 at 00:00

If you’re a regular user of CSS (and hey, if you’re reading this you probably are) you should definitely complete the State of CSS survey, an annual survey of CSS features. Registering your opinion of CSS features, frameworks, tools and resources is helpful to the CSS world as it helps browsers and working groups figure out what to prioritise. If enough people shout loudly enough about a particular feature then it increases the chance that we’ll get to be able to use that feature a bit sooner.

Rightly or wrongly, I often think there’s a bit of intrinsic bias built into tools like surveys. The people most enthusiastic about taking the survey are often the ones who pride themselves in keeping up with the latest CSS news, and are more likely to have experimented with new features. (I include myself here.) In my experience speaking at conferences and working with other developers, I come across a far larger proportion of front end and full-stack developers whose knowledge of CSS is sufficient to do their job, but which doesn’t extend to the latest and greatest features. This is purely anecdotal, of course. But my hunch is that with the lay-offs and cut-backs we’ve seen in the past year or so, CSS is becoming further deprioritised in the jobs market, and companies are favouring “full-stack” candidates for new roles. It means that even while the development of new CSS features is accelerating, the depth of knowledge in the wider community of developers is declining. Paradoxically, the people who already view CSS as “hard” are the ones who might benefit the most from the new CSS features designed to make developers’ lives easier. But being less involved with the CSS community, they are perhaps less likely to take the survey, and therefore their views aren’t represented.

A survey isn’t the only way to register your interest in CSS, of course. You could comment on Github issues (or raise your own), or write or speak about CSS in the community. But if you’re a member of an under-represented group, there’s all the more reason to take the survey. We need more than the opinions of straight white men in order for technology to thrive and to ensure it serves the needs of a diverse population.

Where Are All the Not-men of CSS?

Speaking of diversity...in last question of the survey participants are encouraged to list individuals whose work they follow in the community. I usually list a bunch of people here, there are so many individuals doing great work writing blogs, publishing demos and making videos. One thing I noticed this time around is the number of men I’m aware of doing this stuff has increased, while the proportion of women, non-binary, or non-gender conforming people has declined. Maybe I’m just not following the right people? Or has the ratio of men to others become more skewed? Perhaps all the women and NB folk are burnt out by work, life and the state of the world in general to care too much about CSS right now, or to spend time waxing lyrical about it? (I know I am.) Honestly, being able to spend large amounts of time exploring CSS feels like a luxury as the moment, when the job market is so hostile towards anything other than JS and “full-stack” development.

It could well be just my perception that’s changed. But it’s a far cry from 2019, when I spoke at a CSS conference that featured six out of seven female speakers. It feels a little like we’re going backwards.

Take the State of CSS survey

Styling Tables the Modern CSS Way

18 July 2024 at 00:00

In this article for Andy Bell’s blog Piccalilli, we explore something I’ve been consumed with styling a lot recently: data tables. Not to be confused with using tables for layout (as in the bad old days of CSS), HTML tables are vital for displaying tabular data (data with row and column relationships) accessibly. They come with a few quirks, however, and some less-familiar CSS properties that are handy for styling.

Head on over to Piccalilli to learn more, and check out some of Andy’s other great articles.

Read the article on Piccalilli

❌
❌