Developer News

The Cost of Javascript Frameworks

Css Tricks - Sun, 04/26/2020 - 3:30am

I expect this post from Tim Kadlec to be quoted in every performance conference talk for the next few years. There is a lot of data here, so please check it out for yourself, but the short story is that JavaScript-framework-powered sites are definitely heavier and more resource-intensive than non-JavaScript-framework-powered sites. Angular is the beefiest and React is hardest on the CPU. But as Tim says:

… it says very little about the performance of the core frameworks in play and much more about the approach to development these frameworks may encourage

Another big caveat is that there isn’t data here on-site usage after first-load, which is a huge aspect of “single-page app” approaches.

Still, while you can be performant with frameworks (although even that top 10% isn’t super encouraging), the frameworks aren’t doing much to help what has turned into a bad situation. It mimics exactly what we talked about recently with accessibility. It’s not the frameworks “fault” exactly, but they are also the best positioned to stop the bleeding.

Direct Link to ArticlePermalink

The post The Cost of Javascript Frameworks appeared first on CSS-Tricks.


Css Tricks - Sat, 04/25/2020 - 3:29am

The @property is totally new to me, but I see it’s headed to Chrome, so I suppose it’s good to know about!

There is a draft spec and an “intent to ship” document. The code from that document shows:

@property --my-property { syntax: "<color>"; initial-value: green; inherits: false; }

That is the CSS exact-equivalent to a   CSS.registerProperty(), the JavaScript syntax for declaring CSS custom properties, also a new thing (under the Houdini umbrella, it seems).

It looks like you declare these not within a selector block, but outside (like a @media query), and once you have, you haven’t actually created a new custom property yet, you’ve just registered the fact that you probably will later. When you actually go to create/use the custom property, you create it within a selector block like you already do now.

The “commonly cited use-case” is pretty darn cool. Right now, this isn’t possible in CSS:

.el { background: linear-gradient(white, black); /* this transition won't work */ transition: 1s; } .el:hover { background: linear-gradient(red, black); }

You might think the white in that gradient will fade to red with that transition, but no, that’s not transition-able in that way. If we needed this in the past, we’d resort to trickery like fading in a pseudo-element with the new gradient colors or transitioning the background-position of a wider-than-the-element gradient to fake it.

Sounds like now we can…

@property --gradient-start { syntax: "<color>"; initial-value: white; inherits: false; } .el { --gradient-start: white; background: linear-gradient(var(--gradient-start), black); transition: --gradient-start 1s; } .el:hover { --gradient-start: red; }

Presumably, that works now because we’ve told CSS that this custom property is a <color> so it can be treated/animated like a color in away that wasn’t possible before.

Reminds me of how when we use the attr() function to pull like data-size="22px" off an element, we can’t actually use the <length> 22px, it’s just a string. But that maybe-someday we’ll get attr(data-size px);

I have no idea when @property will actually be available, but looks like Chrome will ship first and there are positive signals from Safari and Firefox. &#x1f44d;

The post @property appeared first on CSS-Tricks.

How to Make a CSS-Only Carousel

Css Tricks - Fri, 04/24/2020 - 9:29am

We mentioned a way to make a CSS-only carousel in a recent issue of the newsletter and I thought that a more detailed write up would be interesting and capture some of my thoughts on making one.

So, here’s what we’re making today:

There’s no JavaScript here, whatsoever! No jQuery plugins. No trickiness. Just a couple of new-ish CSS properties that I’ve been experimenting with as well as some basic HTML.

OK to start, we need to focus on the markup. The design includes a left navigation made up of images and a large image gallery on the right that lets us scroll through each image individually. We’ll also need a wrapper to help us organize the layout:

<div class="wrapper"> <nav class="lil-nav"></nav> <div class="gallery"></div> </div>

Next, we can add images! For this little example, I checked out our list of sites with high quality images that you can use for free and went with Unsplash.

After saving images with the CodePen asset manager, I started adding the URLs to the nav element:

<nav class="lil-nav"> <a href="#image-1"> <img class="lil-nav__img" src="..." alt="Yosemite" /> </a> <a href="#image-2"> <img class="lil-nav__img" src="..." alt="Basketball hoop" /> </a> <!-- more images go here --> </nav>

See that the href to each of these links is pointing to an ID? That’s because if we look at the demo again, we want to be able to click an image and then we want to it to hop to the larger version of that image in the gallery to the right.

So, now we can start to add these images to the large gallery, too…

<div class="gallery"> <img class="gallery__img" id="image-1" src="..." alt="Yosemite" /> <img class="gallery__img" id="image-2" src="..." alt="Basketball hoop" /> <!-- more images go here --> </div>

Nifty. Next is the fun part: styling this bad boy. We can use a grid layout the parent .wrapper and set some smart defaults for the img element:

img { display: block; max-width: 100%; } .wrapper { display: grid; grid-template-columns: 1fr 5fr; grid-gap: 20px; } CodePen Embed Fallback

So far, we have our layout sorted and our links set up. Next, let’s account for overflow that might spill outside our wrapper and make sure that the nav and the gallery are scrollable:

.wrapper { display: grid; grid-template-columns: 1fr 5fr; grid-gap: 10px; overflow: hidden; height: 100vh; } .gallery { overflow: scroll; } .lil-nav { overflow-y: scroll; overflow-x: hidden; } CodePen Embed Fallback

We can scroll through each image in the gallery now, but if this was a production website we’d probably want to make sure that folks can scroll passed this carousel a bit more easily. Trent Walton wrote about this very problem several years ago and I think it’s always worth keeping in mind.

Next up, let’s focus on the carousel snap of each image in the gallery. To do that we’ll need to use the scroll-snap-type and scroll-snap-align property like this:

.gallery { overflow: scroll; scroll-snap-type: x mandatory; } .gallery__img { scroll-snap-align: start; margin-bottom: 10px; }

Now try scrolling through the gallery on the right-hand side again:

CodePen Embed Fallback

If you want to learn more about these properties I’d highly recommend this piece about practical CSS scroll snapping which digs into the nitty-gritty of these properties.

We have a pretty dang usable carousel! From here, all we have to do is tidy up the design because the gallery image isn’t the full height of the screen. To do that we can use object-fit and give each image a min-height with the vh unit, just like this:

.gallery__img { scroll-snap-align: start; margin-bottom: 10px; min-height: 100vh; object-fit: cover; }

Now the big gallery images will always be the full size of the screen and will scale to take up the width and height. Let’s move on and tackle the style of the little navigation images:

.lil-nav { overflow-y: scroll; overflow-x: hidden; } .lil-nav a { height: 200px; display: flex; margin-bottom: 10px; } .lil-nav__img { object-fit: cover; } CodePen Embed Fallback

At first, I made this little nav act like a carousel too, but it felt really weird. I’m just keeping the default scroll behavior for now. In that demo above, though, try clicking an image. Notice how it jumps to that image in the carousel immediately? It would be nice if we could animate that transition a bit — and we can!

.gallery { overflow: scroll; scroll-snap-type: x mandatory; scroll-behavior: smooth; }

That scroll-behavior CSS property is super handy for this and so now the whole thing will animate if you click one of the nav items:

CodePen Embed Fallback

Nifty, eh? One more tiny thing we could do here is throw a filter on the nav items to make them black and white and then animate them on hover:

.lil-nav__img { object-fit: cover; filter: saturate(0); transition: 0.3s ease all; } .lil-nav__img:hover { transform: scale(1.05); filter: saturate(1); }

I’m sure there’s a lot more we could do here but I think this works quite nicely!

CodePen Embed Fallback

We could even throw a tiny bit of JavaScript into the mix to show which image is active, but I reckon that folks know that just from looking at the gallery.

That’s it! We now have a carousel that’s pretty dang good for progressive enhancement and it means we don’t have to load a library of JavaScript or write a bunch more code than we really need to.

Making the carousel responsive…

Let’s go one step further though and make this chap responsive. What we want to do is reverse the order of our grid by moving all of our current styles into a media query that is only activated at larger screens.

You might want to open up this demo in a new tab and decrease/increase the size of the browser to see the changes take place:

CodePen Embed Fallback

If you load this demo on a mobile device you should see how the layout switches between the two modes. This is done by using a single media query on the .wrapper element. Note that we’re using Sass:

$large: 1200px; .wrapper { overflow: hidden; height: 100vh; display: grid; grid-template-rows: 2fr 1fr; grid-gap: 10px; @media screen and (min-width: $large) { grid-template-columns: 1fr 5fr; grid-template-rows: auto; } }

Let’s add one on the navigation, too. But this time, we need to tell the navigation to start on the second row so it moves to the bottom of the screen:

.lil-nav { overflow-x: scroll; overflow-y: hidden; display: flex; grid-row-start: 2; @media screen and (min-width: $large) { overflow-y: scroll; overflow-x: hidden; display: block; grid-row-start: auto; } }

With the gallery we need to switch around the scroll-type for larger screens and reverse the overflow property as well:

.gallery { overflow-x: scroll; overflow-y: hidden; scroll-snap-type: x mandatory; scroll-behavior: smooth; display: flex; @media screen and (min-width: $large) { display: block; overflow-y: scroll; overflow-x: hidden; scroll-snap-type: y mandatory; } }

That’s the bulk of the changes we’ve had to make and I quite like it! If we wanted to make this production-ready, we would think about accessibility (e.g. we probably don’t want screen readers to read out all the images in both the nav and gallery). Then there’s performance — we might consider lazy-loading so the images are only rendered when they’re needed.

Either way, this is a good start !

The post How to Make a CSS-Only Carousel appeared first on CSS-Tricks.

“The title ‘Front-End Developer’ is obsolete.”

Css Tricks - Fri, 04/24/2020 - 8:35am

That title is from the opening tweet of a thread from Benjamin De Cock. I wouldn’t go that far, myself. What I like about the term is that ‘Front-End’ literally means the browser, and while the job has been changing quite a lot — and is perhaps fracturing before our eyes — the fact that the job is about doing browser work is still true. We’re browser people. This was a point I tried to make in my “Ooooops I guess we’re full-stack developers now” talk.

I really like Benjamin’s sentiment though. There is a scourge of implementations of things on the web that are both heavier and worse because they re-implement something that the browser offers better and “for free.” Think sliders: scrolling behavior, snap points, fixed/sticky positioning, form controls, animation, etc.

Our industry seems to have acknowledged that backend and frontend developers require very different skills (even though they often use the exact same language), and yet it’s struggling to see there’s too much bundled into the term “front-end developer”.

That’s the tricky part. That’s at the heart of The Great Divide. There’s an awful lot of front-end developers where their job solely focuses on JavaScript. You could call them “JavaScript Engineers” or “JavaScript Developers,” and that feels OK. However, I’m not sure what you call someone who’s a great front-end developer, not particularly focused on JavaScript, but is on other aspects of the front end.

The modern frontend developer is most often than not a “Jack of all trades” mastering JS (or even just a framework) and barely tolerating HTML/CSS as a necessary evil. That’s understandable. I strongly think it’s a different specialization, and it’s too much for a single person.

Yep, it’s OK! The divide isn’t a bad thing; it’s just a thing. Front-end teams need JavaScript specialists and CSS specialists and accessibility specialists and performance specialists and animation specialists and internationalization specialists and, and, and, and. They don’t have to all be separate people. People can be good at multiple things. It’s just exceptionally rare that people are good at everything, even when scoped only to front-end skills.

The post “The title ‘Front-End Developer’ is obsolete.” appeared first on CSS-Tricks.

Chrome + System Fonts Snafu

Css Tricks - Fri, 04/24/2020 - 5:49am

There was just a bug late last year where system fonts (at least on Mac, I don’t know what the story was on other platforms) in Chrome appeared too thin and tracked-in at small sizes and too thick and tracked-out at larger sizes. That one was fixed, thankfully. But while it was a problem, it was the reason I gave up on system fonts for now and switched something else. A performance loss but aesthetic gain.

Now there is a new much worse bug, where the system font can’t be bolded. It’s not great, as a ton of sites roll with the system font stack as it has two major benefits: 1) it can help your site look like the operating system 2) it has great performance as the site doesn’t need to download/display and custom fonts.

Jon Henshaw wrote it up:

… the bug caught the attention of Adam Argyle, maker of VisBug and Chrome CSS Developer Advocate at Google. Argyle created a Chromium bug report, but the Chromium development team ultimately decided it wasn’t a blocker for releasing version 81. That resulted in sites like Coywolf not being able to use bold text for fonts that are larger than 16px (e.g., every heading).

The bug won’t be fixed in version 82 because the Chromium team announced that they’re skipping it, and will be releasing version 83 in mid-May instead. Argyle assured everyone on the original GitHub bug report that it would be fixed in version 83.

Above is Jon’s site. Andy Bell’s site got hit by it too.

So we’re looking at 4 weeks or so. Šime Vidas proposed a temporary fix of going Helvetica for now:

body { font-family: -apple-system, Helvetica; }

I guess with -apple-system in there, older versions of Chrome/macOS still might be able to benefit from system fonts? Not sure.

That brings up a source of confusion for me. When I first heard of using system font stacks, there was -apple-system and BlinkMacSystemFont and you were supposed to use them in that order in the font stack. Then came along -system-ui, and that seemed to work well all by itself and that was nice as it was obviously less Mac-specific. But there is also system-ui (no starting dash), and that seems to do the same thing and I’m not sure which is correct. Now it looks like the plan is ui-sans-serif and friends (like ui-serif and ui-monospace). I like the idea, but I’d love to hear clarity from browser vendors on what the recommended use is. Are we in a spot like this?

/* Just a guess... */ body { font-family: ui-sans-serif, system-ui, -system-ui, -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji"; }

Another observation from me… as I was trying to replicate this on Chrome 81, at first I was like “weird, works for me”, because I was trying out the bolding on default 16px text. I noticed that it was when the font was 20px or bigger the problem kicked in:

Bramus has an alternative fix idea: use Inter.

The post Chrome + System Fonts Snafu appeared first on CSS-Tricks.

SVG, Favicons, and All the Fun Things We Can Do With Them

Css Tricks - Fri, 04/24/2020 - 4:58am

Favicons are the little icons you see in your browser tab. They help you understand which site is which when you’re scanning through your browser’s bookmarks and open tabs. They’re a neat part of internet history that are capable of performing some cool tricks.

One very new trick is the ability to use SVG as a favicon. It’s something that most modern browsers support, with more support on the way.

Here’s the code for how to add favicons to your site:

<link rel="icon" type="image/svg+xml" href="/favicon.svg"> <link rel="alternate icon" href="/favicon.ico"> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#ff8a01">

If a browser doesn’t support a SVG favicon, it will ignore the first link element declaration and continue on to the second. This ensures that all browsers that support favicons can enjoy the experience. 

You may also notice the alternate attribute value for our rel declaration in the second line. This programmatically communicates to the browser that the favicon with a file format that uses .ico is specifically used as an alternate presentation.

Following the favicons is a line of code that loads another SVG image, one called safari-pinned-tab.svg. This is to support Safari’s pinned tab functionality, which existed before other browsers had SVG favicon support. There’s additional files you can add here to enhance your site for different apps and services, but more on that in a bit.

Here’s more detail on the current level of SVG favicon support:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

DesktopChromeFirefoxIEEdgeSafari8041No80TPMobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari81NoNo13.4 Why SVG?

You may be questioning why this is needed. The .ico file format has been around forever and can support images up to 256×256 pixels in size. Here are three answers for you.

Ease of authoring

It’s a pain to make .ico files. The file is a proprietary format used by Microsoft, meaning you’ll need specialized tools to make them. SVG is an open standard, meaning you can use them without any further tooling or platform lock-in.


Retina? 5k? 6k? When we use a resolution-agnostic SVG file for a favicon, we guarantee that our favicons look crisp on future devices, regardless of how large their displays get


SVGs are usually very small files, especially when compared to their raster image counterparts — even more-so if you optimize them beforehand. By only using a 16×16 pixel favicon as a fallback for browsers that don’t support SVG, we provide a combination that enjoys a high degree of support with a smaller file size to boot. 

This might seem a bit extreme, but when it comes to web performance, every byte counts!


Another cool thing about SVG is we can embed CSS directly in it. This means we can do fun things like dynamically adjust them with JavaScript, provided the SVG is declared inline and not embedded using an img element.

<svg  version="1.1" xmlns="" viewBox="0 0 100 100">   <style>     path { fill: #272019; }   </style>   <!-- etc. --> </svg>

Since SVG favicons are embedded using the link element, they can’t really be modified using JavaScript. We can, however, use things like emoji and media queries.


Lea Verou had a genius idea about using emoji inside of SVG’s text element to make a quick favicon with a transparent background that holds up at small sizes.

In response, Chris Coyier whipped up a neat little demo that lets you play around with the concept.

Dark Mode support

Both Thomas Steiner and Mathias Bynens independently stumbled across the idea that you can use the prefers-color-scheme media query to provide support for dark mode. This work is built off of Jake Archibald’s exploration of SVG and media queries.

<svg width="128" height="128" xmlns="">   <style>     path { fill: #000000; }     @media (prefers-color-scheme: dark) {       path { fill: #ffffff; }     }   </style>   <path d="M111.904 52.937a1.95 1.95 0 00-1.555-1.314l-30.835-4.502-13.786-28.136c-.653-1.313-2.803-1.313-3.456 0L48.486 47.121l-30.835 4.502a1.95 1.95 0 00-1.555 1.314 1.952 1.952 0 00.48 1.99l22.33 21.894-5.28 30.918c-.115.715.173 1.45.768 1.894a1.904 1.904 0 002.016.135L64 95.178l27.59 14.59c.269.155.576.232.883.232a1.98 1.98 0 001.133-.367 1.974 1.974 0 00.768-1.894l-5.28-30.918 22.33-21.893c.518-.522.71-1.276.48-1.99z" fill-rule="nonzero"/> </svg>

For supporting browsers, this code means our star-shaped SVG favicon will change its fill color from black to white when dark mode is activated. Pretty neat!

Other media queries

Dark mode support got me thinking: if SVGs can support prefers-color-scheme, what other things can we do with them? While the support for Level 5 Media Queries may not be there yet, here’s some ideas to consider:

A mockup of how these media query-based adjustments could work. Keep it crisp

Another important aspect of good favicon design is making sure they look good in the small browser tab area. The secret to this is making the paths of the vector image line up to the pixel grid, the guide a computer uses to turn SVG math into the bitmap we see on a screen. 

Here’s a simplified example using a square shape:

When the vector points of the square align to the pixel grid of the artboard, the antialiasing effect a computer uses to smooth out the shapes isn’t needed. When the vector points aren’t aligned, we get a “smearing” effect:

A vector point’s position can be adjusted on the pixel grid by using a vector editing program such as Figma, Sketch, Inkscape, or Illustrator. These programs export SVGs as well. To adjust a vector point’s location, select each node with a precision selection tool and drag it into position.

Some more complicated icons may need to be simplified, in order to look good at such a small size. If you’re looking for a good primer on this, Jeremy Frank wrote a really good two-part article over at Vidget.

Go the extra mile

In addition to favicons, there are a bunch of different (and unfortunately proprietary) ways to use icons to enhance its experience. These include things like the aforementioned pinned tab icon for Safari¹, chat app unfurls, a pinned Windows start menu tile, social media previews, and homescreen launchers.

If you’re looking for a great place to get started with these kinds of enhancements, I really like

It’s a lot, but it guarantees robust support.

A funny thing about the history of the favicon: Internet Explorer was the first browser to support them and they were snuck in at the 11th hour by a developer named Bharat Shyam:

As the story goes, late one night, Shyam was working on his new favicon feature. He called over junior project manager Ray Sun to take a look.

Shyam commented, “This is good, right? Check it in?”, requesting permission to check the code into the Internet Explorer codebase so it could be released in the next version. Sun didn’t think too much of it, the feature was cool and would clearly give IE an edge. So he told Shyam to go ahead and add it. And just like that, the favicon made its way into Internet Explorer 5, which would go on to become one of the largest browser releases the web has ever seen.

The next day, Sun was reprimanded by his manager for letting the feature get by so quickly. As it turns out, Shyam had specifically waited until later in the day, knowing that a less experienced Program Manager would give him a pass. But by then, the code had been merged in. Incidentally, you’d be surprised just how many relatively major browser features have snuck their way into releases like this.

From How We Got the Favicon by Jay Hoffmann

I’m happy to see the platform throw a little love at favicons. They’ve long been one of my favorite little design details, and I’m excited that they’re becoming more reactive to user’s needs. If you have a moment, why not sneak a SVG favicon into your project the same way Bharat Shyam did way back in 1999. 

¹ I haven’t been able to determine if Safari is going to implement SVG favicon support, but I hope they do. Has anyone heard anything?

The post SVG, Favicons, and All the Fun Things We Can Do With Them appeared first on CSS-Tricks.

Different Approaches to Responsive CSS Motion Path

Css Tricks - Fri, 04/24/2020 - 4:58am

As a follow-up to Jhey’s recent post on responsive motion paths, Michelle Barker notes that another approach could be to just transform: scale() the whole dang element.

The trade-off there is that you’re scaling both the path and the element on the path at the same time; Jhey’s approach only makes path flexbile and the element stays the same size.

Calculating scale is a really cool trick I think and one we’ve also covered before.

Direct Link to ArticlePermalink

The post Different Approaches to Responsive CSS Motion Path appeared first on CSS-Tricks.

Dark mode and variable fonts

Css Tricks - Thu, 04/23/2020 - 11:43am

Not so long ago, we wrote about dark mode in CSS and I’ve been thinking about how white text on a black background is pretty much always harder to read than black text on a white background. After thinking about this for a while, I realized that we can fix that problem by making the text thinner in dark mode using variable fonts!

Here’s an example of the problem where I’m using the typeface Yanone Kaffeesatz from Google Fonts. Notice that the section with white text on a black background looks heavier than the section with black text on a white background.

CodePen Embed Fallback

Oddly enough, these two bits of text are actually using the same font-weight value of 400. But to my eye, the white text looks extra bold on a black background.

Stare at this example for a while. This is just how white text looks on a darker background; it’s how our eyes perceive shapes and color. And this might not be a big issue in some cases but reading light text on a dark background is always way more difficult for readers. And if we don’t take care designing text in a dark mode context, then it can feel as if the text is vibrating as we read it.

How do we fix this?

Well, this is where variable fonts come in! We can use a lighter font weight to make the text easier to read whenever dark mode is active:

body { font-weight: 400; } @media (prefers-color-scheme: dark) { body { font-weight: 350; } }

Here’s how that looks with this new example:

CodePen Embed Fallback

This is better! The two variants now look a lot more balanced to me.

Again, it’s only a small difference, but all great designs consist of micro adjustments like this. And I reckon that, if you’re already using variable fonts and loading all these weights, then you should definitely adjust the text so it’s easier to read.

This effect is easier to spot if we compare the differences between longer paragraphs of text. Here we go, this time in Literata:

CodePen Embed Fallback

Notice that the text on the right feels bolder, but it just isn’t. It’s simply an optical allusion — both examples above have a font-weight of 500.

So to fix this issue we can do the same as the example above:

body { font-weight: 500; } @media (prefers-color-scheme: dark) { body { font-weight: 400; } } CodePen Embed Fallback

Again, it’s a slight change but it’s important because at these sizes every typographic improvement we make helps the reading experience.

Oh and here’s a quick Google fonts tip!

Google Fonts lets you add a font to your website by adding a <link> in the <head> of the document, like this:

<head> <link href="" rel="stylesheet"> </head>

That’s using the Rosario typeface and adding a font-weight of 515 — that’s the bit in the code above that says wght@515. Even if this happens to be a variable font, only this font weight will be downloaded. If we try to do something like this though…

body { font-weight: 400; }

…nothing will happen! In fact, the font won’t load at all. Instead, we need to declare which range of font-weight values we want by doing the following:

<link href="" rel="stylesheet">

This @300..500 bit in the code above is what tells Google Fonts to download a font file with all the weights between 300 and 500. Alternatively, adding a ; between each weight will then only download weights 300 and 500 – so, for example, you can’t pick weight 301:

<link href=";500&display=swap" rel="stylesheet">

It took me a few minutes to figure out what was going wrong and why the font wasn’t loading at all, so hopefully the Google Fonts team can make that a bit clearer with the embed codes in the future. Perhaps there should be an option or a toggle somewhere to select a range or specific weights (or maybe I just didn’t see it).

Either way, I think all this is why variable fonts can be so gosh darn helpful; they allow us to adjust text in ways that we’ve never been able to do before. So, yay for variable fonts!

The post Dark mode and variable fonts appeared first on CSS-Tricks.

Innovating on Web Monetization: Coil and Firefox Reality

Css Tricks - Thu, 04/23/2020 - 4:56am

I still think Coil is cool. I have it installed on CSS-Tricks as a publisher and money trickles in. I have a paid account and I trickle out money to other sites that use it. I wrote about all that last year.

This’ll explode to something huge if we actually get the Web Monetization API stuff. No more browser extensions would be needed, and a real ecosystem could be built around it.

Anselm Hook writes about using Coil (for now) to monetize games on the web, which is a good reminder that this isn’t just for publications — it’s for anything-web. Coil even works for things off your own domain, like your YouTube channel.

Direct Link to ArticlePermalink

The post Innovating on Web Monetization: Coil and Firefox Reality appeared first on CSS-Tricks.

Rethinking Twitter as a Serverless App

Css Tricks - Thu, 04/23/2020 - 4:56am

In a previous article, we showed how to build a GraphQL API with FaunaDB. We’ve also written a series of articles [1, 2, 3, 4] explaining how traditional databases built for global scalability have to adopt eventual (vs. strong) consistency, and/or make compromises on relations and indexing possibilities. FaunaDB is different since it does not make these compromises. It’s built to scale so it can safely serve your future startup no matter how big it gets, without sacrificing relations and consistent data.

In this article, we’re very excited to start bringing all of this together in a real-world app with highly dynamic data in a serverless fashion using React hooks, FaunaDB, and Cloudinary. We will use the Fauna Query Language (FQL) instead of GraphQL and start with a frontend-only approach that directly accesses the serverless database FaunaDB for data storage, authentication, and authorization.

The golden standard for example applications that feature a specific technology is a todo app–mainly because they are simple. Any database out there can serve a very simple application and shine. 

And that is exactly why this app will be different! If we truly want to show how FaunaDB excels for real world applications, then we need to build something more advanced. 

Introducing Fwitter

When we started at Twitter, databases were bad. When we left, they were still bad

Evan Weaver

Since FaunaDB was developed by ex-Twitter engineers who experienced these limitations first-hand, a Twitter-like application felt like an appropriately sentimental choice. And, since we are building it with FaunaDB, let’s call this serverless baby ‘Fwitter’

Below is a short video that shows how it looks, and the full source code is available on GitHub.

When you clone the repo and start digging around, you might notice a plethora of well-commented example queries not covered in this article. That’s because we’ll be using Fwitter as our go-to example application in future articles, and building additional features into it with time. 

But, for now, here’s a basic rundown of what we’ll cover here:

We build these features without having to configure operations or set up servers for your database. Since both Cloudinary and FaunaDB are scalable and distributed out-of-the-box, we will never have to worry about setting up servers in multiple regions to achieve low latencies for users in other countries. 

Let’s dive in!

Modeling the data 

Before we can show how FaunaDB excels at relations, we need to cover the types of relations in our application’s data model. FaunaDB’s data entities are stored in documents, which are then stored in collections–like rows in tables. For example, each user’s details will be represented by a User document stored in a Users collection. And we eventually plan to support both single sign-on and password-based login methods for a single user, each of which will be represented as an Account document in an Accounts collection.

At this point, one user has one account, so it doesn’t matter which entity stores the reference (i.e., the user ID). We could have stored the user ID in either the Account or the User document in a one-to-one relation:


However, since one User will eventually have multiple Accounts (or authentication methods), we’ll have a one-to-many model.


In a one-to-many relation between Users and Accounts, each Account points to only one user, so it makes sense to store the User reference on the Account:

We also have many-to-many relations, like the relations between Fweets and Users, because of the complex ways users interact with each other via likes, comments, and refweets. 


Further, we will use a third collection, Fweetstats, to store information about the interaction between a User and a Fweet.

Fweetstats’ data will help us determine, for example, whether or not to color the icons indicating to  the user that he has already liked, commented, or refweeted a Fweet. It also helps us determine what clicking on the heart means: unlike or like.

The final model for the application will look like this: 

The application model of the fwitter application

Fweets are the center of the model, because they contain the most important data of the Fweet such as the information about the message, the number of likes, refweets, comments, and the Cloudinary media that was attached. FaunaDB stores this data in a json format that looks like this: 

As shown in the model and in this example json, hashtags are stored as a list of references. If we wanted to, we could have stored the complete hashtag json in here, and that is the preferred solution in more limited document-based databases that lack relations. However, that would mean that our hashtags would be duplicated everywhere (as they are in more limited databases) and it would be more difficult to search for hashtags and/or retrieve Fweets for a specific hashtag as shown below.

Note that a Fweet does not contain a link to Comments, but the Comments collection contains a reference to the Fweet. That’s because one Comment belongs to one Fweet, but a Fweet can have many comments–similar to the one-to-many relation between Users and Accounts.

Finally, there is a FollowerStats collection which basically saves information about how much users interact with each other in order to personalize their respective feeds. We won’t cover that much in this article, but you can experiment with the queries in the source code and stay tuned for a future article on advanced indexing.

Hopefully, you’re starting to see why we chose something more complex than a ToDo app. Although Fwitter  is nowhere near the complexity of the real Twitter app on which it’s based, it’s already becoming apparent that implementing such an application without relations would be a serious brainbreaker. 

Now, if you haven’t already done so from the github repo, it’s finally time to get our project running locally!

Setup the project

To set up the project, go to the FaunaDB dashboard and sign up. Once you are in the dashboard, click on New Database, fill in a name, and click Save. You should now be on the “Overview” page of your new database. 

Next, we need a key that we will use in our setup scripts. Click on the Security tab in the left sidebar, then click the New key button. 

In the “New key” form, the current database should already be selected. For “Role”, leave it as “Admin”. Optionally, add a key name. Next, click Save and copy the key secret displayed on the next page. It will not be displayed again.

Now that you have your database secret, clone the git repository and follow the readme. We have prepared a few scripts so that you only have to run the following commands to initialize your app, create all collections, and populate your database. The scripts will give you further instructions:

// install node modules npm install // run setup, this will create all the resources in your database // provide the admin key when the script asks for it. // !!! the setup script will give you another key, this is a key // with almost no permissions that you need to place in your .env.local as the // script suggestions npm run setup npm run populate // start the frontend

After the script, your .env.local file should contain the bootstrap key that the script provided you (not the admin key)


You can optionally create an account with Cloudinary and add your cloudname and a public template (there is a default template called ‘ml_default’ which you can make public) to the environment to include images and videos in the fweets. 


Without these variables, the include media button will not work, but the rest of the app should run fine:

Creating the front end

For the frontend, we used Create React App to generate an application, then divided the application into pages and components. Pages are top-level components which have their own URLs. The Login and Register pages speak for themselves. Home is the standard feed of Fweets from the authors we follow; this is the page that we see when we log into our account. And the User and Tag pages show the Fweets for a specific user or tag in reverse chronological order. 

We use React Router to direct to these pages depending on the URL, as you can see in the src/app.js file.

<Router> <SessionProvider value={{ state, dispatch }}> <Layout> <Switch> <Route exact path="/accounts/login"> <Login /> </Route> <Route exact path="/accounts/register"> <Register /> </Route> <Route path="/users/:authorHandle" component={User} /> <Route path="/tags/:tag" component={Tag} /> <Route path="/"> <Home /> </Route> </Switch> </Layout> </SessionProvider> </Router>

The only other thing to note in the above snippet is the SessionProvider, which is a React context to store the user’s information upon login. We’ll revisit this in the authentication section. For now, it’s enough to know that this gives us access to the Account (and thus User) information from each component.

Take a quick look at the home page (src/pages/home.js) to see how we use a combination of hooks to manage our data. The bulk of our application’s logic is implemented in FaunaDB queries which live in the src/fauna/queries folder. All calls to the database pass through the query-manager, which in a future article, we’ll refactor into serverless function calls. But for now these calls originate from the frontend and we’ll secure the sensitive parts of it with FaunaDB’s ABAC security rules and User Defined Functions (UDF). Since FaunaDB behaves as a token-secured API, we do not have to worry about a limit on the amount of connections as we would in traditional databases. 

The FaunaDB JavaScript driver

Next, take a look at the src/fauna/query-manager.js file to see how we connect FaunaDB to our application using FaunaDB’s JavaScript driver, which is just a node module we pulled with `npm install`. As with any node module, we import it into our application as so:

import faunadb from 'faunadb'

And create a client by providing a token. 

this.client = new faunadb.Client({ secret: token || this.bootstrapToken })

We’ll cover tokens a little more in the Authentication section. For now, let’s create some data! 

Creating data

The logic to create a new Fweet document can be found in the src/fauna/queries/fweets.js file. FaunaDB documents are just like JSON, and each Fweet follows the same basic structure: 

const data = { data: { message: message, likes: 0, refweets: 0, comments: 0, created: Now() } }

The Now() function is used to insert the time of the query so that the Fweets in a user’s feed can be sorted chronologically. Note that FaunaDB automatically places timestamps on every database entity for temporal querying. However, the FaunaDB timestamp represents the time the document was last updated, not the time it was created, and the document gets updated every time a Fweet is liked; for our intended sorting order, we need the created time. 

Next, we send this data to FaunaDB with the Create() function. By providing Create() with the reference to the Fweets collection using Collection(‘fweets’), we specify where the data needs to go. 

const query = Create(Collection('fweets'), data )

We can now wrap this query in a function that takes a message parameter and executes it using client.query() which will send the query to the database. Only when we call client.query() will the query be sent to the database and executed. Before that, we combine as many FQL functions as we want to construct our query. 

function createFweet(message, hashtags) { const data = … const query = … return client.query(query) }

Note that we have used plain old JavaScript variables to compose this query and in essence just called functions. Writing FQL is all about function composition; you construct queries by combining small functions into larger expressions. This functional approach has very strong advantages. It allows us to use native language features such as JavaScript variables to compose queries, while also writing higher-order FQL functions that are protected from injection.

For example, in the query below, we add hashtags to the document with a CreateHashtags() function that we’ve defined elsewhere using FQL.

const data = { data: { // ... hashtags: CreateHashtags(tags), likes: 0, // ... }

The way FQL works from within the driver’s host language (in this case, JavaScript) is what makes FQL an eDSL (embedded domain-specific language). Functions like CreateHashtags() behave just like a native FQL function in that they are both just functions that take input. This means that we can easily extend the language with our own functions, like in this open source FQL library from the Fauna community. 

It’s also important to notice that we create two entities in two different collections, in one transaction. Thus, if/when things go wrong, there is no risk that the Fweet is created yet the Hashtags are not. In more technical terms, FaunaDB is transactional and consistent whether you run queries over multiple collections or not, a property that is rare in scalable distributed databases. 

Next, we need to add the author to the query. First, we can use the Identity() FQL function to return a reference to the currently logged in document. As discussed previously in the data modeling section, that document is of the type Account and is separated from Users to support SSO in a later phase.

Then, we need to wrap Identity() in a Get() to access the full Account document and not just the reference to it.


Finally, we wrap all of that in a Select() to select the data.user field from the account document and add it to the data JSON. 

const data = { data: { // ... hashtags: CreateHashtags(tags), author: Select(['data', 'user'], Get(Identity())), likes: 0, // ... } }

Now that we’ve constructed the query, let’s pull it all together and call client.query(query) to execute it.

function createFweet(message, hashtags) { const data = { data: { message: message, likes: 0, refweets: 0, comments: 0, author: Select(['data', 'user'], Get(Identity())), hashtags: CreateHashtags(tags), created: Now() } } const query = Create(Collection('fweets'), data ) return client.query(query) }

By using functional composition, you can easily combine all your advanced logic in one query that will be executed in one transaction. Check out the file src/fauna/queries/fweets.js to see the final result which takes even more advantage of function composition to add rate-limiting, etc. 

Securing your data with UDFs and ABAC roles

The attentive reader will have some thoughts about security by now. We are essentially creating queries in JavaScript and calling these queries from the frontend. What stops a malicious user from altering these queries? 

FaunaDB provides two features that allow us to secure our data: Attribute-Based Access Control (ABAC) and User Defined Functions (UDF). With ABAC, we can control which collections or entities that a specific key or token can access by writing Roles. 

With UDFs, we can push FQL statements to the database by using the CreateFunction(). 

CreateFunction({ name: 'create_fweet', body: <your FQL statement>, })

Once the function is in the database as a UDF, where the application can’t alter it anymore, we then call this UDF from the front end.

client.query( Call(Function('create_fweet'), message, hashTags) )

Since the query is now saved on the database (just like a stored procedure), the user can no longer manipulate it. 

One example of how UDFs can be used to secure a call is that we do not pass in the author of the Fweet. The author of the Fweet is derived from the Identity() function instead, which makes it impossible for a user to write a Fweet on someone’s behalf.

Of course, we still have to define that the user has access to call the UDF. For that, we will use a very simple ABAC role that defines a group of role members and their privileges. This role will be named logged_in_role, its membership will include all of the documents in the Accounts collection, and all of these members will be granted the privilege of calling the create_fweet UDF.

CreateRole( name: 'logged_in_role', privileges: [ { resource: q.Function('create_fweet'), actions: { call: true } } ], membership: [{ resource: Collection('accounts') }], )

We now know that these privileges are granted to an account, but how do we ‘become’ an Account? By using the FaunaDB Login() function to authenticate our users as explained in the next section.

How to implement authentication in FaunaDB

We just showed a role that gives Accounts the permissions to call the create_fweets function. But how do we “become” an Account?.

First, we create a new Account document, storing credentials alongside any other data associated with the Account (in this case, the email address and the reference to the User).

return Create(Collection('accounts'), { credentials: { password: password }, data: { email: email, user: Select(['ref'], Var('user')) } }) }

We can then call Login() on the Account reference, which retrieves a token.

Login( Match( < Account reference > , { password: password } ) )

We use this token in the client to impersonate the Account. Since all Accounts are members of the Account collection, this token fulfills the membership requirement of the logged_in_role and is granted access to call the create_fweet UDF.

To bootstrap this whole process, we have two very important roles.

  • bootstrap_role: can only call the login and register UDFs
  • logged_in_role: can call other functions such as create_fweet

The token you received when you ran the setup script is essentially a key created with the bootstrap_role. A client is created with that token in src/fauna/query-manager.js which will only be able to register or login. Once we log in, we use the new token returned from Login() to create a new FaunaDB client which now grants access to other UDF functions such as create_fweet. Logging out means we just revert to the bootstrap token. You can see this process in the src/fauna/query-manager.js, along with more complex role examples in the src/fauna/setup/roles.js file. 

How to implement the session in React

Previously, in the “Creating the front end” section, we mentioned the SessionProvider component. In React, providers belong to a React Context which is a concept to facilitate data sharing between different components. This is ideal for data such as user information that you need everywhere in your application. By inserting the SessionProvider in the HTML early on, we made sure that each component would have access to it. Now, the only thing a component has to do to access the user details is import the context and use React’s ‘useContext’ hook.

import SessionContext from '../context/session' import React, { useContext } from 'react' // In your component const sessionContext = useContext(SessionContext) const { user } = sessionContext.state

But how does the user end up in the context? When we included the SessionProvider, we passed in a value consisting of the current state and a dispatch function. 

const [state, dispatch] = React.useReducer(sessionReducer, { user: null }) // ... <SessionProvider value={{ state, dispatch }}>

The state is simply the current state, and the dispatch function is called to modify the context. This dispatch function is actually the core of the context since creating a context only involves calling React.createContext() which will give you access to a Provider and a Consumer.

const SessionContext = React.createContext({}) export const SessionProvider = SessionContext.Provider export const SessionConsumer = SessionContext.Consumer export default SessionContext

We can see that the state and dispatch are extracted from something that React calls a reducer (using React.useReducer), so let’s write a reducer.

export const sessionReducer = (state, action) => { switch (action.type) { case 'login': { return { user: } } case 'register': { return { user: } } case 'logout': { return { user: null } } default: { throw new Error(`Unhandled action type: ${action.type}`) } } }

This is the logic that allows you to change the context. In essence, it receives an action and decides how to modify the context given that action. In my case, the action is simply a type with a string. We use this context to keep user information, which means that we call it on a successful login with: 

sessionContext.dispatch({ type: 'login', data: e }) Adding Cloudinary for media 

When we created a Fweet, we did not take into account assets yet. FaunaDB is meant to store application data, not image blobs or video data. However, we can easily store the media on Cloudinary and just keep a link in FaunaDB. The following inserts the Cloudinary script (in app.js):


We then create a Cloudinary Upload Widget (in src/components/uploader.js):

window.cloudinary.createUploadWidget( { cloudName: process.env.REACT_APP_LOCAL___CLOUDINARY_CLOUDNAME, uploadPreset: process.env.REACT_APP_LOCAL___CLOUDINARY_TEMPLATE, }, (error, result) => { // ... } )

As mentioned earlier, you need to provide a Cloudinary cloud name and template in the environment variables (.env.local file) to use this feature. Creating a Cloudinary account is free and once you have an account you can grab the cloud name from the dashboard.

You have the option to use API keys as well to secure uploads. In this case, we upload straight from the front end so the upload uses a public template. To add a template or modify it to make it public, click on the gear icon in the top menu, go to Upload tab, and click Add upload preset

You could also edit the ml_default template and just make it public.

Now, we just call when our media button is clicked.

const handleUploadClick = () => { } return ( <div> <FontAwesomeIcon icon={faImage} onClick={handleUploadClick}></FontAwesomeIcon> </div> )

This provides us with a small media button that will open the Cloudinary Upload Widget when it’s clicked. 

When we create the widget, we can also provide styles and fonts to give it the look and feel of our own application as we did above (in src/components/uploader.js): 

const widget = window.cloudinary.createUploadWidget( { cloudName: process.env.REACT_APP_LOCAL___CLOUDINARY_CLOUDNAME, uploadPreset: process.env.REACT_APP_LOCAL___CLOUDINARY_TEMPLATE, styles: { palette: { window: '#E5E8EB', windowBorder: '#4A4A4A', tabIcon: '#000000', // ... }, fonts: {

Once we have uploaded media to Cloudinary, we receive a bunch of information about the uploaded media, which we then add to the data when we create a Fweet.

We can then simply use the stored id (which Cloudinary refers to as the publicId) with the Cloudinary React library (in src/components/asset.js):

import { Image, Video, Transformation } from 'cloudinary-react'

To show the image in our feed.

<div className="fweet-asset"> <Image publicId={} cloudName={cloudName} fetchFormat="auto" quality="auto" secure="true" /> </div>

When you use the id, instead of the direct URL, Cloudinary does a whole range of optimizations to deliver the media in the most optimal format possible. For example when you add a video image as follows:

<div className="fweet-asset"> <Video playsInline autoPlay loop={true} controls={true} cloudName={cloudName} publicId={publicId}> <Transformation width="600" fetchFormat="auto" crop="scale" /> </Video> </div>

Cloudinary will automatically scale down the video to a width of 600 pixels and deliver it as a WebM (VP9) to Chrome browsers (482 KB), an MP4 (HEVC) to Safari browsers (520 KB), or an MP4 (H.264) to browsers that support neither format (821 KB). Cloudinary does these optimizations server-side, significantly improving page load time and the overall user experience. 

Retrieving data

We have shown how to add data. Now we still need to retrieve data. Getting the data of our Fwitter feed has many challenges. We need to: 

  • Get fweets from people you follow in a specific order (taking time and popularity into account)
  • Get the author of the fweet to show his profile image and handle
  • Get the statistics to show how many likes, refweets and comments it has
  • Get the comments to list those beneath the fweet. 
  • Get info about whether you already liked, refweeted, or commented on this specific fweet. 
  • If it’s a refweet, get the original fweet. 

This kind of query fetches data from many different collections and requires advanced indexing/sorting, but let’s start off simple. How do we get the Fweets? We start off by getting a reference to the Fweets collection using the Collection() function.


And we wrap that in the Documents() function to get all of the collection’s document references.


We then Paginate over these references.


Paginate() requires some explanation. Before calling Paginate(), we had a query that returned a hypothetical set of data. Paginate() actually materializes that data into pages of entities that we can read. FaunaDB requires that we use this Paginate() function to protect us from writing inefficient queries that retrieve every document from a collection, because in a database built for massive scale, that collection could contain millions of documents. Without the safeguard of Paginate(), that could get very expensive!

Let’s save this partial query in a plain JavaScript variable references that we can continue to build on.

const references = Paginate(Documents(Collection('fweets')))

So far, our query only returns a list of references to our Fweets. To get the actual documents, we do exactly what we would do in JavaScript: map over the list with an anonymous function. In FQL, a Lambda is just an anonymous function.

const fweets = Map( references, Lambda(['ref'], Get(Var('ref'))) )

This might seem verbose if you’re used to declarative query languages like SQL that declare what you want and let the database figure out how to get it. In contrast, FQL declares both what you want and how you want it which makes it more procedural. Since you’re the one defining how you want your data, and not the query engine, the price and performance impact of your query is predictable. You can exactly determine how many reads this query costs without executing it, which is a significant advantage if your database contains a huge amount of data and is pay-as-you-go. So there might be a learning curve, but it’s well worth it in the money and hassle it will save you. And once you learn how FQL works, you will find that queries read just like regular code. 

Let’s prepare our query to be extended easily by introducing Let. Let will allow us to bind variables and reuse them immediately in the next variable binding, which allows you to structure your query more elegantly.

const fweets = Map( references, Lambda( ['ref'], Let( { fweet: Get(Var('ref')) }, // Just return the fweet for now Var('fweet') ) ) )

Now that we have this structure, getting extra data is easy. So let’s get the author.

const fweets = Map( references, Lambda( ['ref'], Let( { fweet: Get(Var('ref')), author: Get(Select(['data', 'author'], Var('fweet'))) }, { fweet: Var('fweet'), author: Var('user') } ) ) )

Although we did not write a join, we have just joined Users (the author) with the Fweets. We’ll expand on these building blocks even further in a follow up article. Meanwhile, browse src/fauna/queries/fweets.js to view the final query and several more examples. 

More in the code base

If you haven’t already, please open the code base for this Fwitter example app. You will find a plethora of well-commented examples we haven’t explored here, but will in future articles. This section touches on a few files we think you should check out.

First, check out the src/fauna/queries/fweets.js file for examples of how to do complex matching and sorting with FaunaDB’s indexes (the indexes are created in src/fauna/setup/fweets.js). We implemented three different access patterns to get Fweets by popularity and time, by handle, and by tag.

Getting Fweets by popularity and time is a particularly interesting access pattern because it actually sorts the Fweets by a sort of decaying popularity based on users’ interactions with each other. 

Also, check out src/fauna/queries/search.js, where we’ve implemented autocomplete based on FaunaDB indexes and index bindings to search for authors and tags. Since FaunaDB can index over multiple collections, we can write one index that supports an autocomplete type of search on both Users and Tags.

We’ve implemented these examples because the combination of flexible and powerful indexes with relations is rare for scalable distributed databases. Databases that lack relations and flexible indexes require you to know in advance how your data will be accessed and you will run into problems when your business logic needs to change to accommodate your clients’ evolving use cases.

In FaunaDB, if you did not foresee a specific way that you’d like to access your data, no worries — just add an Index! We have range indexes, term indexes, and composite indexes that can be specified whenever you want without having to code around eventual consistency. 

A preview of what’s to come 

As mentioned in the introduction, we’re introducing this Fwitter app to demonstrate complex, real-world use cases. That said, a few features are still missing and will be covered in future articles, including streaming, pagination, benchmarks, and a more advanced security model with short-lived tokens, JWT tokens, single sign-on (possibly using a service like Auth0), IP-based rate limiting (with Cloudflare workers), e-mail verification (with a service like SendGrid), and HttpOnly cookies.

The end result will be a stack that relies on services and serverless functions which is very similar to a dynamic JAMstack app, minus the static site generator. Stay tuned for the follow-up articles and make sure to subscribe to the Fauna blog and monitor CSS-Tricks for more FaunaDB-related articles. 

The post Rethinking Twitter as a Serverless App appeared first on CSS-Tricks.

My Visual Studio Code Setup: Extensions and Themes

Css Tricks - Wed, 04/22/2020 - 10:19am

Matthias Ott’s posted his VS Code setup. I find lists like this (I rounded up some recent updates of my own) irresistible, probably because, like y’all, I spend an awful lot of time in VS Code and wanna make sure I’m getting the most out of it.

Things from the list that stood out to me:

  • I didn’t realize Bracket Pair Colorizer had gone v2 and it’s a separate install.
  • I didn’t realize you needed an extension to honor .editorconfig files.
  • I wasn’t using anything for PHP, but Matthias listed PHP Intelephense and I’m giving it a whirl. It has fewer users than the non-weirdly named one though? And when I installed that, I saw Format HTML in PHP which I’m also trying because, yes, please! (Even Prettier’s PHP add-on can’t do that.)

Messing with extensions is also a good opportunity to clear out old crap.

Also super interesting…

So, is currently an email series (you can sign up now to catch up).

It's turning into an ebook/screencast/extra-stuff product.

I'm gonna cover A TON, but I think the PHP section will be one of the most valuable parts for most of you

— Caleb Porzio (@calebporzio) April 20, 2020

The main point of that series is cleaning up the interface of VS Code in extreme ways. All the way down to:

Direct Link to ArticlePermalink

The post My Visual Studio Code Setup: Extensions and Themes appeared first on CSS-Tricks.

Puzzles and Mysteries

Css Tricks - Wed, 04/22/2020 - 4:54am

Bob Hoffman:

Puzzles, [Malcom Gladwell] wrote, are problems for which there is not enough information. An example of a puzzle: Where is Jimmy Hoffa buried? If we had more information, we would know the answer. If someone told us “Jimmy Hoffa is buried in New Jersey,” we’d know a little more than we know now. If they said, “He’s buried in northern New Jersey,” we’d know even more. If they said, “He’s buried in the Meadowlands,” we’d have an answer to our puzzle.

On the other hand, there are mysteries. Mysteries are problems for which we have plenty of information, but no accurate analysis. An example of a mystery: Why do inner-city schools do such a crappy job of educating kids? There are thousands of studies. Every education department of every university in America has done a study on this; every committee of Congress has done a report on it; every editorial writer has a theory about it, and every pundit has an opinion. And yet, we have no definitive answer.

It’s fun to think about how that correlates to front-end development. When we’re coding, every¹ problem we face is a puzzle. We just need more information and we can figure out what to do. Sometimes, design is like that too. Information can make our designs better. But success in design² has a nebulous quality that makes it feel more like a mystery.

  1. Except for CORS. CORS is a mystery.
  2. And business, marketing, and love.

Direct Link to ArticlePermalink

The post Puzzles and Mysteries appeared first on CSS-Tricks.

Fake Code

Css Tricks - Tue, 04/21/2020 - 11:01am

Here’s a fun little idea from Knut Synstad. You give it the URL of a GitHub Gist and it converts the Gist into grayscale rounded blobs (SVG) that sorta look like code if you squint. Maybe fun for interesting dynamic backgrounds or for whatever you might use code-looking stock art for.

It reminded me of Christian Naths’s Redacted font, which turns every glyph into a box or squiggles.

And if you need some actual totally fake code, Harry Parton’s Pen is nice for that:

CodePen Embed Fallback

The post Fake Code appeared first on CSS-Tricks.

Constrained CSS grids without `max-width`

Css Tricks - Tue, 04/21/2020 - 4:43am

Ain’t nothing wrong with max-width, but Ethan makes a point in the last sentence:

Rather than simply defaulting to max-width as a constraint, I can use the empty space around my design, and treat it as a layout tool.

If the space “around” your grid is actually part of the grid, it’s easier to use. Maybe you’d decide to scootch some author information up there after all, or show an ad, or who knows what. It will be more robust if you do it within the established grid.

Direct Link to ArticlePermalink

The post Constrained CSS grids without `max-width` appeared first on CSS-Tricks.

Drupal to Jamstack

Css Tricks - Tue, 04/21/2020 - 3:04am

I’ve been harping for a while that Jamstack doesn’t necessarily mean throwing away your old CMS. In fact, I’d argue that Jamstack is at it’s most powerful when paired with a system that you already know, are comfortable with, and perhaps even like. You’d call that decoupling the front end.

Netlify has a webinar coming up on exactly this, featuring Alex De Winne, who is going to show you a real-world site in a live demo combining Drupal (a classic PHP & MySQL CMS that powers an absolute ton of sites) and Jamstack architecture.

Register for Free Webinar

The post Drupal to Jamstack appeared first on CSS-Tricks.

Can JavaScript Detect the Browser’s Zoom Level?

Css Tricks - Mon, 04/20/2020 - 1:08pm

No, not really.

My first guess was that this was intentionally not exposed in browsers because browsers intentionally don’t want us fighting it — or making well-intentioned but bad-outcome decisions based on that info. But I don’t see any evidence of that.

StackOverflow answers paint how weird cross-browser it can be. This script from 2013 works for me in Chrome, but not at all in Safari and reports incorrectly in Firefox. Even if that script worked, it relies on user agent detection (which is not long for this world) and some incredibly weird hacks.

So please, feel free to correct me if I’m wrong, but I think the answer is that we can’t really do this right now.

There is a thing called the Visual Viewport API

I’m kinda confused by it.

  • The spec is a draft
  • The support chart lists a lot of support
  • window.visualViewport is defined in Firefox, Safari, and Chrome (desktop)
  • But… window.visualViewport.scale is always 1 in Safari and Chrome, and undefined in Firefox. In other words: useless.

I don’t entirely know if that is supposed to accurately represent the browser zoom level, because the spec talks about specifically about pinch-zoom. So, maybe it’s just not intended for desktop browser zoom levels.

What’s a use case here?

I had a fella spell out a situation like this:

He wanted to use CSS grid to layout cemetery plots (interesting already), like a top-down blueprint of a graveyard. There was lots of information in the layout. If you were “zoomed out” so you could see the whole graveyard on one page, the text in each area would be too small to read (sincethe type would be sized to fit within the boxes/graves). Ideally, the page would hide that text while the browser is zoomed out (perhaps a .hide-text class). When zoomed in far enough, the text is shown again.


// Dunno if "resize" is best. I don't know what the "change zoom" event would be window.visualViewport.addEventListener("resize", viewportHandler); function viewportHandler(event) { // NOTE: This doesn't actually work at time of writing if ( > 3) { document.body.classList.remove("hide-text"); } else { document.body.classList.add("hide-text"); } } There is Pixel Density…

Ben Nadel recently blogged: Looking At How Browser Zoom Affects CSS Media Queries And Pixel-Density.

If you look at window.devicePixelRatio and zoom in, the pixel density in Chrome and Firefox will increase as you zoom in and decrease as you zoom out. Theoretically, you could test the original value (it might start at different places for users with different screens) and use changes in that value to guess zoom. But… in Safari it does nothing, as in, it stays the same regardless of zoom. Plus, the operating system zoom level can affect things here, making it extra tricky; not to mention that a page might start at a zoomed in level which could throw off the whole calculation from the start.

The post Can JavaScript Detect the Browser’s Zoom Level? appeared first on CSS-Tricks.

The Contrast Triangle

Css Tricks - Mon, 04/20/2020 - 12:15pm

Chip Cullen:

Let’s say you’re building a site, and you’re working with a designer. They come to you with some solid designs, and you’re ready to go. You’re also a conscientious front end developer and you like to make sure the sites you build are accessible. The designs you’re working from have some body copy, but you notice that the links inside the body copy are missing underlines.

You are now in The Contrast Triangle.

The “triangle” meaning a three-sided comparison:

  • The contrast between the background and text
  • The contrast between the background and links
  • the contrast between text and links

I would think this matters whether you underline links or not, but I take the point that without underlines the problem is worse.

I’d also argue the problem is even more complicated. You’ve also got to consider the text when it is selected, and make sure everything has contrast when that is the case too. And the hover states, active states, visited states, and focus states. So it’s really like hexacontakaienneagon of contrast or something.

Direct Link to ArticlePermalink

The post The Contrast Triangle appeared first on CSS-Tricks.

How (some) good corporate engineering blogs are written

Css Tricks - Sat, 04/18/2020 - 10:23am

Interesting research from Dan Luu¹:

… it’s pretty common for my personal blog to get more traffic than the entire corp eng blog for a company with a nine to ten figure valuation and it’s not uncommon for my blog to get an order of magnitude more traffic.

I think this is odd because tech companies in that class often have hundreds to thousands of employees. They’re overwhelmingly likely to be better equipped to write a compelling blog than I am and companies get a lot more value from having a compelling blog than I do.

First, yes. There is a crapload of value in having a good blog (top of funnel traffic, showcasing culture for hiring, establishing industry leadership…) yet so few companies do it well even when they have more than enough resources to do so.

Dan doesn’t just speculate on this, he interviewed people at companies that have actually good engineering blogs: Heap, Segment, and Cloudflare. Then he listed their internal process for blogging. The first step in all three is the same: “Someone has an idea to write a post”. That makes sense, but I would think there is something deeper going on with good blogs: engineers that want to come up with ideas because it is encouraged and incentivized. And then after the ball is rolling, there is a positive feedback loop and as few blockers as possible.

Random observations from me:

  • We recently started using Appcues at CodePen, and it was on our radar at all because people at CodePen read their blog and liked it.
  • While Appcues largely blogs about stuff that is directly related to stuff their software can help with, Logrocket, a software product for tracking JavaScript errors, has a blog that isn’t terribly different than CSS-Tricks. It’s just about front-end everything and every single blog post has a section in it that is a pitch for the product. Looks like they’ve been doing it for about 3 years now, so my hunch is that it works extremely well.
  • All the browser vendors do a pretty good job of blogging overall, but at the same time, feel a bit disjointed. What blog(s) should I be reading for Mozilla/Firefox stuff? I don’t know really. Is it the official looking one? Or the “hacks” one? Or the planet one? Or nightly one? Or the one for releases? Google seems to have the same problem. There isn’t an obvious hub of writing.
  • Stripe has a strong engineering blog, but they take it to another level by producing a fancy publication (Increment) that is both online and in print.
  1. A little CSS would go a long way on this site: body { max-width: 600px; margin: 20px auto; }

Direct Link to ArticlePermalink

The post How (some) good corporate engineering blogs are written appeared first on CSS-Tricks.

Front-End Challenges

Css Tricks - Fri, 04/17/2020 - 11:50am

My favorite way to level up as a front-end developer is to do the work. Literally just build websites. If you can do it for money, great, you should. If the websites you make can help yourself or anyone else you care about, then that’s also great. In lieu of that, you can also make things simply for the sake of making them, and you’ll still level up. It’s certainly better than just reading about things!

Here’s some resources that encourage you to level up by building things for the sake of leveling up, if you’re up to it.

Frontend Mentor

It looks like this recently launched, and it’s what inspired this post. This idea of giving people front-end work to do is enough to build a business around! Some of them are free, and some of them are not.


Other businesses have centered themselves around this too. HackerRank is all about getting jobs and hiring, so they’ve got a very strong agenda, but part of the way they do that is putting you through these skill tests (solving challenges) which are meant to asses you, but you can certainly learn from them too.

Others like this: Codewars, ChallengeRocket, Codesignal, Topcoder (Jeepers, VCs must love this idea.)


Coderbyte has paid plans too, and they’re designed to leveling up your job interviewing skills with challenges.

Classic situation: sometimes the site is the product and you’re the customer, and sometimes hiring companies are the customer and you’re the product.

Build Dribbble shots

Here’s the classic move: find something you like on Dribbble, rebuild it. The @keyframers often do it. Tim Evko’s Practice site used to pick a shot for you (along with random GitHub issues and random coding challenges), but the Dribbble part appears broken at the moment. The other stuff still works!

Matt Delac used to do a series along these lines. Indrek Lasn also does it in Medium posts.

Front-End Challenges Club

Andy Bell did a Front-End Challenges Club for a while, and while I think it’s on break, you can view the archives.

CodePen Challenges

CodePen Challenges run every week are are a prompt (along with ideas and resources) for building whatever you like. Low key.

100 Days of CSS Challenge

Matthias Martin created 100 days of CSS challenges. They are all there for you to see, including entries from other people — but the point is for you to give it a shot yourself, of course.

Daily UI

Daily UI challenges gives you a new challenge every day that starts when you sign up (and it’s free). Lots of people complete the challenge with code.


Frontloops charges $19 for 30 challenges, which includes information, advice, assets, and a solution.


If your idea of a fun challenge is mimicking a design in as few bytes of code as possible, CSSBattle will appeal to you.

Writing things as tersely as possible is often called “Code Golf” and there is a challenge site for that too.

Ace Front End

Ace Front End has challenges that focus specifically on vanilla HTML, CSS, and JavaScript.

I just happened to notice that the first challenge is a drop down navigation menu, and it doesn’t handle things like aria-expanded. I’m not entirely sure how big of a problem that is and I don’t mean to pick on Ace Front End — it’s just a reminder that there could be problems with any of these challenges. But that doesn’t mean you can’t learn something from them.


Codier has public challenges that include solutions posted by other users.

rendezvous with cassidoo

Cassidy’s weekly newsletter includes a challenge in every issue.

Rina Diane Caballar quoting Tim Carry in Extending the Limits of CSS:

Carry’s advice is to start with a real-world object—the interface of a gaming console or a calculator, for example—and to try to recreate it using only CSS. “A great way to push the boundaries with a language is to make something that the language wasn’t meant to be doing in the first place,” he says.

The post Front-End Challenges appeared first on CSS-Tricks.

Pseudo-Randomly Adding Illustrations with CSS

Css Tricks - Fri, 04/17/2020 - 10:28am

Between each post of Eric Meyer’s blog there’s this rather lovely illustration that can randomly be one of these five options:

Eric made each illustration into a separate background image then switches out that image with the nth-of-type CSS property, like this:

.entry:nth-of-type(2n+1)::before { background-image: url(image-1.png); } .entry:nth-of-type(3n+1)::before { background-image: url(image-2.png); } .entry:nth-of-type(4n+1)::before { background-image: url(image-3.png); } .entry:nth-of-type(5n+1)::before { background-image: url(image-4.png); }

This seems like a good time to plug our very own little :nth Tester tool. It definitely helps me understand what something like (2n+1) means in English. You can type in any string you like and see what effect that has on your site:

Anyway, back to Eric’s post. As he mentions, his technique is pseudo-random in that it looks like a random image on the page but it technically isn’t. Either way, I think it’s a really lovely technique! And it certainly breaks up the visual monotony that happens when you’re looking at a website for too long.

Here’s what it looks like in practice:

Lovely stuff!

Another way to do this is to use random numbers in CSS. For example, we could set a variable in JavaScript and then apply it with CSS custom properties. Or we could put all the images in a single sprite file and change the background-position based on a random number.

This is definitely one of those things in CSS where there are no wrong answers; just different ways to do the same awesome thing!

Direct Link to ArticlePermalink

The post Pseudo-Randomly Adding Illustrations with CSS appeared first on CSS-Tricks.

Syndicate content
©2003 - Present Akamai Design & Development.