Convert a Keynote presentation to a set of hi-res images

To create presentations I like to use Keynote, mainly thanks to its animation feature. I admit that it took me some time to get accustomed to it – and that not all is perfect – but I think I’ve become efficient at using it over time. The transitions and animations – such as seen in the recording of my CSS Day talk on the Cascade – really help convey the message.

One of my gripes with it though, is that the built-in functionality to export your slides to a set of images gives a very, very, poor result. The exported images all seem blurry, even when set to the highest setting.


Thankfully, Keynote can perfectly export to PDFs just fine. With that as a universal format, it can easily be converted to a set of images. To achieve the latter, I used pdftoppm which is part of the Poppler PDF rendering library. On Mac, Poppler can be installed using Homebrew:

brew install poppler

With Poppler installed, my procedure to convert a Keynote presentation to a set images looks like this:

  1. Make presentation in Keynote
  2. Export presentation to PDF using Keynote’s built-in “Export to PDF” functionality
  3. Convert the PDF to a set of images with pdftoppm:
    pdftoppm -png -progress presentation.pdf image

There’s a bunch of other options you can pass into pdftoppm, such as the ability to limit which pages you want to convert, sizing options, JPG compression settings, etc. To know which ones you can use, invoke pdftoppm -h


For comparison, here’s two exports. The first one using Keynote’s “Export to Images” functionality, the second one using pdftoppm.

Slide exported using Keynote’s built-in functionality
Keynote’s built-in “Export to Images”
Slide exported using pdftoppm

The pdftoppm one is much better, right?


🔥 Like what you see? Want to stay in the loop? Here's how:

Escaping the sRGB Prison

At the most recent CSS Day, Chris Lilley gave a talk titled “Escaping the sRGB Prison”.

Imagine that I told you that you are only allowed to use two-thirds of the colors that your screen can display. All the brightest and most vivid shades are not allowed. Unacceptable, right?! Welcome to Web design for the last seven years. You might not know it but compared to native apps, you are in the sRGB Prison. After attending this talk you will understand the measurable, reproducible sensation we call color. You will understand Lab and OKLab color spaces, be comfortable with gamut volume plots, and be able to laugh at snake-oil claims about color gamut coverage in advertising.

It’s a very insightful talk and should get you excited about the support for more Color Spaces and new color functions that are landing in browsers.

In addition to his talk, Chris also recently published “What are color gamuts” on his blog which is worth a read.

JavaScript: Restart all CSS Animations of an Element

Recently built a demo that demonstrated a specific animation. Only problem: if you missed it, you had no way of restarting it. Instead of forcing the visitor to reload the page, this little JavaScript-snippet – attached to a button click – did the trick:

const restartAnimations = ($el) => {
	$el.getAnimations().forEach((anim) => {

Pass in an Element, and it will will loops all attached animations bound to it. For each animation it restarts them by triggering a cancel + play.



See the Pen
JavaScript Restart Animations
by Bramus (@bramus)
on CodePen.


🔥 Like what you see? Want to stay in the loop? Here's how:

The Future of CSS: Variable Units, powered by Custom Properties

Recently the CSS Working Group approved to start work on the css-variables-2 specification. First planned addition is support for “Custom Units”. Let’s take a look.


👨‍🔬 The CSS features described in this post are still being developed and are not finalised. It might take a long time before this lands in any browser.


# Variable Units 101

The idea behind Variable Units – first suggested by Jonathan Neal in this tweet and now specced by Tab Atkins – is to allow authors to define custom properties which can be used as units.

For example:

:root {
  --size: 8px;

Here the Custom Property --size is set to 8px. To use it in your CSS, you would need to write this:

elem {
  width: calc(4 * (var(--size))); /* = 4 times the --size = 32px */

Thanks to Variable Units, this can become shorter and easier to write. Just like how you can use the em unit, you use the --size property as a unit, like so:

elem {
  width: 4--size; /* = 4 times the --size = 32px */

Much shorter to write, and once you know how to read it, it’s fairly easy 🙂


# Digging deeper

Because you can put just about anything in Custom Properties, you can make Variable Units a tad more complicated than the simple 8px from before.

:root {
  --fem: clamp(10px, 1vw + 1vh, 1rem);

Throw in @property to register your Custom Property, and you don’t need to define the property on :root anymore + you will be able to animate the value. Furthermore it will fallback to the initial value, should you assign a non-<length> to it.

@property --fem { /* "fluid em" */
  syntax: "<length>";
  initial: clamp(10px, 1vw + 1vh, 1rem);
  inherits: true;

.fluid-type {
  font-size: 1.2--fem; /* Won’t get smaller than 12px, or larger than 1.2rem. */


# Polyfilling New Units

The cool thing about this feature is that also opens up the way to polyfill new units, should a browser not support them yet. Take this fictitious brm unit for example:

  1. Alias the new unit to its -- counterpart. Browsers with support for brm will use the initial value.

    @property --brm {
      syntax: "<length>";
      initial: 1brm; /* browsers with support for `brm` will use this */
      inherits: true;
  2. In case of no support for that new brm unit, have a JS polyfill calculate the length to use instead, and set that the initial value

  3. Use the custom unit throughout the code

    height: 100--brm; /* Will use the real brm unit, or the polyfilled version if no support */


# In Closing

If you want to follow along, or have some feedback on the syntax, you can do so at these links:


# Spread the word

To help spread the contents of this post, feel free to retweet the announcement tweet:


🔥 Like what you see? Want to stay in the loop? Here's how:

Show CSS browser support data with css-browser-support

If you want to include Browser Support tables on your site, you can use this package by Stephanie Eckles

Query for CSS browser support data, combined from caniuse and MDN, including version support started and global support percentages.

Per feature that ask it, you get back an object with the browsers and the data:

  'aspect-ratio': {
    chrome: {
      sinceVersion: '88',
      flagged: true,
      globalSupport: 22.46,
      browserTitle: 'Chrome'
    chrome_android: {
      sinceVersion: '88',
      flagged: false,
      globalSupport: 41.34,
      browserTitle: 'Chrome Android'
    edge: {
      sinceVersion: '88',
      flagged: false,
      globalSupport: 3.88,
      browserTitle: 'Edge'
    // ... continued for all browsers
    globalSupport: 86.49

Stephanie also released a plugin for use with 11ty.


Do note that if you statically build your site, you will have to update dependencies and rebuild regularly to have up-to-date support data

Be the browser’s mentor, not its micromanager

Wonderful talk by Andy Bell.

We look at how we can hint the browser, rather than micromanage it by leaning into progressive enhancement, CSS layout, fluid type & space and modern CSS capabilities to build resilient front-ends that look great for everyone, regardless of their device, connection speed or context.

Slides available as well

Bun is a fast all-in-one JavaScript runtime

Bundle, transpile, install and run JavaScript & TypeScript projects — all in Bun. Bun is a new JavaScript runtime with a native bundler, transpiler, task runner and npm client built-in.

I’ve been following Jarred’s progress on Twitter over the past few weeks and it’s impressive what he has achieved. Building Bun, he also landed a ton of performance fixes to JavaScriptCore, which is used underneath.

Bun is a fast all-in-one JavaScript runtime →


UPDATE: This first look video should give you a good idea of what it’s all about:

GitHub: How we think about browsers

GitHub is currently shipping ES2019-compatible code, and will soon ship ES2020 code.

GitHub will soon be serving JavaScript using syntax features found in the ECMAScript 2020 standard, which includes the optional chaining and nullish coalescing operators. This change will lead to a 10kb reduction in JavaScript across the site.

Wow, won’t that exclude a whole bunch of browsers? No. Looking at the data, the majority of their visitors use the latest version of a browser, or the version before that (wow!).

This shows us that the promise of evergreen browsers is here today. The days of targeting one specific version of one browser are long gone. […] With that said, we still need to ensure some compatibility for user agents, which do not fall into the neat box of evergreen browsers. Universal access is important, and 1% of 73 million users is still 730,000 users.

To cater for older browsers they include some polyfills to plug the holes, but that’s not really needed:/p>

With JavaScript disabled, you’re still able to log in, comment on issues and pull requests, browse source code, search for repositories, and even star, watch, or fork them. Popover menus even work.

Yes, a thousand times YES!

GitHub: How we think about browsers →

What’s New in React Native 0.69

Photo by Lautaro Andreani on Unsplash

It’s been a while since I’ve done some React Native work, but the 0.69 release seems like a very welcome one: React 18, bundled Hermes, Fabric, TurboModules, and the JSI.

React Native 0.69 brings a ton of important improvements & updates to the table, with performance & memory usage being a major theme. Because some of the changes are opt-in, you’ll need to decide yourself which to enable and in what order. No matter which upgrade path or strategy you take, though, these changes bring exciting improvements to the React Native platform. It’s a great time to be a React Native developer!

What’s New in React Native 0.69 — How to Upgrade and Why it Matters →