Vary variables not rules in CSS media queries

· CSS · Patrick Smith

A common pattern you’ll see when implementing responsive design with CSS is to redefine rules in media queries:

h1 {
  font-size: 2rem;
}

@media (min-width: 720px) {
  h1 {
    font-size: 2.5rem;
  }
}

@media (min-width: 1200px) {
  h1 {
    font-size: 3rem;
  }
}

Here we are writing our media queries mobile first. This means the styles for small devices (like phones) are defined first, such as our font size of 2rem for h1 elements. Then a wider device is targeted with a media query that increases the font size of h1 elements to 2.5rem when the viewport width is 720px or greater. And then another one, increasing to 3rem for viewports 1200px or wider.

So what are the issues with this approach?

First, these media queries can get bloated. You’ll find that you’ll define your base rules first using a mobile-first approach, and then repeat those rules with overrides within media queries. You could put all of these rules for the same media query together underneath, or could you have the media query override live next to its mobile-first rule and rely on some sort of CSS optimiser or compression so the repetition doesn’t contribute to an increase in file size.

Second, knowing what to override can lead to complex code. Perhaps you only need to override the rules for nav a and not for nav elements within your media queries. These specifics lead to harder to understand code.

nav {
  background-color: lightskyblue;
}
nav a {
  display: block;
  padding: 0.5rem;
}

@media (min-width: 720px) {
  /* We override the rule for `nav a` but not for `nav` */
  nav a {
    padding: 1rem;
  }
}

Perhaps you used quite complex selectors that now need to be repeated for each media query. SCSS allows the media query to be written nested inside the rule, avoiding the need to repeat it as an author. But this still adds a layer of complexity that can make understanding which rule applies in what case that much harder.

Here’s the above two examples but written to take advantage of SCSS’s nesting of media queries:

h1 {
  font-size: 2rem;

  @media (min-width: 720px) {
    font-size: 2.5rem;
  }
  @media (min-width: 1200px) {
    font-size: 3rem;
  }
}

nav {
  background-color: lightskyblue;
  
  a {
    display: block;
    padding: 0.5rem;

    @media (min-width: 720px) {
      padding: 1rem;
    }
  }
}

The result is very direct — these rules have these specific media query overrides. But there a lot of indentation going on, and things get hard to read once you add more rules and more overrides.

In summary, by redefining our rules inside media queries they have a lot of responsibility. They contain knowledge about our selectors. They override some rules but usually not all of them.

Varying CSS custom properties

So what’s the alternative? If we start using custom properties (aka CSS variables), which we can do if we no longer support Internet Explorer, then our media queries can take on a lot less responsibility and thereby be a lot simpler.

/* Define our variables, with mobile-first values */

:root {
  --heading-1-size: 2rem;
  --nav-link-padding: 0.5rem;
}

/* Override just the variables for different viewports */

@media (min-width: 720px) {
  :root {
    --heading-1-size: 2.5rem;
    --nav-link-padding: 1rem;
  }
}

@media (min-width: 1200px) {
  :root {
    --heading-1-size: 3rem;
  }
}

/* Define all our rules, consuming those variables */

h1 {
  font-size: var(--heading-1-size);
}

nav {
  background-color: lightskyblue;
}
nav a {
  display: block;
  padding: var(--nav-link-padding);
}

Making our variables responsive lets our rules be a lot simpler. Everything is flatter without the multiple levels of indentation. And we don’t need to use SCSS to attempt to reduce repetition, we can just stick with CSS!

This works not only for viewport width checks but also for media queries that check for dark mode or reduced motion or which features are supported.

:root {
  --text-color: white;
}

@media (prefers-color-scheme: light) { /* We respect a user's browser preference */
  :root {
    --text-color: black;
  }
}

[data-mode=light] { /* We allow users to switch light/dark mode */
  --text-color: black;
}

p, h1, h2, h3, h4, figure, ul, ol {
  color: var(--text-color);
}

Our CSS rules can be none the wiser to what value a --text-color is under the hood, which is set to white by default and black when the user prefers a light color scheme. Our variables vary and our rules stay the same.

Tips for concatenating URLs in JavaScript

· JavaScript · Patrick Smith

You might have a base URL from configuration, that you then concatenate with paths to produce a full URL. Say an API URL you fetch from.

But should you provide the base URL with or without a trailing slash? e.g. Should you have https://api.example.org/ or https://api.example.org?

If you simply combine the strings together, you can end up with double slashes:

const baseURL = 'https://api.example.org/';

const productsURL = baseURL + '/products';
// 'https://api.example.org//products'

We could try and check for the double slash, but that’s a pain:

const productsURL = baseURL +
(baseURL.endsWith('/') ? 'products' : '/products');

Would you just rather not worry about this? Turns out we can with the built-in URL class. It’s supported in all modern browsers (not IE).

const baseURL = 'https://api.example.org/';

const productsURL = new URL('/products', baseURL).toString();
// 'https://api.example.org/products'

Isn’t that better? It’s also available in Node.js and Deno, so we can use the same technique everywhere.

Tim Bray on subscribing to publications

· Product · Patrick Smith

Tim Bray writes why he won’t subscribe:

Their arithmetic didn’t consider their chance of getting me to click on “Subscribe.” In my particular case, that chance is almost exactly Zero. I subscribe to enough things and I am acutely reluctant to give anyone else the ability to make regular withdrawals from my bank account. … It’s exactly because I’ve done some subscribing that I’m just not gonna do any more.

Biasing your business to producing value in a public manner

· Marketing · Patrick Smith

Some ancient 2011 advice from Patrick McKenzie:

Businesses create value with almost everything they do.  The lion’s share of the value is, like an iceberg, below the waterline: it cannot be seen from anywhere outside the business.  Create more value than you capture, certainly, but get the credit for doing so, both from Google and from everybody else.  This means biasing your business to producing value in a public manner.  Did you write software not core to  your line of business?  Great, OSS it.  Get the credit.  Have you learned something novel about your customers or industry?  Write about it.  Get the credit.  Are your business’ processes worthy of emulation?  Spread what you know.  Get the credit.

37Signals is amazing at this. You can do it, too.

Get good at SEO — You need more links. Create ways to justify people who aren’t in a commercial relationship with you linking to you anyway. My favorite way for doing this is getting the credit for things you do, as described above.

https://www.kalzumeus.com/2011/06/17/software-businesses-in-5-hours-a-week-microconf-2010-presentation-1-hour/

There are also slides for the talk.

My most used commands for front-end

· JavaScript, Workflow · Patrick Smith
# Measure HTTP response time
httpstat "https://example.org/"

# Run Google’s PageSpeed Insights
npx psi "https://example.org/" --strategy=mobile

# Run Axe Core across multiple breakpoints
npx evaluatory "https://example.org/

# Optimize & minify SVG file
npx svgo <some-file.svg>

# Visualize JavaScript usage
npx bundle-wizard example.org

# Create a new TypeScript library with TSDX
npx tsdx create <project-name>

# Create a new web app with Vite
npm init vite-app <project-name> # Vue by default
npm init vite-app <project-name> --template preact
npm init vite-app <project-name> --template react

# Creates a Cloudflare Worker
npm i -g @cloudflare/wrangler
wrangler generate <project-name>