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.

Low Overhead React

· CSS, JavaScript, Planning, React, Workflow · Patrick Smith

“React. Write reusable components. Make your data flow one way. Never directly use the DOM again.” There’s a lot of benefits that a developer gets by using React.

But React is easy to make complex. It’s so very tempting to optimize early. There are a lot of decisions left to you on how to architect your web app.

The techniques listed here will often make your React app have less work to do, and therefore run faster. Implicit performance is a benefit if it means less time later adding optimizations to claw back to a fast experience.

But the primary aim is to make your app easier to write, easier to change, and easier to debug. Less overhead for the person maintaining the code. Writing low overhead React means you can more easily focus on creating that great experience for your users, while not sacrificing the developer experience.

Low Overhead React Principles

  1. Don’t abandon CSS
  2. Choose packages carefully
  3. Ensure every component does at least one thing well
  4. Don’t let every component talk with the world
  5. Don’t attempt to optimize React rendering too early

Don’t abandon CSS

Look, CSS (or SCSS) has its limitations. JavaScript is obviously more capable as a language. But CSS is proven for its task. It is fast. It can declaratively handle focus and active and hover states, and it can handle a variety of media queries. It just works with server rendering, and is interoperable with technology choices other than React.

Use CSS variables to create reusable styles for typography, spacing, colors, and other things that are consistent across components.

html {
  --link-color: orange;
}

main a {
  color: var(--link-color);
}

You override CSS variables when rendering by using the style prop:

<article style={{ "--link-color": "purple" }}>
  …
</article>

(You can see an example like the above here: https://codesandbox.io/s/react-css-variables-chbsj)

CSS is still evolving. The standards are adding more capabilities. There are new approaches to organizing and reusability. Not to mention layout.

If you use CSS-in-JS, it doesn’t mean you are banned from using standalone CSS. Base CSS files work great for reusable styles across components.

Choose packages carefully

I’ll be honest: there are a lot of heavy packages out there. Calendars. Themeable kits that come with every UI control you can imagine. State and data structure libraries. Many of these dependencies have a big impact on the bundle size, and therefore the time your users will spend waiting for your application.

This doesn’t mean stay from them. Use some, but don’t just add all your first choices. Don’t say yes unless it really provides key value. A package that makes your experience better as a developer (let the team finish faster!) might make your users’ experience worse (let the user start slower!).

Yes, tree shaking can help improve the initial load time, but if you have large packages that you use as the foundation of your app, most of the tree will remain no matter how hard you shake it. And if the shaken out parts are soon downloaded after anyway, you might be consuming a lot of your users’ limited data. We don’t need to pretend NPM doesn’t exist. But we do need to remember that every npm add is also often adding wait time for every user.

Ensure every component does at least one thing well

Presentation vs Controller. Smart vs Dumb. Connected vs Reusable. Organism vs Molecule. There are many ways of categorizing and splitting-up components into specific roles. I’m not going to offer an opinion here because many ways work well. What I am going to ask is to make sure every component has a role it does particularly well.

You might decide this means the single responsibility principle, so that a component can only be purely HTML focused, or purely subscriber focused, or purely Redux connect focused, or purely analytics focused, etc etc. I leave that up to you, because there often is value in a component doing multiple things. If a component is a function, then it serve to reason that it can be made out of multiple other functions, and these functions can have a single responsibility.

All I request is that you ensure each component does at least one thing particularly well. For example: It’s great at producing markup. It’s great at talking to the rest of the app. It’s great at optimizing render times for this particular case. It may well do more, but at the least it is competent in some particular thing.

If it’s not doing a great job at anything in particular, if it’s trying to speak HTML and API and Redux and URL all at the same time, then it might be time to split it up. A dedicated smaller component can focus on a particular task and communicate its intent better. It’s often fine to keep it in the same file.

Don’t let every component talk with the world

A component is easiest to manage when it follows a simple contract: given some input, I will return what I want presented. Different input, different output. No other factors.

A slightly more complicated component contract is that: in addition, I will produce some side effects. I will POST to a server, I will talk to a local data store, I will add an event listener directly to the DOM.

Another more complicated variation is I will read from outside of my props. I will fetch from a server, I will subscribe to the latest changes to a local data store, I will listen to when the window changes size.

These components can get out hand. Many components start talking to many things. When hundreds of components have a direct connection with the local data store or communicate with the server. Or when the user resizes the window and many dozens of components individually want to update themselves.

I’m not advocating for a particular architecture or a particular number of communication points or set a limit to how many components are allowed to talk to the server. I’m saying be mindful of how many connections there are between components and their outside world. Otherwise a couple of simple changes in the outside world can schedule a huge amount of work.

This may not only lead to performance bottlenecks, but it will also make debugging and future changes harder. Which component made that change? If I rename or restructure this reducer, how many components had this knowledge and so will need to be updated? It’s best to make these questions easier to answer by reducing the surface area of connections.

Make smaller worlds for your components by bundling collaborators together, and then export a simpler component that will be used to hide the details and nuance inside. Add single connection points that bridge between one world and another. Add facades with small surface areas your components see, that then go and talk to the larger world, translating back and forth. Don’t just let any random component talk to whatever it likes. It might feel convenient now, but it’ll likely become inconvenient when changes are needed.

Don’t attempt to optimize React rendering too early

Don’t optimize your components until there’s a clear problem to be solved. React is designed so that straight forwardly written components will lead to great performance!

If do you want to apply optimizations, first read the documentation provided:

If these don’t answer your question, then reach out to people from the React core team like Dan Abramov who happily answers people’s questions on Twitter.

Next, measure using the open source React DevTools which is available for Firefox and Chrome.

Measure the current profile by clicking record, performing the particular flow that you wish to make more performant, then stopping the record. Use the interactive charts to see the bottle necks and which components need attention.

Make changes, and profile again. It may take several iterations because your expected optimization technique might make things slower.

If you have existing optimizations like React.memo in place, it might be worth removing them all, and then using the React DevTools profiler to introduce them where you see a meaningful improvement.

Using React.memo relies on every prop passed to a component staying the same (being referentially equal) across renders to skip its own rendering. This can be infectious, leading to all your components having to follow this extra contract on their props coming in. Trying to make everything referentially equal can be a lot more computational work that can lead to other performance overheads. And it’s very easy to pass just a single prop that does not stay consistent (say an arrow function), leading to the intended optimization being never applied.


React. It’s unique selling point is that it makes writing web apps easier. Yet it’s easy with React to find yourself battling complexity, or for users to be getting penalised performance. Being mindful about the overhead that some techniques and third party packages bring helps keep complexity low, performance easy to find, and future changes maintainable.

Typed Subatomic Styling: Part 1 — Benefits of TypeScript and CSS

· CSS, JavaScript, React · Patrick Smith

The benefits of components have been realized with systems such as React and VueJS. Styling these components has a number of approaches, from CSS classes written in SCSS, inline styles, and dynamically generated stylesheets via something like emotion.

Each styling approach has trade-offs in regards to reusability, performance, and developer experience. Also, special considerations often have to be made when doing server-side rendering.

In this post, we’ll explore TypeScript combined with functional CSS. I believe it offers big benefits over the other choices — it’s extremely reusable, fast, and offers a very pleasant developer experience.

Read more…

The difference between CSS Grid and Flexbox

· CSS · Patrick Smith