Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Thu, 13 Nov 2025 19:56:47 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 Browserslist & Baseline https://frontendmasters.com/blog/browserslist-baseline/ https://frontendmasters.com/blog/browserslist-baseline/#respond Thu, 13 Nov 2025 19:56:46 +0000 https://frontendmasters.com/blog/?p=7741 I saw Tony Conway & Jeremy Wagner’s post on web.dev, Use Baseline with Browserslist, and I had a little play with it myself (saved live stream). Allow me to write down what I know and what I learned.

So here’s Browserslist1.

Browserslist is the developer community at it’s best. There are a bunch of tools that make choices about what they do based on what browsers they are trying to support. Like the intro on their homepage says:

Shared browser compatibility config for popular JavaScript tools like Autoprefixer / PostCSS, Babel, ESLint, and Webpack

Links to those tools added by me, linking to their Browserslist details. LightningCSS is another one.

So instead of all these tools coming up with their own syntax for how to express a set of supported browsers, they all use this shared syntax. And not even just a shared syntax, they can even share the same location or file (i.e. .browserslist) to store that information.

That’s great. It’s in the same vein as the famed package.json file being the canonical source of packages and JavaScript processing information, or .editorconfig for all editor behavior and prettification needs.

Browserslist recommends "defaults" if “you’re building a web application for the global audience.” This translates to their config "> 0.5%, last 2 versions, Firefox ESR, not dead". That translates to any browser which has more than half a percent of browser share (it gets browser data like this), the last two major versions of all major browsers, Firefox “Extended Support Release” specifically, and only “not dead” browsers (as in, not Internet Explorer).

That’s pretty reasonable? But you can always add things to your support list if you need to go deeper or be more specific.

But now Google is in the game of qualifiying web platform features with Baseline.

Web Platform Baseline brings clarity to information about browser support for web platform features.

Their biggest rubber stamps are “widely available” and “newly available”.

  • Baseline Widely available includes all web features that were fully supported by the Baseline core browser set 30 or more months in the past.
  • Baseline year feature sets, for example Baseline 2020, include all features that were Newly available at the end of the specified year.

This means you can literally use Browserslist config strings to use these delegations.

"baseline newly available"
"baseline widely available"
"baseline 2022"

Specifying a year, like the last example above, might be how your project wants to roll! I don’t hate it, honestly. If I were to use "baseline 2020" as config and run CSS through Lightning CSS, I’d see some transformations like this:

oklch() not supported at all, so transformed into three different formats, supporting as much as it can. light-dark() not supported at all, so provides custom properties for your own implementation, and mask is vendor prefixed.

If we went back to "baseline 2018" we’d even see some big transformations of padding-inline-start transformed into padding-left for a big ol’ pile of :lang() values and padding-right for another big pile (!!).

If we went forward to "baseline 2022" we’d see the color() declaration go away, leaving only the #hex and lab() values left, but the rest would be the same.

I think the general “recommendation” (if that’s fair) is to use "baseline widely available" where our CSS transformations are like this:

The oklch() color is left alone, but we still see the light-dark() transformation and the vendor prefixing for mask.

Using "baseline newly available" is still somewhat cross-browser compatible, just not to the level of widely available. To be “widely” available the feature needs to be baseline for 30 months! So I’d say pretty-darn available. Using "baseline newly available" we get:

Just some trivial conversion of colors, which is mostly a minification thing.

Example Repo

I tossed together a repo for testing this stuff. You can basically change the config here. It’s an npm run dev thing and it’s wired up to not fingerprint or minify, so it’s easy to see both src and dist files like the screenshots I was doing above.

It’s also wired up to do JavaScript processing with Babel, although I’m not 100% sure I even did that part right, but if you wanted to test JavaScript transformation on this stuff, it might be a good starting point.

Stuff That Isn’t Touched

Remember that CSS features aren’t always transformable to backwards-compatible things. Like if you use @layer or rlh units or something, that’s just going to get left alone despite any browser support levels. Same with stuff like shape(), which is brand new, and would be awesome if they ported it to something, but alas, they do not.

Final Thoughts

I think it comes down to a battle: "defaults" vs. "baseline widely available". Anything else is just playing around or very specialty situations. Between the two, I think I’d actually go with "baseline widely available". It just seems a smidge more modern and I like the momentum behind it.

The best possible move is to use your own analytics data to inform the choices. This can be done with a Baseline Target Report and Jeremy Wagner and Rachel Andrew get into it in How to choose your Baseline target.


  1. The pluralization of Browserslist is weird. Feels like it should be Browserlist. You’ll also see config strings like “last 2 version” which then lacks the pluralization it wants (but it also works pluralized). 🤷‍♀️ ↩︎

]]>
https://frontendmasters.com/blog/browserslist-baseline/feed/ 0 7741
Fine, I’ll Use a Super Basic CSS Processing Setup. https://frontendmasters.com/blog/fine-ill-use-a-super-basic-css-processing-setup/ https://frontendmasters.com/blog/fine-ill-use-a-super-basic-css-processing-setup/#comments Wed, 06 Dec 2023 16:30:46 +0000 http://fem.flywheelsites.com/?p=126 If you’d like, you can jump straight to the setup, but I’d like to lay down my thinking first. Here goes.


These days I try to use just CSS. You don’t need a CSS processor these days like you used to, thanks to native CSS getting so much better over the years.

I used to reach for Sass like seconds on Thanksgiving. Of course I’m going for it. There was so much value there! Nesting, loops, mixins, includes, variables, math, just a cornucopia of value. Most of that stuff is in CSS now, and better.

Except includes. Dammit.

Good ones, anyway. Native CSS has @import which is technically an include. I suppose it predates ESM imports by gotta be somewhere near 2 decades. But all it does is trigger another HTTP Request for another stylesheet. It doesn’t concatenate them together which is generally what you want with CSS. Bundle, as it were.

So Sass made @import do bundling. That was a weird call, actually, since it trampled over a native CSS feature in a way that Sass was usually very good about not doing.

And I’d usually lump on PostCSS too, mostly because Autoprefixer had a real hay day of usefulness. These days, there is only precious few things left that even need it anymore (looking at you, mask).

So, between Sass and PostCSS (and probably a specialized minifier), that was getting to be a rather complex CSS processing pipeline. Plus, you’d always have to figure out how to wire it up with whatever other site-building dance you were doing. Maybe you had your own favorites. I know there are Less and Stylus people out there, not to mention a soup of potential PostCSS plugins, specialty tools like CSS modules, and the rest of the iceberg of CSS-in-JS machines.

That fatigued me, and so again: these days I try to use just CSS.

But there are still growing pains as a single CSS file gets bigger. The length of the file can get annoying. The odd CSS property or value that still needs prefixing. The desire to use some new CSS feature that would be easier with a sprinkle of processing.

SO FINE:

Let’s wire up a modern CSS processing setup.

We’re going to use Lightning CSS. It has one job: processing CSS. But offers a compelling set of features:

  1. Bundling
  2. Syntax Lowering
  3. Vendor prefixing
  4. Minification

I’ll get to the details of those as we go.

First, let’s get it installed. Here’s both the core and the CLI because that’s where we want to use it:

npm install lightningcss lightningcss-cli --save-dev

Lightning CSS is from the Parcel project, but here we’re not using it via Parcel, we’re using it directly. It’s now ready to be called at the command line.

Say we have a style.css file and we’re going to process it into a style-min.css file:

lightningcss --bundle --targets '>= 0.25%' --minifystyle.css -o style-min.css

Sweet, done.

Notice we’re specifically asking for the features we want via flags.

We’re going to want a file watcher.

We could make this script easier to run by putting it in our package.json like:

  "scripts": {
    "process": "lightningcss --bundle --targets '>= 0.25%' --minifystyle.css -o style-min.css"
  },

But that’s no fun.

Now every time we write some CSS we have to come back to the command line, run npm run process and then head over to the browser and refresh it to see the changes? We’ve made the choice opt-in to some extra technology here, so let’s get the DX right.

Let’s use Turbowatch to:

  1. Watch our CSS file(s)
  2. Run the command to process the CSS when the files change

I’m no Turbowatch expert but I found it straightforward to set up.

npm install turbowatch --save-dev

Let’s make our npm script run Turbowatch instead:

  "scripts": {
    "dev": "turbowatch ./turbowatch.ts"
  },

So we’ll need that turbowatch.ts file. Fortunately, it’s straightforward:

import { defineConfig } from "turbowatch";

export default defineConfig({
  project: __dirname,
  triggers: [
    {
      expression: ["match", "*.css", "basename"],
      name: "build",
      onChange: async ({ spawn }) => {
        await spawn`lightningcss --minify --bundle style.css -o style-min.css`;
      },
    },
  ],
});

Match files that end in *.css, when they change, run the command.

Cool.

Let’s add a live reloader.

Fair warning, this is extremely old school and definitely not the only option, but I like how easy it is: Live.js.

It’s a script you link up and it monitors the resources on the page and refreshes when they change. So now…

  1. We update our CSS file
  2. Turbowatcher sees the changes
  3. Lightning CSS runs and makes a new CSS file
  4. Live.js sees the change and refreshes the page

That’s the complete DX we’re after here.

Case closed. Lightweight CSS build process complete.

About those Lighting CSS features

Bundling

Like Sass, it’s slightly weird that the concatenation/bundling feature uses @import, which is the native CSS syntax. I sort of don’t blame them as otherwise Lightning CSS doesn’t invent any CSS syntax, which is a pretty special line to cross, and would probably mean they’d have to stop using .css and use .lightning instead or something.

I say that, but the conditional syntax for imports actually is an invented syntax. Strange. Probably would have went with @bundle or something then, so that the native @import behavior would be preserved. As it is, if you try to use the bundling feature and, say try to use Open Props via @import "https://unpkg.com/open-props"; or the like, it will error.

I still think bundling is a crucial feature here and probably wouldn’t use Lightning CSS at all if it didn’t have it.

I can do:

/project/
  /css/
    footer.css
    header.css
    layout.css
    reset.css
  style.css

And style.css can bundle those all together during processing as long as they are referenced:

@import "footer.css";
@import "header.css";
@import "layout.css";
@import "reset.css";

The order is preserved.

Syntax Lowering

Lightning CSS makes the choices on what CSS to process down to code that older browsers understand based on what browsers you actually want to support, ala Browserslist.

Say I want to take advantage of the OKLCH color model (and I do!). I could write…

html {
  --color-dark-purple: oklch(15.96% 0.079 316);
  --color-bright-purple: oklch(45.08% 0.24 308);
  --color-light-purple: #dabbe0;
}

And, as I write, with Browserslist set to defaults, the output will be:

html {
  --color-dark-purple: #190023;
  --color-bright-purple: #7900b5;
  --color-light-purple: #dabbe0;
}

@supports (color: lab(0% 0 0)) {
  html {
    --color-dark-purple: lab(2.88618% 14.7006 -16.436);
    --color-bright-purple: lab(31.5834% 65.0634 -67.2974);
  }
}

Notice the HEX color was left alone, and the OKLCH values were set into lab() instead. I’m not even 100% sure why, but it must be that I’ll get the best cross-browser support of that. That’s nice to not have to think intensely about.

It’s not just colors, it’s lots of features of “modern CSS”. Take, for instance, CSS nesting. I can now write:

div {
  div {
    border-block-start: 1px solid red;
  }
}

We’re close, but not all browsers are supporting native nesting yet. And some browsers right now will choke on that nested selector not “starting with a symbol”. So Lightning CSS will process that to:

div div {
  border-block-start: 1px solid red;
}

That’s supported everywhere, so fine with me! But notice it left the logical property alone. The browsers you get from the default setting all support that, so it’s left alone.

Vendor Prefixing

There isn’t terribly much to be said here, but it does what it says on the box.

.mask {
  mask: url(mask.png);
}

Turns into:

.mask {
  -webkit-mask: url("mask.png");
  mask: url("mask.png");
}

Minification

This is a great feature of Lightning CSS, because it saves us from needing a separate tool for this. Plus, they say it’s the best tool on the market for squeezing the size of that CSS down, so bonus. Sass always had a “compressed” option for output, but nobody trusted it to truly minify CSS. Lightning CSS will do the job nicely.

CSS Modules

I figured I’d point out that Lightning CSS can do CSS Modules. I’m a fan of CSS Modules, the way it does scoped styles is very useful, but generally only in the context of React where I’m in a position to import the styles and apply the proper className. For a very simple CSS processing like this, which doesn’t involve the HTML or templates in any way, we don’t need CSS modules.

Example Project

Wanna see all this functional on a project so you can copy the bits you need like we all do every day? Heck yes, you do:

]]>
https://frontendmasters.com/blog/fine-ill-use-a-super-basic-css-processing-setup/feed/ 1 126