Tech News

Serverless Functions as Proxies

Css Tricks - Tue, 08/03/2021 - 10:23am

The first time cloud functions / serverless functions clicked for me was when I saw and tried Auth0’s (now defunct) Webtask. It was a little CodePen-like IDE but you didn’t really see anything aside from code and logs. The point was to write little bits of Node when you hit the functions URL (that’s literally exactly what a serverless function is). It would even store your secrets for you, meaning that you could use the serverless function as a proxy. You hit the function, the function hits the API using your unexposed API Key secrets, the API returns data, the function then returns data, and that data is available to the client side for you to work with. The entire point was 1) you can snag data from an otherwise totally static website, and 2) your API keys are protected. Brilliant.

I still miss Webtask, but I’m sure there are better and fancier things these days. I don’t have a solid handle on the whole landscape. Even AWS has an online editor for lambdas (a “lambda” is AWS’s standards-setting implementation of what a serverless function is), but using the AWS console directly for anything isn’t usually… very good. Functions in AWS Amplify are probably a better bet there.

My guess is the proper modern way of building these things are things like…

But there are all sorts of other tools that seem pretty modern that I just can’t speak to as well, but seem good:

But what makes me think of all this, and is also in the category of things I don’t have any personal experience with, is Pipedream. I heard about it via Raymond, who has a similar story to mine:

One of the first things that intrigued me about serverless, and honestly it’s not really that novel, is the ability to build proxies to other APIs. So for example, imagine a cool API that requires authentication of some sort to use, like an API key. If you use this in client-side JavaScript, anyone can look at your code and get your key. Better services let you lock a key to a domain, but if you don’t have that option, then a simple use of serverless is to simply give you an endpoint that makes the call to the API with your key.

Raymond Camden, “Using Pipedream to Proxy Other APIs”

Pipedream looks pretty fancy:

Not only is it a web-based IDE for crafting functions, but I can trigger it a bunch of ways—a URL of course, but also on a CRON, or things like via email or RSS. Neat. Look at all the other options too. Slack? GitHub? Twitter? It’s kinda like how Zapier looks in that way, only where Zapier is entirely no-code (I think). Pipedream makes code a first-class citizen.

And it does secrets by way of account-level environment variables.

So, it’s perfect for being a serverless proxy. Read Raymond’s post for actual implementation and code examples.

The post Serverless Functions as Proxies appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Automatic Daily GitHub Backups, Restored in Seconds

Css Tricks - Tue, 08/03/2021 - 10:22am

Any company that uses GitHub for critical applications needs a backup that can be restored quickly when needed. Cyberattacks, human errors, or a forced push are just some of the scenarios that can result in the loss of GitHub data. In the event of an emergency, you can’t be wasting time asking which developer has the most recent copy of your code. You need to restore your code, and you need it restored now.

Why do you need GitHub backups?

GitHub is a git repository hosting and version control platform, not a backup solution. GitHub, like most SaaS platforms, follows the Shared Responsibility Model. This model divides the responsibilities of users and the responsibilities of the platform, with account-level data security falling securely in the realm of users.

GitHub’s terms of service specifically states that they are “not liable to you or any third party for any loss of profits, use, goodwill, or data.” This means that the information stored in your GitHub account — all of your repositories including code, issues, pull requests, and other essential metadata — is your responsibility to back up.

Although the most common cause for data loss is human error, malicious attacks are becoming increasingly common. Recently, GitHub reported a phishing attack named SawFish which even worked against some kinds of two-factor authentication (2FA) attack, according to Sophos. Some Rewind customers also reported choosing Rewind after phishing attacks resulted in their GitHub data being stolen.Having a backup of your code protects your business’s intellectual property (IP) ensuring that your critical data is always recoverable no matter what goes wrong. 

Regular data backups and other data hygiene principles are also often required for compliance purposes. Required data hygiene may include keeping backups with consistent intervals, having offsite backups, regularly testing restores, having an audit report, and a history of data pull requests among many others. 

In-house backup solution — pros and cons

“Build vs. Buy” is a common refrain when investigating new tech tools. After all, why pay for something that you could build yourself? Developing an in-house backup solution for your GitHub repositories and metadata is an option. For teams with developer resources to spare, this can be an economical choice. However, building your own backup solution isn’t as simple as writing a backup script.

In-house backup scripts need to be written, tested, and maintained which has an indirect cost to your business. These scripts are also vulnerable to updates to the GitHub APIs. Since the GitHub API changes periodically, in-house scripts have to be updated and tested to make sure your data is still being backed up. Once you’ve backed up your data, you’ll also need to spend developer resources to figure out how you will restore it quickly in case of an emergency. This is one of the hardest capabilities to build, yet the most important. After all, what good is a data backup if you can’t use it to actually put your data back?

Another thorn to be solved is metadata. Your repo is much more than just code: pull requests, issues, commits, branches and more are all essential to your workflow. Backing up and restoring metadata isn’t the same process as backing up and restoring code. Most companies reported having difficulty with backing up and restoring metadata such as Mercado Libre, which backs up 13,000+ repositories with BackHub by Rewind. Another Rewind customer, a major player in the EdTech space, also reported that they chose Rewind because they were not able to backup their metadata which was essential for their business.  

On the other hand, the main advantage of an inhouse backup solution is that you have more control over your backups. This may be the frequency or time of the backups  among other things. However, this comes at the cost of using your developer and IT resources for developing, maintaining, and testing your in-house solution. Thus, before deciding to build an in-house backup solution, identify your needs and assess your capabilities. Consider if you need to backup your metadata and what your target for time to recovery is. Then, ensure your team has the necessary resources and time to fully develop and maintain the in-house solution. 

Why use BackHub by Rewind for GitHub backups?

BackHub by Rewind automates daily backups of your GitHub repositories, pull requests and associated metadata including:

  • Commits (including comments)
  • Issues
  • Projects
  • Releases
  • Milestones
  • Wikis

BackHub by Rewind is set-up in minutes and allows you to restore your repositories and metadata in a few clicks. As the solution works natively within GitHub, your repositories and associated metadata is directly and securely restored to your GitHub account.

When you do need to perform a data restore, simply install BackHub by Rewind’s dedicated restore app, select the date where everything worked perfectly, and click ‘restore’. Your selected repositories, including associated metadata, will then be pushed and restored directly back into your GitHub account.

BackHub by Rewind follows the security principle of least access, meaning once the app is installed, it only has “read” access to your data. This means that BackHub by Rewind cannot alter, modify, or change the code in your repository in any way. 

To restore your data, “write” access is required, and so BackHub by Rewind has a separate app used solely for data restoration that can be deleted once restoration is complete. This provides an additional layer of security and peace of mind that your code is kept safe, secure, and secret. 

BackHub by Rewind was built with enterprise compliance in mind. Enterprise plans offer advanced features such as 365 days of data retention, full account activity logs, choice of data storage location (US or Europe), and SLA with 99.9% availability. With over 2 PB of data backed up worldwide, Rewind is SOC2 Type 1 certified, and expects to receive SOC2 Type 2 by the end of 2021. 

BackHub by Rewind is a true “set it and forget it” tool, and requires no specialized technical or coding knowledge to operate. Running quietly in the background, BackHub by Rewind backs up your GitHub data everyday and allows you to restore it in a few clicks so that your development team can focus on your core product.

If you are interested in BackHub by Rewind, start your free 14-day trial to test it out yourself. 

Direct Link to ArticlePermalink

The post Automatic Daily GitHub Backups, Restored in Seconds appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

How to Make the Most of Every UX Interview

Usability Geek - Tue, 08/03/2021 - 7:39am
At the heart of every user experience design is a strong emphasis on the user’s own perspective. To properly design products and interactions that are helpful for the user, one must understand...
Categories: Web Standards

A Deep Dive on Skipping to Content

Css Tricks - Tue, 08/03/2021 - 4:35am

While most people browsing the web on a computer use a mouse, many rely on their keyboard instead. Theoretically, using a web page with the keyboard should not be a problem — press the TAB key to move the keyboard focus from one focusable element to the next then press ENTER to activate, easy! However, many (if not most) websites tend to have a menu of links at the top of the page, and it can sometimes take a lot of presses to get to the content that you want. Take the homepage of 20min for example, one of the biggest news sites here in Switzerland. You want to read the top story? That’ll be the best part of 40 key presses — not the best best use of anyone’s time.

So, if you want keyboard users to actually use your website rather than getting bored and going somewhere else, you need to do a bit of work behind the scenes to make skipping straight to your main content quicker and easier. You can find all sorts of techniques for this scattered across the web (including here at CSS-Tricks) but most are missing a trick or two and many recommend using outdated or deprecated code. So, in this article, I’m going to take a deep dive into skipping to content and cover everything in a 2021-friendly fashion.

Two types of keyboard users

Although there are numerous differences in the exact type of keyboard or equivalent switch device that people use to navigate, from a coding point of view, we only need to consider two groups:

  • People who use the keyboard in conjunction with a screen reader — like NVDA or JAWS on a PC, or VoiceOver on a Mac — that reads the content of the screen out loud. These devices are often used by people with more severe visual impairments.
  • All other keyboard users.

Our skip-to-content techniques need to cater to both these groups while not getting in the way of all the mouse users. We will use two complementary techniques to get the best result: landmarks and skip links.

Look at a basic example

I created an example website I’m calling Style Magic to illustrate the techniques we’re covering. We’ll start off with it in a state that works fine for a mouse user but is a bit of a pain for those using a keyboard. You can find the base site and the versions for each of the techniques in this collection over at CodePen and, because testing keyboard navigation is a little tricky on CodePen, you can also find standalone versions here.

CodePen Embed Fallback

Try using the TAB key to navigate this example. (It’s easier on the standalone page; TAB to move from one link to the next, and SHIFT+TAB to go backwards.) You will find that it’s not too bad, but only because there aren’t many menu items.

If you have the time and are on Windows then as I’d also encourage you to download a free copy of the NVDA screen reader and try all the examples with that too, referring to WebAIM’s overview for usage. Most of you on a Mac already have the VoiceOver screen reader available and WebAIM has a great intro to using it as well.

Adding landmarks

One of the things that screen reading software can do is display a list of landmarks that they find on a web page. Landmarks represent significant areas of a page, and the user can pull up that list and then jump straight to one of those landmarks.

If you are using NVDA with a full keyboard, you hit INS+F7 to bring up the “Elements List” then ALT+d to show the landmarks. (You can find a similar list on VoiceOver by using the Web Item Rotor.) If you do that on example site, though, you will only be presented with an unhelpful empty list.

A disappointingly empty list of landmarks in NVDA

Let’s fix that first.

Adding landmarks is incredibly easy and, if you are using HTML5, you might already have them on your website without realizing it, as they are directly linked to the HTML5 semantic elements (<header>, <main>, <footer>, and so on).

Here’s a before and after of the HTML used to generate the header section of the site:

<div class="bg-dark"> <div class="content-width flex-container"> <div class="branding"><a href="#">Style Magic</a></div> <div class="menu-right with-branding"> <a href="#">Home</a> <!-- etc. --> </div> </div> </div>

Becomes

<div class="bg-dark"> <header class="content-width flex-container"> <section class="branding"><a href="#">Style Magic</a></section> <nav aria-label="Main menu" class="menu-right with-branding"> <a href="#">Home</a> <!-- etc. --> </nav> </header> </div>

The classes used remain the same, so we don’t need to make any changes at all to the CSS.

Here’s a full list of the changes we need to make in our example site:

  • The <div> denoting the header at the top of the page is now a <header> element.
  • The <div> containing the branding is now a <section> element.
  • The two <div>s containing menus have been replaced with <nav> elements.
  • The two new <nav> elements have been given an aria-label attribute which describes them: “Main menu” for the menu at the top of the page, and “Utility menu” for the menu at the bottom of the page.
  • The <div> containing the main content of the page is now a <main> element.
  • The <div> denoting the footer at the bottom of the page is now a <footer> element.

You can see the full updated HTML on CodePen.

Let’s try that landmark list trick in NVDA again (INS+F7 then ALT+d — here’s the link to the standalone page so you can test yourself):

Hurrah for landmarks!

Great! We now have the banner landmark (mapped to the <header> element), Main menu; navigation (mapped to the top <nav> element, and displaying our aria-label), main (mapped to <main>) and content info (mapped to footer). From this dialog I can use TAB and the cursor keys to select the main landmark and skip straight to the content of the page, or even better, I can just press the D key when browsing the page to jump from one landmark role directly to the next. Users of the JAWS screen reader have it even easier — they can simply press Q when browsing to jump straight to the main landmark.

As an added bonus, using semantic elements also help search engines understand and index your content better. That’s a nice little side benefit of making a site much more accessible.

Adding a skip link

I expect you’re sitting back thinking “job done” at this point. Well, I’m afraid there’s always a “but” to consider. Google did some research way back in 2011 on the use of CTRL+f to search within a web page and found that a startling 90% of people either didn’t know it existed, or have never used it. Users with a screen reader behave in much the same way when it comes to landmarks — a large portion of them simply do not use this feature even though it’s very useful. So, we’re going to add a skip link to our site to help out both groups as well as all those keyboard users who don’t use a screen reader.

The basic requirements for what makes a good skip link are:

  • It should be perceivable to all keyboard users (including screen reader users) when it is needed.
  • It should provide enough information to the keyboard user to explain what it does.
  • It should work on as wide a range of current browsers as possible.
  • It should not interfere with the browsing of a mouse user.
Step 1: Improving the keyboard focus appearance

First up, we’re going to improve the visibility of the keyboard focus across the site. You can think of the keyboard focus as the equivalent to the position of the cursor when you are editing text in a word processor. When you use the TAB key to navigate the keyboard focus moves from link to link (or form control).

The latest web browsers do a reasonable job of showing the position of the keyboard focus but can still benefit from a helping hand. There are lots of creative ways to style the focus ring, though our goal is making it stand out more than anything.

We can use the :focus pseudo-class for our styling (and it’s a good idea to apply the same styles to :hover as well, which we’ve already done on the example site — CodePen, live site). That’s the very least we can do, though it’s common to go further and invert the link colors on :focus throughout the page.

Here’s some CSS for our :focus state (a copy of what we already have for :hover):

a:focus { /* generic rule for entire page */ border-bottom-color: #1295e6; } .menu-right a:focus, .branding a:focus { /* inverted colors for links in the header and footer */ background-color: white; color: #1295e6; } Step 2: Adding the HTML and CSS

The last change is to add the skip link itself to the HTML and CSS. It consists of two parts, the trigger (the link) and the target (the landmark). Here’s the HTML that I recommend for the trigger, placed right at the start of the page just inside the <header> element:

<header class="content-width flex-container"> <a href="#skip-link-target" class="text-assistive display-at-top-on-focus">Skip to main content.</a> <!-- etc. --> </header>

And here’s the HTML for the target, placed directly before the start of the <main> content:

<a href="#skip-link-target" class="text-assistive display-at-top-on-focus" id="skip-link-target">Start of main content.</a> <main class="content-width"> <!-- etc. --> </main>

Here’s how the HTML works:

  • The skip link trigger links to the skip link target using a standard page fragment (href="#skip-link-target") which references the id attribute of the target (id="skip-link-target"). Following the link moves the keyboard focus from the trigger to the target.
  • We link to an anchor (<a>) element rather than adding the id attribute directly to the <main> element for two reasons. First, it avoids any issues with the keyboard focus not moving correctly (which can be a problem in some browsers); secondly, it means we can provide clear feedback to the user to show that the skip link worked.
  • The text of the two links is descriptive so as to clearly explain to the user what is happening.

We now have a functioning skip link, but there’s one problem: it’s visible to everyone. We’ll use CSS to hide it from view by default, which keeps it out of the way of mouse users, then have it appear only when it receives the keyboard focus. There are lots of ways to do this and most of them are okay, but there’s a couple of wrong ways that you should avoid:

  • Do: use clip-path to make the link invisible, or use transform: translate or position: absolute to position it off screen instead.
  • Don’t: use display: none, visibility: hidden, the hidden attribute, or set the width or height of the skip link to zero. All of these will make your skip link unusable for one or both classes of keyboard users.
  • Don’t: use clip as it is deprecated.

Here’s the code that I recommend to hide both links. Using clip-path along with its prefixed -webkit- version hits the spot for 96.84% of users at time of writing, which (in my opinion) is fine for most websites and use cases. Should your use case require it, there are a number of other techniques available that are detailed on WebAIM.

.text-assistive { -webkit-clip-path: polygon(0 0, 0 0, 0 0, 0 0); clip-path: polygon(0 0, 0 0, 0 0, 0 0); box-sizing: border-box; position: absolute; margin: 0; padding: 0; }

And to show the links when they have focus, I recommend using a version of this CSS, with colors and sizing to match your branding:

.text-assistive.display-at-top-on-focus { top: 0; left: 0; width: 100%; } .text-assistive.display-at-top-on-focus:focus { -webkit-clip-path: none; clip-path: none; z-index: 999; height: 80px; line-height: 80px; background: white; font-size: 1.2rem; text-decoration: none; color: #1295e6; text-align: center; } #skip-link-target:focus { background: #084367; color: white; }

This provides for a very visible display of the trigger and the target right at the top of the page where a user would expect to see the keyboard focus directly after loading the page. There is also a color change when you follow the link to provide clear feedback that something has happened. You can fiddle with the code yourself on CodePen (shown below) and test it with NVDA on the standalone page.

CodePen Embed Fallback Taking this further

Skip links aren’t just for Christmas your main menu, they are useful whenever your web page has a long list of links. In fact, this CodePen demonstrates a good approach to skip links within the content of your pages (standalone page here) using transform: translateY() in CSS to hide and show the triggers and targets. And if you are in the “lucky” position of needing to support older browsers, then you here’s a technique for that on this CodePen (standalone page here).

Let’s check it out!

To finish off, here are a couple of short videos demonstrating how the skip links work for our two classes of keyboard user.

Here’s how the finished skip link works when using the NVDA screen reader:

Here it is again when browsing using the keyboard without a screen reader:

We just looked at what I consider to be a modern approach for accessible skip links in 2021. We took some of the ideas from past examples while updating them to account for better CSS practices, an improved UI for keyboard users, and an improved experience for those using a screen reader, thanks to an updated approach to the HTML.

The post A Deep Dive on Skipping to Content appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

A Shared ESLint Configuration

Css Tricks - Fri, 07/30/2021 - 10:07am

Looks like it was almost 9 years ago when Airbnb first published their JavaScript Style Guide. 112k stars on GitHub later, it seems like the de facto preset for Babel / ES Lint. But it’s not the only company out there with public ES Lint setups. Katy recently shared Mapbox’s setup.

ESLint plugins will help keep your code consistent and improve the quality, but they are also excellent teaching tools. When I come across a plugin, I take joy in reading each rule to learn the benefits of enabling or disabling it.

Katy DeCorah, “A shared ESLint configuration”

Sophisticated linting as-you-author is one of those things that has really upped the game of development over the years.

Direct Link to ArticlePermalink

The post A Shared ESLint Configuration appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Conjuring Generative Blobs With The CSS Paint API

Css Tricks - Fri, 07/30/2021 - 4:54am

The CSS Paint API (part of the magical Houdini family) opens the door to an exciting new world of design in CSS. Using the Paint API, we can create custom shapes, intricate patterns, and beautiful animations — all with a touch of randomness — in a way that is portable, fast, and responsive.

We are going to dip our toes into the bubbling cauldron of generative CSS magic by creating my favorite shape, the blob. Random blobs are a great starting point for anyone new to generative art/design, and we will be learning the CSS Paint API as we go, so this is an ideal starting point for folks who are new to this world. You’ll be a generative CSS magician in no time!

Let’s hop on our broomsticks and conjure up some shapes.

Generative?

For some folks reading this, generative art may be an unfamiliar topic. If you are already comfortable with generative art/design, feel free to hop down to the next section. If not, here’s a little example:

Imagine, for a moment, that you are sitting at a desk. You have three stamps, some dice, and a piece of paper. Each of the stamps has a different shape on it. There is a square, a line, and a circle. You roll the dice. If the dice land on one, you use the square stamp on the page. If the dice lands on two, you use the line stamp. If it lands on three, you use the circle stamp. If the dice reads four, five, or six, you do nothing. You repeat the roll-and-stamp process until the page fills with shapes — this is generative art!

It can seem a little scary at first, but really, that’s all “generative” means — something created with an element of chance/unpredictability. We define some rules and let a source of randomness guide us to an outcome. In the “analog” example above, the randomness source is some dice. When we are working in the browser, it could be Math.random() or another similar function.

To bring things back to the land of ones and zeros for a moment, this is what the above example would look like if written in code:

CodePen Embed Fallback

Pretty cool, eh? By defining some simple rules and actioning them at random, we have created a unique pattern. In this tutorial series, we will use generative techniques just like this to create exciting user interfaces.

What is the CSS Paint API, and what’s a worklet?

The CSS Paint API allows us low-level access to CSS itself(!) through an HTML5 <canvas>-like drawing API. We can harness this power with something called a worklet.

Worklets, in short, are JavaScript classes. Each worklet class must have a paint() function. A worklet’s paint() function can programmatically create an image for any CSS property that expects one.

For example:

.my-element { background-image: paint(texture); }

Here, we have a fictional texture worklet that generates a beautiful (I’ll leave this up to your imagination), programmatic texture. Where we might usually assign a url(...) value to the background-image property, we instead call paint(worklet_name) — this runs the worklet’s paint() function and renders the output to the target element.

We will be getting into how to write worklets in detail shortly, but I wanted to give you a quick primer on what they are before I start talking about them.

What we are building

So, in this tutorial, we will be building a generative blob worklet. Our worklet will take a few input parameters (as CSS Custom Properties, more on this a little later) and return a beautiful, random blob shape.

Let’s get started by checking out some examples of the finished worklet in action — if a picture paints a thousand words, a CodePen must paint a million, right?

The blob worklet, as a background image

First, here’s a demo of the blob worklet just hanging out on its own, generating a value for the background-image property of an element:

CodePen Embed Fallback

I encourage you to look at the CSS for the above CodePen, change the custom properties, resize the element, and see what happens. See how the shape resizes fluidly and updates when the custom properties change? Don’t worry about understanding how this works right now. At this stage, we are only concerned with what we are building.

Generative image masks, a practical use case

Awesome, now that we have seen the “standalone” worklet, let’s check out how we can use it. In this example, the worklet functions as a generative image mask:

CodePen Embed Fallback

The result (I think) is rather striking. The worklet adds a natural, eye-catching curve to the design. In addition, the mask shape is different each time the page loads, which is a fantastic way to keep the UI fresh and exciting — click “rerun” on the CodePen above to see this effect in action. This ever-changing behavior is subtle, for sure, but I hope it will bring folks who notice it a little bit of joy. The web can be quite a cold, sterile place, and generative touches like this can make it feel a lot more organic!

Note: I’m certainly not suggesting we all start making our entire interfaces change at random. That would be terrible for usability! This kind of behavior works best when applied sparingly and only to presentational elements of your website or app. Think blog post headers, hero images, subtle background patterns, etc.

Now, this is just one example (and simple one, at that), but I hope it gives you some ideas on how you could use the blob worklet in your own design and development. For anyone looking for some extra inspiration, a quick Dribbble search for “blobs” should give you a whole heap of ideas!

Wait, do I need the CSS Paint API to make blobs?

In short, no!

There are, in fact, a plethora of ways to make blobs to use in your UI design. You could reach for a tool like Blobmaker, do some magic with border-radius, use a regular <canvas> element, whatever! There are tons of roads leading to blob city.

None of these, however, are quite the same as using the CSS Paint API. Why?

Well, to name a few reasons…

It allows us to be expressive in our CSS

Instead of dragging around sliders, tweaking radii, or endlessly clicking “regenerate” in the hope that a perfect blob comes our way, we can use just a few human-readable values to get what we need.

For example, the blob worklet we will be building in this tutorial takes the following input properties:

.worklet-target { --blob-seed: 123456; --blob-num-points: 8; --blob-variance: 0.375; --blob-smoothness: 1; --blob-fill: #000; }

Need your blobs to be super subtle and minimal? Reduce the --blob-variance custom property. Need them to be detailed and overstated? Bring it up!

CodePen Embed Fallback

Fancy redesigning your site in a more brutalist direction? No problem! Instead of re-exporting hundreds of assets or custom coding a bunch of border-radius properties, simply reduce the --blob-smoothness custom property to zero:

CodePen Embed Fallback

Handy, eh? The CSS Paint API, through worklets, allows us to create ever-unique UI elements that fit right in with a design system.

Note: I am using GSAP in the examples above to animate the input properties of the paint worklet we are building in this tutorial.

It is super performant

It just so happens that generative work can get a little heavy, computation-wise. We often find ourselves looping through lots of elements, performing calculations, and other fun stuff. When we factor in that we may need to create multiple programmatic, generative visuals on a page, performance issues could become a risk.

Luckily for us, CSS Paint API worklets do all their magic outside of the main browser thread. The main browser thread is where all of the JavaScript we usually write exists and executes. Writing code this way is perfectly OK (and generally preferable), **but it can have limitations. When we try and do too much on the main browser thread, the can UI become sluggish or even blocked.

As worklets run on a different thread to the main website or app, they will not “block” or slow down the interface. Additionally, this means that the browser can spin up lots of separate worklet instances that it can call on when needed — this is similar to containerization and results in blazing fast performance!

It won’t clutter the DOM

Because the CSS Paint API essentially adds an image to a CSS property, it doesn’t add any extra elements to the DOM. To me, this feels like a super clean approach to creating generative visual elements. Your HTML structure remains clear, semantic, and unpolluted, while your CSS handles how things look.

Browser support

It is worth noting that the CSS Paint API is a relatively new technology, and although support is growing, it is still unavailable in some major browsers. Here is a browser support table:

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

DesktopChromeFirefoxIEEdgeSafari65NoNo79NoMobile / TabletAndroid ChromeAndroid FirefoxAndroidiOS Safari92No92No

Although browser support is still a little thin on the ground — in this tutorial, we will be looking at how to use the css-paint-polyfill maintained by GoogleChromeLabs to make sure users in all browsers can enjoy our creations.

We will, additionally, be looking at how to “fail gracefully” when the CSS Paint API is unsupported. A polyfill means extra JavaScript weight, so for some folks, it is not a viable solution. If this is you, don’t worry. We will be exploring browser support options for everyone.

Let’s code!

OK, OK! We know what we are building and why the CSS Paint API rocks — now let’s get coding! First things first, let’s get a development environment spun up.

Note: if you get a little lost at any point during in this tutorial, you can view a finished version of the worklet.

A simple development environment

To get us started, I have created a worklet-starter-kit repository. As a first step, pop on over to GitHub and clone it. Once you have cloned and navigated inside the repo, run:

npm install

Followed by:

npm run start

Once you have run the above commands, a simple development server fires up in the current directory, and your default browser opens. As worklets must be loaded either over HTTPS or from localhost — this setup ensures that we can use our worklet without any CORS issues. The starter kit also handles automatically refreshing the browser when we make any changes.

As well as serving our content and providing a basic live reload, this repository features a simple build step. Powered by esbuild, this process bundles up any JavaScript imports inside our worklet and outputs the result to a worklet.bundle.js file. Any changes made in worklet.js are automatically reflected in worklet.bundle.js.

If you have a poke around the repository, you might notice that there is already some HTML and CSS kicking around. We have a simple index.html file with a single worklet-canvas div, alongside some CSS to center it on the page and scale it to the viewport. Think of this as a blank canvas for all of your worklet experimentation!

Initializing our worklet

OK, now that we have our development environment up and running, it’s time to create our worklet. Let’s start by navigating to the worklet.js file.

Note: Remember, worklet.bundle.js is automatically generated by our build step. We don’t ever want to edit this file directly.

In our worklet.js file, we can define our Blob class and register it with the registerPaint function. We pass two values to registerPaint — the name we would like our worklet to have (in our case, blob) and the class that defines it:

class Blob {} registerPaint("blob", Blob);

Excellent! We just took the first step towards creating our blobs!

Adding a paint() function

Now, not much is happening yet, so let’s add a simple paint() function to our Blob class to check things are working OK:

paint(ctx, geometry, properties) { console.log(`Element size is ${geometry.width}x${geometry.height}`); ctx.fillStyle = "tomato"; ctx.fillRect(0, 0, geometry.width, geometry.height); }

We can think of this paint() function like a callback. It runs, initially, when the worklet’s target element first renders. After this, any time the element’s dimensions change or the worklet’s input properties update, it runs again.

When the paint() function is called, it automatically has a few values passed through it. In this tutorial, we are making use of the first three:

  1. context — a 2D drawing context similar to that of a <canvas> element, we use this to draw things.
  2. geometry — an object containing the width and height of the target element
  3. properties — an array of custom properties

Now that we have a simple paint() function defined, let’s pop over to the index.html file and load our worklet. To do so, we are going to add a new <script> just before our closing </body> tag:

<script> if (CSS["paintWorklet"] !== undefined) { CSS.paintWorklet.addModule("./worklet.bundle.js"); } </script>

Note: we are registering the bundled version of our worklet!

Excellent. Our blob worklet is now loaded and ready for use in our CSS. Let’s use it to generate a background-image for our worklet-canvas class:

.worklet-canvas { background-image: paint(blob); }

Once you have added the above snippet, you should see a red square. Our worklet is alive! Nice work. If you resize the browser window, you should see the worklet-canvas element’s dimensions printed in the browser console. Remember, the paint() function runs whenever the worklet target’s dimensions change.

Defining the worklet’s input properties

To allow our worklet to generate beautiful blobs, we need to help it out and pass it some properties. The properties we need are:

  • --blob-seed — a “seed” value for a pseudorandom number generator; more on this in a moment
  • --blob-num-points — how detailed the blob is based on the number of points used along the shape
  • --blob-variance — how varied the blob’s control points are
  • --blob-smoothness — the smoothness/sharpness of the blob’s edges
  • --blob-fill — the blob’s fill color

Let’s tell our worklet that it will receive these properties and that it needs to watch them for changes. To do so, we can head back over to our Blob class and add an inputProperties getter:

static get inputProperties() { return [ "--blob-seed", "--blob-num-points", "--blob-variance", "--blob-smoothness", "--blob-fill", ]; }

Cool. Now that our worklet knows what input properties to expect, we should add them to our CSS:

.worklet-canvas { --blob-seed: 123456; --blob-num-points: 8; --blob-variance: 0.375; --blob-smoothness: 1; --blob-fill: #000; }

Now, at this point, we could use the CSS Properties and Values API (another member of the Houdini family) **to assign some defaults and make these custom properties a little easier to parse in our worklet. Unfortunately, however, at this moment, the Properties and Values API does not have the best browser support.

For now, to keep things simple, we are going to leave our custom properties as they are — relying on some basic parsing functions in our worklet instead.

Heading back to our worklet class for a moment, let’s add these utility functions:

propToString(prop) { return prop.toString().trim(); } propToNumber(prop) { return parseFloat(prop); }

In the absence of the Properties and Values API, these simple utility functions will help us convert the properties passed to paint() to usable values.

Using our new helper functions, we can parse properties and define some variables to use in our paint() function. Let’s remove the old “debug” code, too:

paint(ctx, geometry, properties) { const seed = this.propToNumber(properties.get("--blob-seed")); const numPoints = this.propToNumber(properties.get("--blob-num-points")); const variance = this.propToNumber(properties.get("--blob-variance")); const smoothness = this.propToNumber(properties.get("--blob-smoothness")); const fill = this.propToString(properties.get("--blob-fill")); }

If you log any of these variables, you should see that the properties made available by the paint() function map exactly to the Custom Properties we defined in our CSS a moment ago.

If you open up dev-tools, inspect the worklet-canvas element, and change any of these custom properties — you should see that the logs re-run and reflect the updated value. Why? Our worklet reacts to any changes to its input properties and re-runs its paint() function when it detects them.

OK, folks, it’s time to start forming our blob shape. To do this, we need a way of generating random numbers. After all, this is what will make our blobs generative!

Now, you may be thinking, “Hey, we can use Math.random() for this!” and in many ways, you would be right on. There is, however, a problem with using a “regular” random number generator in CSS Paint API worklets. Let’s check it out.

The problem with Math.random()

We noticed earlier how a worklet’s paint() function runs rather often. If we use a method such as Math.random() to generate random values within paint() — they will be different each time the function executes. Different random numbers mean a different visual result every time the worklet re-renders. We do not want this at all. Sure, we want our blobs to be random, but only at the point of conception. They shouldn’t change once they exist on the page unless we explicitly tell them to do so.

I found this concept a little tricky to get my head around at first, so I have made a couple of CodePens (best viewed in a browser that natively supports the CSS Paint API) to help demonstrate. In the first example, we have a worklet that sets a random background color, using Math.random():

Warning: resizing the element below will result in a flash of color.

CodePen Embed Fallback

Try resizing the element above and notice how the background color changes as it updates. For some niche applications and fun demos, this might be what you want. In most practical use-cases, though, it isn’t. Aside from being visually jarring, behavior like this could be an accessibility issue for users who are sensitive to motion. Imagine that your worklet contained hundreds of dots that all started flying around and flashing whenever something on the page changed size!

Luckily for us, this issue is quite simple to fix. The solution? A pseudorandom number generator! Pseudorandom number generators (or PRNGs) generate random numbers based on a seed. Given the same seed value, a PRNG always returns the same sequence of random numbers — this is perfect for us, as we can re-initialize the PRNG every time the paint() function runs, ensuring the same sequence of random values!

Here’s a CodePen demonstrating how a PRNG works:

CodePen Embed Fallback

Click “generate” to choose some random numbers — then, click “generate” a few more times. Notice how the sequence of numbers is the same each time you click? Now, try changing the seed value, and repeat this process. The numbers will be different from the previous seed value, but consistent across generations. This is the beauty of a PRNG. Predictable randomness!

Here’s the random-background-color CodePen again, using a PRNG rather than Math.random():

CodePen Embed Fallback

Ah! Much better! The element has a random color set when the page loads, but the background color does not change when it resizes. Perfect! You can test this out by clicking “Rerun” on the CodePen above, and resizing the element.

Adding pseudorandom numbers to our worklet

Let’s go ahead and add a PRNG function above our Blob class definition:

// source: https://github.com/bryc/code/blob/master/jshash/PRNGs.md function mulberry32(a) { return function () { a |= 0; a = (a + 0x6d2b79f5) | 0; var t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; }

Now, I’d be lying if I said I understand quite literally anything this function is doing. I discovered this beautiful little snippet of code through Jake Archibald’s excellent article on being predictably random with the CSS Paint API, and have used it in a ton of work since. You can find the original repository for this function over at GitHub — it includes a whole heap of excellent PRNGs and is certainly worth a look.

Note: while I don’t fully understand how this function works, I know how to use it. Often, when working in the generative world (if you are anything like me, anyway!), you will find yourself in this situation. When you do, don’t worry! It is absolutely OK to use a snippet of code to create some art/design without knowing exactly how it works. We can learn by doing, and that’s awesome.

OK, great, we have a PRNG function. Let’s add it to paint():

const random = mulberry32(seed);

In this snippet, we call mulberry32() with our --blob-seed custom property as its seed value, and it returns a brand new function. This new function — random — returns a random number between zero and one.

Lovely, let’s put our shiny new PRNG to use.

A quick aside: Drawing with the CSS Paint API

When working with CSS Paint API worklets, just like HTML <canvas>, we draw everything inside a 2D context. This context has a width and a height. For worklets, the width and height of this context always matches that of the element the worklet is painting to.

Say, for example, we wanted to add a point to the center of a 1920x1080px context, we could visualize it like so:

As we begin to write our “render” code, this is good to keep in mind.

How our blob is formed, an overview

Before we write any code, I’d like to show you a little SVG animation of how we will create our blob shape. If you are a visual learner like me, you may find an animated reference helpful for understanding this kind of thing:

CodePen Embed Fallback

To break this process down into three steps:

  1. Plot several equally spaced points around the radius of a circle.
  2. Pull each point a random amount towards the center of the circle.
  3. Draw a smooth curve through each of the points.

Now, things are about to get a tiny bit maths-y but don’t worry. We’ve got this!

Defining the blob’s control points

To start, let’s define the radius of our blob. The blob’s radius determines how large or small it is.

We want our blob shape to always “fit” inside the element it is painted on. To ensure this is the case, we check the width and height of the worklet’s target element and set the blob’s radius accordingly. Our blob is essentially a weird circle, and a circle’s total width/height will always be equal to its radius multiplied by two, so we divide this value to match. Let’s add some code to achieve this in our paint() function:

const radius = Math.min(geometry.width, geometry.height) / 2;

Here’s an image to help explain what is happening here:

Cool! Now that we know what the radius of our blob should be, we can initialize its points:

const points = []; const center = { x: geometry.width / 2, y: geometry.height / 2, }; const angleStep = (Math.PI * 2) / numPoints; for (let i = 1; i <= numPoints; i++) { const angle = i * angleStep; const point = { x: center.x + Math.cos(angle) * radius, y: center.y + Math.sin(angle) * radius, }; }

Phew! In this snippet, we “walk” around the circumference of a circle, plopping down some equally spaced points as we go. How does this work?

To start, we define an angleStep variable. The maximum angle between two points on the circumference of a circle is Pi × 2. By dividing Pi × 2 by the number of “points” we would like to create, we have the desired (equally spaced) angle between each point.

Next, we loop over each point. For each of these points, we define an angle variable. This variable is our angleStep multiplied by the point’s index. Given a radius, an angle, and a center point for a circle, we can use Math.cos() and Math.sin() to plot each point.

Note: If you would like to learn a little more about trigonometric functions, I wholeheartedly recommend Michelle Barker’s excellent series!

Now that we have some perfect, beautiful, equally spaced points positioned around the circumference of a circle — we should mess them up. To do so, we can “pull” each one, a random amount, towards the circle’s center.

How can we do this?

First, let’s add a new lerp function (short for linear interpolation) just below where we defined mulberry32:

function lerp(position, target, amt) { return { x: (position.x += (target.x - position.x) * amt), y: (position.y += (target.y - position.y) * amt), }; }

This function takes a start-point, an end-point, and an “amount” value between zero and one. The return value of this function is a new point, placed somewhere between the start and end points.

In our worklet, just below where we define the point variable in our for-loop, we can use this lerp function to “pull” the point towards the center position. We store the modified point in our points array:

points.push(lerp(point, center, variance * random()));

For the linear interpolation amount, we use our --blob-variance property multiplied by a random number generated by random() — as random() always returns a value between zero and one, this amount will always be somewhere between zero, and our --blob-variance number.

Note: A higher --blob-variance will result in crazier blobs, as each point can end up closer to the center.

Drawing the curve

So, we have our blob’s points stored in an array. Right now, though, they aren’t used for anything! For the final step in our blob creation process, we will draw a smooth curve through each of them.

To draw this curve, we are going to use something called a Catmull-Rom spline. A Catmull-Rom spline is, in short, a great way of drawing a smooth Bézier curve through any number of { x, y } points. With a spline, we don’t have to worry about any tricky control point calculation. We pass in an array of points, and get a beautiful, organic curve back. No sweat.

Let’s head over to the start of our worklet.js file and add the following import:

import { spline } from "@georgedoescode/generative-utils";

Then install the package like so:

npm i @georgedoescode/generative-utils

This spline function is quite sizeable and a little complex. For this reason, I have packaged it up and added it to my generative-utils repository, a small collection of handy generative art utilities.

Once we have imported spline — we can use it in our worklet’s paint() function like this:

ctx.fillStyle = fill; ctx.beginPath(); spline(points, smoothness, true, (CMD, data) => { if (CMD === "MOVE") { ctx.moveTo(...data); } else { ctx.bezierCurveTo(...data); } }); ctx.fill();

Note: Place this snippet just after your for-loop!

We pass in our points, --blob-smoothness property, and a flag to let spline know it should return a closed shape. In addition, we use our --blob-fill custom property to set the fill color of the blob. Now, if we take a look at our browser window, we should see something like this!

Hooray! We did it! The spline function has successfully drawn a smooth curve through each of our points, thus making a gorgeous (and random) blob shape. If you would like your blob to be a little less rounded, try reducing the --blob-smoothness property.

Now, all we need to do is add a touch more randomness.

A random, random seed value

Right now, our blob’s PRNG seed is a fixed value. We defined this --blob-seed custom property in our CSS earlier, with a value of 123456 — this is great, but it means that the random numbers generated by random() and, therefore, the blob’s core shape, is always the same.

For some instances, this is ideal. You may not want your blobs to be random! You may want to choose some perfect seed values and use them across your site as part of a semi-generative design system. For other cases, though, you may want your blobs to be random — just like the image mask example I showed you earlier.

How can we do this? Randomize the seed!

Now, this isn’t quite as simple as it might seem. Initially, when I was working on this tutorial, I thought, “Hey, I can initialize the seed value in the Blob class’s constructor!” Unfortunately, though, I was wrong.

Since the browser may spin up multiple instances of a worklet to handle calls to paint() — one of several Blob classes may end up rendering the blob! If we initialize our seed value inside the worklet class, this value will be different across instances, and could lead to the visual “glitching” we discussed earlier.

To test this out, add a constructor function to your Blob class with the following code inside:

constructor() { console.log(`My seed value is ${Math.random()}`); }

Now, check out your browser console, and resize the window. In most cases, you get multiple logs with different random values. This behavior is no good for us; we need our seed value to be constant.

To solve this issue, let’s add a little JavaScript on the main thread. I am popping this in the <script> tag we created earlier:

document .querySelector(".worklet-canvas") .style.setProperty("--blob-seed", Math.random() * 10000);

Excellent! Now when refreshing the browser window, we should see a new blob shape each time.

For our simple demo, this is perfect. In a “real” application, you may want to create a .blob class, target all instances of it on load, and update the seed value of each element. You could also experiment with setting the blob’s variance, number of points, and roundness properties to random values.

For this tutorial, though, that’s it! All we have left to do is make sure our code works OK for users in all browsers, or provide a suitable fallback for when it doesn’t.

Loading a polyfill

By adding a polyfill, our CSS Paint API code will work in all major browsers, with the cost of extra JavaScript weight. Here’s how we can update our CSS.paintWorklet.addModule code to add one to our example:

(async function () { if (CSS["paintWorklet"] === undefined) { await import("https://unpkg.com/css-paint-polyfill"); } CSS.paintWorklet.addModule("./worklet.bundle.js"); })();

Using this snippet, we only load the polyfill if the current browser does not support the CSS Paint API. Nice!

A CSS-based fallback

If extra JavaScript weight isn’t your vibe, that’s cool. I totally get it. Luckily, using @supports, we can define a lightweight, CSS-only fallback for browsers that do not support the CSS Paint API. Here’s how:

.worklet-canvas { background-color: var(--blob-fill); border-radius: 49% 51% 70% 30% / 30% 30% 70% 70%; } @supports (background: paint(blob)) { .worklet-canvas { background-color: transparent; border-radius: 0; background-image: paint(blob); } }

In this snippet, we apply a background-color and a blob-like border-radius (generated by fancy border radius) to the target element. If the CSS Paint API is supported, we remove these values and use our worklet to paint a generative blob shape. Awesome!

The end of the road

Well, folks, we’re all done. To quote the Grateful Dead — what a long, strange trip it’s been!

I know, there’s a lot to take in here. We have covered core generative art concepts, learned all about the CSS Paint API, and made some awesome generative blobs while we were at it. Not bad going at all, I say.

Now that we have learned the basics, though, we are ready to start creating all kinds of generative magic. Keep an eye out for more generative UI design tutorials from me soon, but in the meantime, try and take what we have learned in this tutorial and experiment! I’m sure you have a ton of fantastic ideas.

Until next time, fellow CSS magicians!

The post Conjuring Generative Blobs With The CSS Paint API appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

GitHub Explains the Open Graph Images

Css Tricks - Thu, 07/29/2021 - 9:49am

An explanation of those new GitHub social media images:

[…] our custom Open Graph image service is a little Node.js app that uses the GitHub GraphQL API to collect data, generates some HTML from a template, and pipes it to Puppeteer to “take a screenshot” of that HTML.

Jason Etcovich on The GitHub Blog in “A framework for building Open Graph images”

It’s so satisfying to produce templated images from HTML and CSS. It’s the perfect way to do social media images. If you’re doing it at scale like GitHub, there are a couple of nice tricks in here for speeding it up.

Direct Link to ArticlePermalink

The post GitHub Explains the Open Graph Images appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

So you want to self-publish books and courses on programming

Css Tricks - Thu, 07/29/2021 - 4:54am

John Resig and I recently self-published our book on GraphQL. There are tons of how-tos for self-publishing a book, or even online classes, but very little in the way of why you would want to, or whether it’s even worth your while. I’m going to share my experience and revenue numbers with you in this post, as well as those from others who have self-published material. I’ll go specifically into the pros and cons of self-publishing books and courses in tech.

Revenue

This is probably what you’re most curious about, right? When I originally started working on our book, I sent a book proposal to publishers. But by the time John and I joined forces, we were both set on self-publishing. He had written two popular JavaScript books and a blog post about traditional publishing for programming books, which includes:

  • Programmers aren’t that into reading books! Programming books, by and large, are poor sellers. They rarely sell more than 4,000 copies.
  • He made $7,500 on his first 4,000 copies, where his royalty rate started at 10% for print sales and 20% for digital copies.

On the topic of traditional publishing revenue, Randall Kanna says: “Nothing comes from a tech book. Just the credibility.” A book can make significantly more, but it’s rare. Martin Kleppmann’s book on machine learning was O’Reilly’s second most popular seller in 2019, and he made $478,000 in the first three years (with 108,000 copies sold, a 10% royalty on print sales, and a 25% royalty on digital sales).

The Pragmatic Bookshelf is the outlying publisher when it comes to royalties: it gives authors 50% of gross profit. In their first 10 years operating, 42% of their authors made more than $50,000, and 12% made more than $100,000.

That said, self-publishing has much higher royalty rates:

  • Amazon: 70% (for e-books; 60% minus printing cost for printed books)
  • Leanpub: 80%
  • Gumroad: 96.5% ($10 monthly membership fee)
  • Your own website: 97%

This gives authors the potential to make more money. Discover Meteor was probably the most successful self-published programming book of its time, with around $500,000 in sales (9,000 copies) between 2013 (when they launched) and 2018 (when they made it available for free). The authors Sacha Grief and Tom Coleman put a lot of effort into marketing it (described in their Gumroad case study), and it became the recommended learning resource in the Meteor community. The current best-selling book is Adam Wathan and Steve Schoger’s Refactoring UI, which I believe passed $2 million in 2020! &#x1f911; Their success was also largely due to their ability to market the book, in addition to addressing a significant need for a broad audience (practical user interface design for front-end developers).

That’s books. Looking at publishing video courses, there are a few options:

Like self-published books, self-published courses have a lot of potential. Level Up Tutorials, Kent C. Dodds, and Wes Bos don’t share revenue numbers for their courses, but I’m assuming they have made considerable sums. Wes, for example, has sold his courses to over 140,000 people at the time of writing!

Those are the outliers, of course. The majority of resources out there make significantly less. Take, for example, the self-published books in the GraphQL space that we were entering:

Title (Author)SalesPriceEstimated RevenueProduction Ready GraphQL (Marc-André Giroux)3,000 (this is a guess)$49–$79$147,000–$237,000The Road to GraphQL (Robin Wieruch)2,250$30–$80$67,500–$180,000The GraphQL Guide (John Resig and Loren Sands-Ramshaw)1,000$30–$279$78,000 in sales; $68,000 in sponsorshipsAdvanced GraphQL with Apollo & React (Mandi Wise)200$30–$45$10,000+Fullstack GraphQL (Julian Mayorga)100$39$3,000

So, yes, the potential is big. But it’s not a guarantee.

Self-publishing pros and cons ProsConsPotential for more revenue. You get to set the price, sell different packages, and receive a larger cut.It’s much harder. A publisher does a ton of things for you: editing, gathering feedback from technical reviewers, the build toolchain, translating, an online store, getting it on Amazon and other bookstores, customer service, tracking errata, etc. Doing all of these things yourself takes a lot of time, especially if you also decide to build a Gatsby site to display the text online. &#x1f61c;Flexibility. You get to decide what goes into the book as well as how it’s formatted and sold.No built-in marketing or distribution. The success of your own book completely depends on your ability to market it. This is hard, as you can read in swyx’s notes on the topic. Books published traditionally usually sell more copies.Updates. You have the email addresses of your readers, and can send them updated versions of the book.No print edition. While you can print on demand with some services, like Kindle Direct Publishing, most people don’t put in that effort.

These pros and cons are for books. If you’re wondering about a breakdown of pros and cons specifically for self-published courses, they’re very similar because they face the same opportunities and challenges.

Should I create a book or course?

This is the big question. And while I wish I could give you a definitive answer one way or the other, it’s always going to be the same answer we love to give in tech: it depends.

Why would you want to self-publish a book or course? Here are some reasons:

  • Income: It’s nice to put something out there and have it generate an income as long as it’s available.
  • Positive impact: Creating a digital asset has high potential leverage. For example, if something takes you X amount of resources to create, and you distribute it to a thousand people who each gain Y amount of utility from learning with it, you’ve produced 1000 * Y utility in the world, which can be much larger than X. For more on this, I recommend Kleppmann’s post on the topic.
  • Reputation: Having written a book can help you get a job, gain clients, or simply elevate your reputation. Eve Porcello says publishing boosted her credibility—and as an added benefit, many readers hire her to teach workshops.
  • Knowledge: If you’re like me, you’ll find that you learn much more about the topic you’re writing about than you knew when you started. I know I certainly did—I finally read the GraphQL spec, learned Vue and Android, ran into and solved a ton of bugs, and went through countless blog posts and conference talks. &#x1f604;
  • Enjoyment: Some people enjoy the creative process. I liked learning, building example applications, and writing. In other words, there’s a level of self-fulfillment you can get from the work.

Those are the reasons why you might want to self-publish material. But whether you should actually do it depends on:

  • Your writing ability: You absolutely need to be good at explaining complex concepts in simple terms that are easy for anyone to grasp. That’s a seriously high bar, especially if there’s existing good content on the topic.
  • Your willingness to market: You need to be willing to get the word out, because no one will do it for you (at least at first). That takes some guts. I know there are many of people out there who have a tough time promoting themselves and their work.
  • What it’s worth to you: You’ve seen a lot of the benefits that self-publishing content can offer, but are they worth it to you? Do impact, knowledge, and reputation motivate you? Maybe none of that matters and you’re simply looking for a labor of love. Whatever your motivation is, it’s important. Without it, you could lose steam and wind up with an unfinished project.
  • The opportunity cost: What else would you do with your time and energy if you didn’t self-publish? Is that thing more valuable to you? Is there anything you’d regret missing out on because of this?

For me, while writing a book had an opportunity cost of lower income (compared to doing more consulting work), I’ve made a positive impact, increased my knowledge on a subject I care about, gained reputation, and enjoyed the process. And it also feels great when someone goes out of their way to tell me they’re “blown away” and appreciate reading my book. &#x1f603;&#x1f917;✨

Thanks to Chris Coyier, Geoff Graham, Sacha Greif, Robin Wieruch, Mandi Wise, Sebastian Grebe, Julian Mayorga, and Rachel Lake for providing input for this article.

The post So you want to self-publish books and courses on programming appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Developer-Friendly Passwordless Auth

Css Tricks - Thu, 07/29/2021 - 4:47am

I’d wager to say that most websites that are business-minded have accounts. A way to log into them. Social media sites, eCommerce sites, CMS systems, you name it, having accounts people log into is at the heart of them. So… make it good. That’s what Magic does (great name!).

Have you heard that language used in a sign-in system like “email me a magic link to sign in”? Well, now you know what can power it. But Magic isn’t just that, it’s all types of auth, including social logins and WebAuthn. Magic is a developer SDK that enables passwordless login in all these methods.

Magic is for teams of any size. Upon signing up, you’ll get $85 in credit which covers 10,000 logins, and each login is $0.0085 after that. That kind of pricing makes it extremely affordable for apps of any size. Small apps will have tiny (or no) bill, and by the time you have tens or hundreds of thousands of users, the cost will feel negligible. Especially considering all the time you saved by not rolling auth from scratch.

Why Magic? What does it offer?

Magic appeals to developers because:

  1. Superior developer experience. It’s easy to use and it’s fast to implement.
  2. Metered pricing — only pay for what you need. Also save money by avoiding the technical debt of your own auth.
  3. The ability to adapt to future authentication methods. Auth is always evolving.
  4. Don’t have to to deal with passwords — less security concerns.
  5. Next-gen security infastructure.

I really like all those, but especially #3. I think of it like image CDNs that offer optimization. The world of images is always evolving as well, and a good image CDN will evolve to support the latest formats and optimization techniques without any work on your end. So too with Magic and Auth.

The “J” and the “a” in Jamstack originally referred to “JavaScript” and “APIs”, which is exactly what Magic offers. Magic fits the Jamstack model very nicely. No server? No problem. Even though Magic absolutely has server-side offerings, and Jamstack could use things like cloud functions, you can get auth done entirely client-side if you’d like. Here’s a great (quick!) tutorial on that.

Here’s the most important thing though: Great UX. Users really like it when the auth of an app feels easy and is never a blocker for them using your app. That’s gonna help your conversion rates.

How do you implement Magic?

First, you need an account. I found it satisfying, of course, that they dog food their own auth signup process, giving you a taste for what you can have right away.

From here, you can scaffold an app out super quickly. The great DX continues here as they offer a way to scaffold out a working app right off the bat:

That’s a web-based starter, for which they have docs, examples, and live demos.

I was able to port a demo over to CodePen Projects super quickly. Check it out!

That’s just a client-side web version. The core of it is really this simple:

import { Magic } from 'magic-sdk' const m = new Magic(API_KEY) m.auth.loginWithMagicLink('user@email.address')

They’ve got server-side support for Node, Python, Ruby, PHP and Go. Magic is for apps of any scale, including incredibly security-sensitive apps. For example, you can even use client-side auth but then use AWS services, with their Hardware Security Modules (HSMs) and all.

Magic has SDK’s for React Native, iOS, Android, and of course native web. Then in addition to the email magic link style signup, they have social login support for Google, Facebook, Apple, GitHub, GitLab, Bitbucket, Linkedin, Twitter, and Discord. Phew! That’s a lot of support for a lot of things. Magic has you covered.

While I was plucking away with this and logging in myself, I could see all the action on my dashboard.

No Passwords

It’s notable that with Magic, there are literally no passwords. Magic email link flow means users need no passwords, and with social logins, users only need to be logged into that other service, not remember/save a password unique to your app. That’s the Magic thesis, which they spell out clearly in Passwords Suck:

Using passwords is a nightmare. No one wants to memorize yet another passphrase when our heads are already filled with them. Passwords are a huge vector for security breaches precisely because they place the burden of choosing unique and secure secrets on the user, who just can’t be bothered. We end up having one password for all the important stuff like banking, work, and school, one for the social-medias, and one for all the miscellaneous one-off services we don’t care too much about. The result is that a whopping 59% of people reuse their passwords across services, which means a leak anywhere quickly becomes a liability for the whole web.

Going password-less is good for users and good for the web.

Get Started

I’d encourage you to check it out. You can sign up for free, no credit card required, and if you do that today you’ll get 10,000 free logins on your account to try out. If you love it, and you have fellow industry folks you refer to Magic, you get 3,000 bonus logins — up to 90,000 in total.

Sign Up for Magic

The post Developer-Friendly Passwordless Auth appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Using Absolute Value, Sign, Rounding and Modulo in CSS Today

Css Tricks - Wed, 07/28/2021 - 4:37am

For quite a while now, the CSS spec has included a lot of really useful mathematical functions, such as trigonometric functions (sin(), cos(), tan(), asin(), acos(), atan(), atan2()), exponential functions (pow(), exp(), sqrt(), log(), hypot()), sign-related functions (abs(), sign()) and stepped value functions (round(), mod(), rem()).

However, these are not yet implemented in any browser, so this article is going to show how, using CSS features we already have, we can compute the values that abs(), sign(), round() and mod() should return. And then we’ll see what cool things this allows us to build today.

A few of the things these functions allow us to make.

Note that none of these techniques were ever meant to work in browsers from back in the days when dinosaurs roamed the internet. Some of them even depend on the browser supporting the ability to register custom properties (using @property), which means they’re limited to Chromium for now.

The computed equivalents --abs

We can get this by using the new CSS max() function, which is already implemented in the current versions of all major browsers.

Let’s say we have a custom property, --a. We don’t know whether this is positive or negative and we want to get its absolute value. We do this by picking the maximum between this value and its additive inverse:

--abs: max(var(--a), -1*var(--a));

If --a is positive, this means it’s greater than zero, and multiplying it with -1 gives us a negative number, which is always smaller than zero. That, in turn, is always smaller than the positive --a, so the result returned by max() is equal to var(--a).

If --a is negative, this means it’s smaller than zero, and that multiplying it by -1 gives us a positive number, which is always bigger than zero, which, in turn, is always bigger than the negative --a. So, the result returned by max() is equal to -1*var(--a).

--sign

This is something we can get using the previous section as the sign of a number is that number divided by its absolute value:

--abs: max(var(--a), -1*var(--a)); --sign: calc(var(--a)/var(--abs));

A very important thing to note here is that this only works if --a is unitless, as we cannot divide by a number with a unit inside calc().

Also, if --a is 0, this solution works only if we register --sign (this is only supported in Chromium browsers at this point) with an initial-value of 0:

@property --sign { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ }

This is because --a, being 0, also makes --abs compute to 0 — and dividing by 0 is invalid in CSS calc() — so we need to make sure --sign gets reset to 0 in this situation. Keep in mind that this does not happen if we simply set it to 0 in the CSS prior to setting it to the calc() value and we don’t register it:

--abs: max(var(--a), -1*var(--a)); --sign: 0; /* doesn't help */ --sign: calc(var(--a)/var(--abs));

In practice, I’ve also often used the following version for integers:

--sign: clamp(-1, var(--a), 1);

Here, we’re using a clamp() function. This takes three arguments: a minimum allowed value -1, a preferred value var(--a) and a maximum allowed value, 1. The value returned is the preferred value as long as it’s between the lower and upper bounds and the limit that gets exceeded otherwise.

If --a is a negative integer, this means it’s smaller or equal to -1, the lower bound (or the minimum allowed value) of our clamp() function, so the value returned is -1. If it’s a positive integer, this means it’s greater or equal to 1, the upper bound (or the maximum allowed value) of the clamp() function, so the value returned is 1. And finally, if --a is 0, it’s between the lower and upper limits, so the function returns its value (0 in this case).

This method has the advantage of being simpler without requiring Houdini support. That said, note that it only works for unitless values (comparing a length or an angle value with integers like ±1 is like comparing apples and oranges — it doesn’t work!) that are either exactly 0 or at least as big as 1 in absolute value. For a subunitary value, like -.05, our method above fails, as the value returned is -.05, not -1!

My first thought was that we can extend this technique to subunitary values by introducing a limit value that’s smaller than the smallest non-zero value we know --a can possibly take. For example, let’s say our limit is .000001 — this would allow us to correctly get -1 as the sign for -.05, and 1 as the sign for .0001!

--lim: .000001; --sign: clamp(-1*var(--lim), var(--a), var(--lim));

Temani Afif suggested a simpler version that would multiply --a by a very large number in order to produce a superunitary value.

--sign: clamp(-1, var(--a)*10000, 1);

I eventually settled on dividing --a by the limit value because it just feels a bit more intuitive to see what minimum non-zero value it won’t go below.

--lim: .000001; --sign: clamp(-1, var(--a)/var(--lim), 1); --round (as well as --ceil and --floor)

This is one I was stuck on for a while until I got a clever suggestion for a similar problem from Christian Schaefer. Just like the case of the sign, this only works on unitless values and requires registering the --round variable as an <integer> so that we force rounding on whatever value we set it to:

@property --round { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ } .my-elem { --round: var(--a); }

By extension, we can get --floor and --ceil if we subtract or add .5:

@property --floor { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ } @property --ceil { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ } .my-elem { --floor: calc(var(--a) - .5); --ceil: calc(var(--a) + .5) } --mod

This builds on the --floor technique in order to get an integer quotient, which then allows us to get the modulo value. This means that both our values must be unitless.

@property --floor { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ } .my-elem { --floor: calc(var(--a)/var(--b) - .5); --mod: calc(var(--a) - var(--b)*var(--floor)) } Use cases

What sort of things can we do with the technique? Let’s take a good look at three use cases.

Effortless symmetry in staggered animations (and not only!)

While the absolute value can help us get symmetrical results for a lot of properties, animation-delay and transition-delay are the ones where I’ve been using it the most, so let’s see some examples of that!

We put --n items within a container, each of these items having an index --i. Both --n and --i are variables we pass to the CSS via style attributes.

- let n = 16; .wrap(style=`--n: ${n}`) - for(let i = 0; i < n; i++) .item(style=`--i: ${i}`)

This gives us the following compiled HTML:

<div class='wrap' style='--n: 16'> <div class='item' style='--i: 0'></div> <div class='item' style='--i: 1'></div> <!-- more such items --> </div>

We set a few styles such that the items are laid out in a row and are square with a non-zero edge length:

$r: 2.5vw; .wrap { display: flex; justify-content: space-evenly; } .item { padding: $r; } The result so far.

Now we add two sets of keyframes to animate a scaling transform and a box-shadow. The first set of keyframes, grow, makes our items scale up from nothing at 0% to full size at 50%, after which they stay at their full size until the end. The second set of keyframes, melt, shows us the items having inset box shadows that cover them fully up to the midway point in the animation (at 50%). That’s also when the items reach full size after growing from nothing. Then the spread radius of these inset shadows shrinks until it gets down to nothing at 100%.

$r: 2.5vw; .item { padding: $r; animation: a $t infinite; animation-name: grow, melt; } @keyframes grow { 0% { transform: scale(0); } 50%, 100% { transform: none; } } @keyframes melt { 0%, 50% { box-shadow: inset 0 0 0 $r; } 100% { box-shadow: inset 0 0; } } The base animation (live demo).

Now comes the interesting part! We compute the middle between the index of the first item and that of the last one. This is the arithmetic mean of the two (since our indices are zero-based, the first and last are 0 and n - 1 respectively):

--m: calc(.5*(var(--n) - 1));

We get the absolute value, --abs, of the difference between this middle, --m, and the item index, --i, then use it to compute the animation-delay:

--abs: max(var(--m) - var(--i), var(--i) - var(--m)); animation: a $t calc(var(--abs)/var(--m)*#{$t}) infinite backwards; animation-name: grow, melt;

The absolute value ,--abs, of the difference between the middle, --m, and the item index, --i, can be as small as 0 (for the middle item, if --n is odd) and as big as --m (for the end items). This means dividing it by --m always gives us a value in the [0, 1] interval, which we then multiply with the animation duration $t to ensure every item has a delay between 0s and the animation-duration.

Note that we’ve also set animation-fill-mode to backwards. Since most items will start the animations later, this tells the browser to keep them with the styles in the 0% keyframes until then.

In this particular case, we wouldn’t see any difference without it either because, while the items would be at full size (not scaled to nothing like in the 0% keyframe of the grow animation), they would also have no box-shadow until they start animating. However, in a lot of other cases, it does make a difference and we shouldn’t forget about it.

Another possibility (one that doesn’t involve setting the animation-fill-mode) would be to ensure the animation-delay is always smaller or at most equal to 0 by subtracting a full animation-duration out of it.

--abs: max(var(--m) - var(--i), var(--i) - var(--m)); animation: a $t calc((var(--abs)/var(--m) - 1)*#{$t}) infinite; animation-name: grow, melt;

Both options are valid, and which one you use depends on what you prefer to happen at the very beginning. I generally tend to go for negative delays because they make more sense when recording the looping animation to make a gif like the one below, which illustrates how the animation-delay values are symmetrical with respect to the middle.

The staggered looping animation.

For a visual comparison between the two options, you can rerun the following demo to see what happens at the very beginning.

CodePen Embed Fallback

A fancier example would be the following:

Navigation links sliding up and then back down with a delay proportional to how far they are from the selected one.

Here, each and every one of the --n navigation links and corresponding recipe articles have an index --idx. Whenever a navigation link is hovered or focused, its --idx value is read and set to the current index, --k, on the body. If none of these items is hovered or focused, --k gets set to a value outside the [0, n) interval (e.g. -1).

The absolute value, --abs, of the difference between --k and a link’s index, --idx, can tell us whether that’s the currently selected (hovered or focused) item. If this absolute value is 0, then our item is the currently selected one (i.e. --not-sel is 0 and --sel is 1). If this absolute value is bigger than 0, then our item is not the currently selected one (i.e. --not-sel is 1 and --sel is 0).

Given both --idx and --k are integers, it results that their difference is also an integer. This means the absolute value, --abs, of this difference is either 0 (when the item is selected), or bigger or equal to 1 (when the item is not selected).

When we put all of this into code, this is what we get:

--abs: Max(var(--k) - var(--idx), var(--idx) - var(--k)); --not-sel: Min(1, var(--abs)); --sel: calc(1 - var(--not-sel));

The --sel and --not-sel properties (which are always integers that always add up to 1) determine the size of the navigation links (the width in the wide screen scenario and the height in the narrow screen scenario), whether they’re greyscaled or not and whether or not their text content is hidden. This is something we won’t get into here, as it is outside the scope of this article and I’ve already explained in a lot of detail in a previous one.

What is relevant here is that, when a navigation link is clicked, it slides out of sight (up in the wide screen case, and left in the narrow screen case), followed by all the others around it, each with a transition-delay that depends on how far they are from the one that was clicked (that is, on the absolute value, --abs, of the difference between their index, --idx, and the index of the currently selected item, --k), revealing the corresponding recipe article. These transition-delay values are symmetrical with respect to the currently selected item.

transition: transform 1s calc(var(--abs)*.05s);

The actual transition and delay are actually a bit more complex because more properties than just the transform get animated and, for transform in particular, there’s an additional delay when going back from the recipe article to the navigation links because we wait for the <article> element to disappear before we let the links slide down. But what were’re interested in is that component of the delay that makes the links is closer to the selected one start sliding out of sight before those further away. And that’s computed as above, using the --abs variable.

You can play with the interactive demo below.

CodePen Embed Fallback

Things get even more interesting in 2D, so let’s now make our row a grid!

We start by changing the structure a bit so that we have 8 columns and 8 rows (which means we have 8·8 = 64 items in total on the grid).

- let n = 8; - let m = n*n; style - for(let i = 0; i < n; i++) | .item:nth-child(#{n}n + #{i + 1}) { --i: #{i} } | .item:nth-child(n + #{n*i + 1}) { --j: #{i} } .wrap(style=`--n: ${n}`) - for(let i = 0; i < m; i++) .item

The above Pug code compiles to the following HTML:

<style> .item:nth-child(8n + 1) { --i: 0 } /* items on 1st column */ .item:nth-child(n + 1) { --j: 0 } /* items starting from 1st row */ .item:nth-child(8n + 2) { --i: 1 } /* items on 2nd column */ .item:nth-child(n + 9) { --j: 1 } /* items starting from 2nd row */ /* 6 more such pairs */ </style> <div class='wrap' style='--n: 8'> <div class='item'></div> <div class='item'></div> <!-- 62 more such items --> </div>

Just like the previous case, we compute a middle index, --m, but since we’ve moved from 1D to 2D, we now have two differences in absolute value to compute, one for each of the two dimensions (one for the columns, --abs-i, and one for the rows, --abs-j).

--m: calc(.5*(var(--n) - 1)); --abs-i: max(var(--m) - var(--i), var(--i) - var(--m)); --abs-j: max(var(--m) - var(--j), var(--j) - var(--m));

We use the exact same two sets of @keyframes, but the animation-delay changes a bit, so it depends on both --abs-i and --abs-j. These absolute values can be as small as 0 (for tiles in the dead middle of the columns and rows) and as big as --m (for tiles at the ends of the columns and rows), meaning that the ratio between either of them and --m is always in the [0, 1] interval. This means the sum of these two ratios is always in the [0, 2] interval. If we want to reduce it to the [0, 1] interval, we need to divide it by 2 (or multiply by .5, same thing).

animation-delay: calc(.5*(var(--abs-i)/var(--m) + var(--abs-j)/var(--m))*#{$t});

This gives us delays that are in the [0s, $t] interval. We can take the denominator, var(--m), out of the parenthesis to simplify the above formula a bit:

animation-delay: calc(.5*(var(--abs-i) + var(--abs-j))/var(--m)*#{$t});

Just like the previous case, this makes grid items start animating later the further they are from the middle of the grid. We should use animation-fill-mode: backwards to ensure they stay in the state specified by the 0% keyframes until the delay time has elapsed and they start animating.

Alternatively, we can subtract one animation duration $t from all delays to make sure all grid items have already started their animation when the page loads.

animation-delay: calc((.5*(var(--abs-i) + var(--abs-j))/var(--m) - 1)*#{$t});

This gives us the following result:

The staggered 2D animation (live demo).

Let’s now see a few more interesting examples. We won’t be going into details about the “how” behind them as the symmetrical value technique works exactly the same as for the previous ones and the rest is outside the scope of this article. However, there is a link to a CodePen demo in the caption for each of the examples below, and most of these Pens also come with a recording that shows me coding them from scratch.

In the first example, each grid item is made up of two triangles that shrink down to nothing at opposite ends of the diagonal they meet along and then grow back to full size. Since this is an alternating animation, we let the delays to stretch across two iterations (a normal one and a reversed one), which means we don’t divide the sum of ratios in half anymore and we subtract 2 to ensure every item has a negative delay.

animation: s $t ease-in-out infinite alternate; animation-delay: calc(((var(--abs-i) + var(--abs-j))/var(--m) - 2)*#{$t}); Grid wave: pulsing triangles (live demo)

In the second example, each grid item has a gradient at an angle that animates from 0deg to 1turn. This is possible via Houdini as explained in this article about the state of animating gradients with CSS.

Field wave: cell gradient rotation (live demo)

The third example is very similar, except the animated angle is used by a conic-gradient instead of a linear one and also by the hue of the first stop.

Rainbow hour wave (live demo)

In the fourth example, each grid cell contains seven rainbow dots that oscillate up and down. The oscillation delay has a component that depends on the cell indices in the exact same manner as the previous grids (the only thing that’s different here is the number of columns differs from the number of rows, so we need to compute two middle indices, one along each of the two dimensions) and a component that depends on the dot index, --idx, relative to the number of dots per cell, --n-dots.

--k: calc(var(--idx)/var(--n-dots)); --mi: calc(.5*(var(--n-cols) - 1)); --abs-i: max(var(--mi) - var(--i), var(--i) - var(--mi)); --mj: calc(.5*(var(--n-rows) - 1)); --abs-j: max(var(--mj) - var(--j), var(--j) - var(--mj)); animation-delay: calc((var(--abs-i)/var(--mi) + var(--abs-j)/var(--mj) + var(--k) - 3)*#{$t}); Rainbow dot wave: dot oscillation (live demo)

In the fifth example, the tiles making up the cube faces shrink and move inwards. The animation-delay for the top face is computed exactly as in our first 2D demo.

Breathe into me: neon waterfall (live demo and a previous iteration)

In the sixth example, we have a grid of columns oscillating up and down.

Column wave (live demo)

The animation-delay isn’t the only property we can set to have symmetrical values. We can also do this with the items’ dimensions. In the seventh example below, the tiles are distributed around half a dozen rings starting from the vertical (y) axis and are scaled using a factor that depends on how far they are from the top point of the rings. This is basically the 1D case with the axis curved on a circle.

Circular grid melt (live demo)

The eighth example shows ten arms of baubles that wrap around a big sphere. The size of these baubles depends on how far they are from the poles, the closest ones being the smallest. This is done by computing the middle index, --m, for the dots on an arm and the absolute value, --abs, of the difference between it and the current bauble index, --j, then using the ratio between this absolute value and the middle index to get the sizing factor, --f, which we then use when setting the padding.

--m: calc(.5*(var(--n-dots) - 1)); --abs: max(var(--m) - var(--j), var(--j) - var(--m)); --f: calc(1.05 - var(--abs)/var(--m)); padding: calc(var(--f)*#{$r}); Travel inside the sphere (live demo) Different styles for items before and after a certain (selected or middle) one

Let’s say we have a bunch of radio buttons and labels, with the labels having an index set as a custom property, --i. We want the labels before the selected item to have a green background, the label of the selected item to have a blue background and the rest of the labels to be grey. On the body, we set the index of the currently selected option as another custom property, --k.

- let n = 8; - let k = Math.round((n - 1)*Math.random()); body(style=`--k: ${k}`) - for(let i = 0; i < n; i++) - let id = `r${i}`; input(type='radio' name='r' id=id checked=i===k) label(for=id style=`--i: ${i}`) Option ##{i}

This compiles to the following HTML:

<body style='--k: 1'> <input type='radio' name='r' id='r0'/> <label for='r0' style='--i: 0'>Option #0</label> <input type='radio' name='r' id='r1' checked='checked'/> <label for='r1' style='--i: 1'>Option #1</label> <input type='radio' name='r' id='r2'/> <label for='r2' style='--i: 2'>Option #2</label> <!-- more options --> </body>

We set a few layout and prettifying styles, including a gradient background on the labels that creates three vertical stripes, each occupying a third of the background-size (which, for now, is just the default 100%, the full element width):

$c: #6daa7e, #335f7c, #6a6d6b; body { display: grid; grid-gap: .25em 0; grid-template-columns: repeat(2, max-content); align-items: center; font: 1.25em/ 1.5 ubuntu, trebuchet ms, sans-serif; } label { padding: 0 .25em; background: linear-gradient(90deg, nth($c, 1) 33.333%, nth($c, 2) 0 66.667%, nth($c, 3) 0); color: #fff; cursor: pointer; } The result so far.

From the JavaScript, we update the value of --k whenever we select a different option:

addEventListener('change', e => { let _t = e.target; document.body.style.setProperty('--k', +_t.id.replace('r', '')) })

Now comes the interesting part! For our label elements, we compute the sign, --sgn, of the difference between the label index, --i, and the index of the currently selected option, --k. We then use this --sgn value to compute the background-position when the background-size is set to 300% — that is, three times the label’s width because we may have of three possible backgrounds: one for the case when the label is for an option before the selected one, a second for the case when the label is for the selected option, and a third for the case when the label is for an option after the selected one.

--sgn: clamp(-1, var(--i) - var(--k), 1); background: linear-gradient(90deg, nth($c, 1) 33.333%, nth($c, 2) 0 66.667%, nth($c, 3) 0) calc(50%*(1 + var(--sgn)))/ 300%

If --i is smaller than --k (the case of a label for an option before the selected one), then --sgn is -1 and the background-position computes to 50%*(1 + -1) = 50%*0 = 0%, meaning we only see the first vertical stripe (the green one).

If --i is equal --k (the case of the label for the selected option), then --sgn is 0 and the background-position computes to 50%*(1 + 0) = 50%*1 = 50%, so we only see the vertical stripe in the middle (the blue one).

If --i is greater than --k (the case of a label for an option after the selected one), then --sgn is 1 and the background-position computes to 50%*(1 + 1) = 50%*2 = 100%, meaning we only see the last vertical stripe (the grey one).

CodePen Embed Fallback

A more aesthetically appealing example would be the following navigation where the vertical bar is on the side closest to the selected option and, for the selected one, it spreads across the entire element.

This uses a structure that’s similar to that of the previous demo, with radio inputs and labels for the navigation items. The moving “background” is actually an ::after pseudo-element whose translation value depends on the sign, --sgn. The text is a ::before pseudo-element whose position is supposed to be in the middle of the white area, so its translation value also depends on --sgn.

/* relevant styles */ label { --sgn: clamp(-1, var(--k) - var(--i), 1); &::before { transform: translate(calc(var(--sgn)*-.5*#{$pad})) } &::after { transform: translate(calc(var(--sgn)*(100% - #{$pad}))) } } CodePen Embed Fallback

Let’s now quickly look at a few more demos where computing the sign (and maybe the absolute value as well) comes in handy.

First up, we have a square grid of cells with a radial-gradient whose radius shrinks from covering the entire cell to nothing. This animation has a delay computed as explained in the previous section. What’s new here is that the coordinates of the radial-gradient circle depend on where the cell is positioned with respect to the middle of the grid — that is, on the signs of the differences between the column --i and row --j indices and the middle index, --m.

/* relevant CSS */ $t: 2s; @property --p { syntax: '<length-percentage>'; initial-value: -1px; inherits: false; } .cell { --m: calc(.5*(var(--n) - 1)); --dif-i: calc(var(--m) - var(--i)); --abs-i: max(var(--dif-i), -1*var(--dif-i)); --sgn-i: clamp(-1, var(--dif-i)/.5, 1); --dif-j: calc(var(--m) - var(--j)); --abs-j: max(var(--dif-j), -1*var(--dif-j)); --sgn-j: clamp(-1, var(--dif-j)/.5, 1); background: radial-gradient(circle at calc(50% + 50%*var(--sgn-i)) calc(50% + 50%*var(--sgn-j)), currentcolor var(--p), transparent calc(var(--p) + 1px)) nth($c, 2); animation-delay: calc((.5*(var(--abs-i) + var(--abs-j))/var(--m) - 1)*#{$t}); } @keyframes p { 0% { --p: 100%; } } Sinking feeling (live demo)

Then we have a double spiral of tiny spheres where both the sphere diameter --d and the radial distance --x that contributes to determining the sphere position depend on the absolute value --abs of the difference between each one’s index, --i, and the middle index, --m. The sign, --sgn, of this difference is used to determine the spiral rotation direction. This depends on where each sphere is with respect to the middle – that is, whether its index ,--i, is smaller or bigger than the middle index, --m.

/* relevant styles */ --m: calc(.5*(var(--p) - 1)); --abs: max(calc(var(--m) - var(--i)), calc(var(--i) - var(--m))); --sgn: clamp(-1, var(--i) - var(--m), 1); --d: calc(3px + var(--abs)/var(--p)*#{$d}); /* sphere diameter */ --a: calc(var(--k)*1turn/var(--n-dot)); /* angle used to determine sphere position */ --x: calc(var(--abs)*2*#{$d}/var(--n-dot)); /* how far from spiral axis */ --z: calc((var(--i) - var(--m))*2*#{$d}/var(--n-dot)); /* position with respect to screen plane */ width: var(--d); height: var(--d); transform: /* change rotation direction by changing x axis direction */ scalex(var(--sgn)) rotate(var(--a)) translate3d(var(--x), 0, var(--z)) /* reverse rotation so the sphere is always seen from the front */ rotate(calc(-1*var(--a))); /* reverse scaling so lighting on sphere looks consistent */ scalex(var(--sgn)) No perspective (live demo)

Finally, we have a grid of non-square boxes with a border. These boxes have a mask created using a conic-gradient with an animated start angle, --ang. Whether these boxes are flipped horizontally or vertically depends on where they are with respect to the middle – that is, on the signs of the differences between the column --i and row --j indices and the middle index, --m. The animation-delay depends on the absolute values of these differences and is computed as explained in the previous section. We also have a gooey filter for a nicer “wormy” look, but we won’t be going into that here.

/* relevant CSS */ $t: 1s; @property --ang { syntax: '<angle>'; initial-value: 0deg; inherits: false; } .box { --m: calc(.5*(var(--n) - 1)); --dif-i: calc(var(--i) - var(--m)); --dif-j: calc(var(--j) - var(--m)); --abs-i: max(var(--dif-i), -1*var(--dif-i)); --abs-j: max(var(--dif-j), -1*var(--dif-j)); --sgn-i: clamp(-1, 2*var(--dif-i), 1); --sgn-j: clamp(-1, 2*var(--dif-j), 1); transform: scale(var(--sgn-i), var(--sgn-j)); mask: repeating-conic-gradient(from var(--ang, 0deg), red 0% 12.5%, transparent 0% 50%); animation: ang $t ease-in-out infinite; animation-delay: calc(((var(--abs-i) + var(--abs-j))/var(--n) - 1)*#{$t}); } @keyframes ang { to { --ang: .5turn; } } Consumed by worms (live demo) Time (and not only) formatting

Let’s say we have an element for which we store a number of seconds in a custom property, --val, and we want to display this in a mm:ss format, for example.

We use the floor of the ratio between --val and 60 (the number of seconds in a minute) to get the number of minutes and modulo for the number of seconds past that number of minutes. Then we use a clever little counter trick to display the formatted time in a pseudo-element.

@property --min { syntax: '<integer>'; initial-value: 0; inherits: false; } code { --min: calc(var(--val)/60 - .5); --sec: calc(var(--val) - var(--min)*60); counter-reset: min var(--min) sec var(--sec); &::after { /* so we get the time formatted as 02:09 */ content: counter(min, decimal-leading-zero) ':' counter(sec, decimal-leading-zero); } }

This works in most situations, but we encounter a problem when --val is exactly 0. In this case, 0/60 is 0 and then subtracting .5, we get -.5, which gets rounded to what’s the bigger adjacent integer in absolute value. That is, -1, not 0! This means our result will end up being -01:60, not 00:00!

Fortunately, we have a simple fix and that’s to slightly alter the formula for getting the number of minutes, --min:

--min: max(0, var(--val)/60 - .5);

There are other formatting options too, as illustrated below:

/* shows time formatted as 2:09 */ content: counter(min) ':' counter(sec, decimal-leading-zero); /* shows time formatted as 2m9s */ content: counter(min) 'm' counter(sec) 's';

We can also apply the same technique to format the time as hh:mm:ss (live test).

@property --hrs { syntax: '<integer>'; initial-value: 0; inherits: false; } @property --min { syntax: '<integer>'; initial-value: 0; inherits: false; } code { --hrs: max(0, var(--val)/3600 - .5); --mod: calc(var(--val) - var(--hrs)*3600); --min: max(0, var(--mod)/60 - .5); --sec: calc(var(--mod) - var(--min)*60); counter-reset: hrs var(--hrs) var(--min) sec var(--sec); &::after { /* so we get the time formatted as 00:02:09 */ content: counter(hrs, decimal-leading-zero) ':' counter(min, decimal-leading-zero) ':' counter(sec, decimal-leading-zero); } }

This is a technique I’ve used for styling the output of native range sliders such as the one below.

Styled range input indicating time (live demo)

Time isn’t the only thing we can use this for. Counter values have to be integer values, which means the modulo trick also comes in handy for displaying decimals, as in the second slider seen below.

Styled range inputs, one of which has a decimal output (live demo)

A couple more such examples:

Styled range inputs, one of which has a decimal output (live demo) Styled range inputs, one of which has a decimal output (live demo) Even more use cases

Let’s say we have a volume slider with an icon at each end. Depending on the direction we move the slider’s thumb in, one of the two icons gets highlighted. This is possible by getting the absolute value, --abs, of the difference between each icon’s sign, --sgn-ico (-1 for the one before the slider, and 1 for the one after the slider), and the sign of the difference, --sgn-dir, between the slider’s current value, --val, and its previous value, --prv. If this is 0, then we’re moving in the direction of the current icon so we set its opacity to 1. Otherwise, we’re moving away from the current icon, so we keep its opacity at .15.

This means that, whenever the range input’s value changes, not only do we need to update its current value, --val, on its parent, but we need to update its previous value, which is another custom property, --prv, on the same parent wrapper:

addEventListener('input', e => { let _t = e.target, _p = _t.parentNode; _p.style.setProperty('--prv', +_p.style.getPropertyValue('--val')) _p.style.setProperty('--val', +_t.value) })

The sign of their difference is the sign of the direction, --sgn-dir, we’re going in and the current icon is highlighted if its sign, --sgn-ico, and the sign of the direction we’re going in, --sgn-dir, coincide. That is, if the absolute value, --abs, of their difference is 0 and, at the same time, the parent wrapper is selected (it’s either being hovered or the range input in it has focus).

[role='group'] { --dir: calc(var(--val) - var(--prv)); --sgn-dir: clamp(-1, var(--dir), 1); --sel: 0; /* is the slider focused or hovered? Yes 1/ No 0 */ &:hover, &:focus-within { --sel: 1; } } .ico { --abs: max(var(--sgn-dir) - var(--sgn-ico), var(--sgn-ico) - var(--sgn-dir)); --hlg: calc(var(--sel)*(1 - min(1, var(--abs)))); /* highlight current icon? Yes 1/ No 0 */ opacity: calc(1 - .85*(1 - var(--hlg))); } CodePen Embed Fallback

Another use case is making property values of items on a grid depend on the parity of the sum of horizontal --abs-i and vertical --abs-j distances from the middle, --m. For example, let’s say we do this for the background-color:

@property --floor { syntax: '<integer>'; initial-value: 0; inherits: false; } .cell { --m: calc(.5*(var(--n) - 1)); --abs-i: max(var(--m) - var(--i), var(--i) - var(--m)); --abs-j: max(var(--m) - var(--j), var(--j) - var(--m)); --sum: calc(var(--abs-i) + var(--abs-j)); --floor: max(0, var(--sum)/2 - .5); --mod: calc(var(--sum) - var(--floor)*2); background: hsl(calc(90 + var(--mod)*180), 50%, 65%); } Background depending on parity of sum of horizontal and vertical distances to the middle (live demo)

We can spice things up by using the modulo 2 of the floor of the sum divided by 2:

@property --floor { syntax: '<integer>'; initial-value: 0; inherits: false; } @property --int { syntax: '<integer>'; initial-value: 0; inherits: false; } .cell { --m: calc(.5*(var(--n) - 1)); --abs-i: max(var(--m) - var(--i), var(--i) - var(--m)); --abs-j: max(var(--m) - var(--j), var(--j) - var(--m)); --sum: calc(var(--abs-i) + var(--abs-j)); --floor: max(0, var(--sum)/2 - .5); --int: max(0, var(--floor)/2 - .5); --mod: calc(var(--floor) - var(--int)*2); background: hsl(calc(90 + var(--mod)*180), 50%, 65%); } A more interesting variation of the previous demo (live demo)

We could also make both the direction of a rotation and that of a conic-gradient() depend on the same parity of the sum, --sum, of horizontal --abs-i and vertical --abs-j distances from the middle, --m. This is achieved by horizontally flipping the element if the sum, --sum, is even. In the example below, the rotation and size are also animated via Houdini (they both depend on a custom property, --f, which we register and then animate from 0 to 1), and so are the worm hue, --hue, and the conic-gradient() mask, both animations having a delay computed exactly as in previous examples.

@property --floor { syntax: '<integer>'; initial-value: 0; inherits: false; } .&#x1f41b; { --m: calc(.5*(var(--n) - 1)); --abs-i: max(var(--m) - var(--i), var(--i) - var(--m)); --abs-j: max(var(--m) - var(--j), var(--j) - var(--m)); --sum: calc(var(--abs-i) + var(--abs-j)); --floor: calc(var(--sum)/2 - .5); --mod: calc(var(--sum) - var(--floor)*2); --sgn: calc(2*var(--mod) - 1); /* -1 if --mod is 0; 1 id --mod is 1 */ transform: scalex(var(--sgn)) scale(var(--f)) rotate(calc(var(--f)*180deg)); --hue: calc(var(--sgn)*var(--f)*360); } Grid wave: triangular rainbow worms (live demo).

Finally, another big use case for the techniques explained so far is shading not just convex, but also concave animated 3D shapes using absolutely no JavaScript! This is one topic that’s absolutely massive on its own and explaining everything would take an article as long as this one, so I won’t be going into it at all here. But I have made a few videos where I code a couple of such basic pure CSS 3D shapes (including a wooden star and a differently shaped metallic one) from scratch and you can, of course, also check out the CSS for the following example on CodePen.

Musical toy (live demo)

The post Using Absolute Value, Sign, Rounding and Modulo in CSS Today appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Fonts in the Twilight Zone

Typography - Tue, 07/27/2021 - 3:51pm

Read the book, Typographic Firsts

Among my favorite kinds of typefaces are those that don’t fit neatly into predefined or existing categories; those that dip their toes into more than one genre, or take their cues from disparate historical periods. These twilight zone hybrids aren’t always easy to pull off. Today, I’m featuring an in-between typeface that pulls it off […]

The post Fonts in the Twilight Zone appeared first on I Love Typography.

ES2021 Features

Css Tricks - Tue, 07/27/2021 - 1:18pm

Hemanth HM very succinctly shows off ES2021 features. Gosh it doesn’t feel like that long ago that all we could talk about is ES2015, and now that’s over a half-decade behind us.

There are new things like “arbitrarily chuck underscores in numbers.” I kinda dig that. Like 1_000_000_000 is the same as 1000000000 but more readable. To be honest, I barely even understand the other features.

It’s interesting to observe the JavaScript truck moving forward with new features, while also being around people that write Go a lot and how starkly different, philosophically, it seems to me. Like there is only one kind of loop in Go, a for loop, and that’s it, while JavaScript has a bunch of them—four just for Arrays! Go doesn’t add syntactic sugar on purpose, while JavaScript feels addicted to it. Sugar is a helluva drug.

Direct Link to ArticlePermalink

The post ES2021 Features appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS Logical Properties and Values

Css Tricks - Tue, 07/27/2021 - 5:02am

Now that cross-browser support is at a tipping point, it’s a good time to take a look at logical properties and values. If you’re creating a website in multiple languages, logical properties and values are incredibly useful. Even if you’re not, there are still some convenient new shorthands it’s worth knowing about.

For example, I’ve lost count of the amount of times I’ve written this to center something:

.thing { margin-left: auto; margin-right: auto; }

We could make it a one-liner with something like margin: 0 auto; but then the top and bottom margins get thrown into the mix. Instead, we can select just the left and right margin with the margin-inline logical property.

CodePen Embed Fallback Start thinking of things as “inline” or “block”

That last demo is pretty neat, right? The margin-inline property sets both margin-left and margin-right. Similarly, the margin-block property sets both margin-top and margin-bottom. And we’re not only talking margins. Logical properties has similar shorthands to set border and padding. So if you have a visual design that calls for borders only on the sides, you can just use border-inline instead of fussing with each physical direction on its own.

CodePen Embed Fallback Rather than thinking in physical terms, like left and right, we can think of an “inline” direction and a “block” direction.

So, as we move ahead, we now know that we’re dealing with inline and block directions instead of physical directions. Inline handles the left and right directions, while block manages top and bottom.

That is, until things get swapped around when the writing-mode changes.

Pay attention to direction and writing mode

What we’ve seen so far are examples of CSS logical properties. These are versions of CSS properties were used to like margin and padding, but written in a new way that forgoes physical directions (i.e. left, right, top, and bottom).

CSS was developed with the English language in mind and English is written and read from left-to-right. That’s not the case for all languages though. Arabic, for example, is read from right-to-left. That’s why HTML has a dir attribute.

<html dir="rtl">

CSS has an equivalent property (although it’s recommended to use the HTML attribute just in case the CSS fails to load):

.foreign-language { direction: rtl; } Credit: Ahmad Shadeed

Chinese, Japanese, Korean and Mongolian can be written either horizontally from left-to-right, or vertically from top to bottom. The majority of websites in these languages are written horizontally, the same as with English.

Comparatively, vertical writing is more common on Japanese websites. Some sites use a mixture of both vertical and horizontal text.

baroku.co.jp

When written vertically, Chinese, Japanese and Korean are written with the top-right as a starting point, whereas Mongolian reads from left to right. This is exactly why we have the writing-mode property in CSS, which includes the following values:

  • horizontal-tb: This is the default value, setting the the direction left-to-right for languages like English or French, and right-to-left languages like Arabic. The tb stands for “top-to-bottom.”
  • vertical-rl: This changes the direction to right-to-left in a vertical orientation for languages like Chinese, Japanese and Korean.
  • vertical-lr: This is used for vertical left-to-right languages, like Mongolian.

CSS logical properties offer a way to write CSS that is contextual. When using logical properties, spacing and layout are dependent on both the writing-mode and direction (whether set by either CSS or HTML). It therefore becomes possible to reuse CSS styles across different languages. BBC News, for example, rebuild their website in over a dozen languages. That’s a better experience than leaving users to rely on autotranslate. It also means they can better cater specific content to different parts of the world. The visual styling though, remains much the same across regions.

bbc.com/arabic

Let’s look at the example below to see the shortcomings of physical properties. Using the physical margin-left property (shown in red), everything looks good in English. If you were to reuse the CSS but change the writing mode to rtl (shown at the bottom) there’s no space between the text and the icon and there’s excess white space on the left of the text. We can avoid this by using a logical property instead.

What makes logical properties and values so useful is that they will automatically cater to the context of the language. In a left-to-right language like English, margin-inline-start will set the left-side margin. For a right-to-left language like Arabic, Urdu, or Hebrew, it will set the right-hand margin — which solves the layout problem in the above example. That’s right-to-left taken care of. If you have vertical text, margin-inline-start will cater to that context to, adding the margin at the top, which is where you would start reading from in any vertical language (that’s why it’s called margin-inline-start — just think about which direction you start reading from). The direction of inline changes based on the element’s writing-mode. When a vertical writing-mode is set, it handles the vertical direction top and bottom. See how things can get switched around?

An example of the writing direction in Mongolian. (Credit: W3C) A complete list of logical properties and values

There are dozens of CSS properties that have a logical alternative syntax. Adrian Roselli has a handy visualization where you can toggle between the physical CSS properties that we’re all used to and their logical property equivalents. It’s a nice way to visualize logical properties and the physical properties they map to when the direction is ltr and the writing-mode is horizontal-tb.

CodePen Embed Fallback

Let’s break all of those down even further and map each and every physical CSS property to its logical companion, side-by-side. The tables shown throughout this article show traditional physical CSS in the left column and their logical equivalents (using a left-to-right horizontal mapping) in the right column. Remember though, the whole point of logical properties is that they change based on context!

Sizing

In a horizontal writing mode, inline-size sets the width of an element, while block-size sets the height. In a vertical writing mode, the opposite is true: inline-size sets the height and block-size sets the width.

CodePen Embed Fallback Physical propertyLogical propertywidthinline-sizemax-widthmax-inline-sizemin-widthmin-inline-sizeheightblock-sizemax-heightmax-block-sizemin-heightmin-block-size

Logical properties for sizing have good cross-browser support.

Borders

Everything here has solid cross-browser support among modern browsers.

Physical propertyLogical propertyborder-topborder-block-startborder-bottomborder-block-endborder-leftborder-inline-startborder-rightborder-inline-end

Here’s an example of using border-inline-start shown with English, Arabic, and Chinese.

CodePen Embed Fallback

Here’s an example that sets border-block-start dotted and border-block-end dashed:

CodePen Embed Fallback

There are also logical properties for setting the border color, width, and style individually:

Physical propertyLogical propertyborder-top-colorborder-block-start-colorborder-top-widthborder-block-start-widthborder-top-styleborder-block-start-style

So, again, it’s about thinking in terms of “inline” and “block” instead of physical directions, like left and top. We also have shorthand logical properties for borders:

Physical propertyLogical propertyborder-top and border-bottomborder-blockborder-left and border-rightborder-inline Margin

Here are all the individual logical margin properties:

Physical propertyLogical propertymargin-topmargin-block-startmargin-bottommargin-block-endmargin-leftmargin-inline-startmargin-rightmargin-inline-end

These logical properties has comprehensive modern cross-browser support, including Samsung Internet, and has been supported in Safari since 12.2.

And, remember, we have the shorthands as well:

Physical propertyLogical propertymargin-top and margin-bottommargin-blockmargin-left and margin-rightmargin-inline Padding

Padding is super similar to margin. Replace margin with padding and we’ve got the same list of properties.

Physical propertyLogical propertypadding-toppadding-block-startpadding-bottompadding-block-endpadding-leftpadding-inline-startpadding-rightpadding-inline-endpadding-top and padding-bottompadding-blockpadding-left and padding-rightpadding-inline

Just like margins, logical properties for padding have good cross-browser support.

Positioning

Need to offset an element’s position in a certain direction? We can declare those logically, too.

Physical propertyLogical propertytopinset-block-startbottominset-block-endleftinset-inline-startrightinset-inline-endtop and bottominset-blockleft and rightinset-inline

In a horizontal writing mode (either left-to-right, or right-to-left) inset-block-start is equivalent to setting top, and inset-block-end is equivalent to setting bottom. In a horizontal writing mode, with a left-to-right direction, inset-inline-start is equivalent to left, while inset-inline-end is equivalent to right, and vice-versa for right-to-left languages.

Conversely, for a vertical writing mode, inset-inline-start is equivalent to top while inset-inline-end is equivalent to bottom. If writing-mode is set to vertical-rl, inset-block-start is equivalent to right and inset-block-end is equivalent to left. If the writing-mode is set to vertical-lr, the opposite is the case and so inset-block-start is equivalent to left.

Logical propertyWriting modeEquivalent to:inset-block-startHorizontal LTRtopinset-block-startHorizontal RTLtopinset-block-startVertical LTRleftinset-block-startVertical RTLright

Here’s an example of how the same CSS code for absolute positioning looks in each of the four different writing directions:

CodePen Embed Fallback

Logical properties for positioning are supported in all modern browsers, but only recently landed in Safari.

There’s also a new shorthand for setting all four offsets in one line of code. Here’s an example using inset as a shorthand for setting top, bottom, left, and right in one fell swoop to create a full-page overlay:

CodePen Embed Fallback

I’ve heard inset incorrectly referred to as a logical property. But, a quick look in DevTools shows that it is actually a shorthand for physical values, not logical properties:

What it’s actually doing is defining physical offsets (i.e. left, right, top and bottom) rather than logical ones (i.e. inline, block, start and end). Obviously if you want to set the same value for all four sides, as in the example above, it doesn’t matter.

inset: 10px 20px 5px 8px; /* shorthand for physical properties not logical properties */ Text alignment

Logical values for text alignment enjoy great browser support and have for many years. When working in English, text-align: start is the same as text-align: left, while text-align: end is the same as text-align: right. If you set the dir attribute to rtl, they switch and text-align: start aligns text to the right.

Physical valueWriting modeEquivalent to:startLTRleftstartRTLrightendLTRrightendRTLleft Border radius

So far everything we’ve looked at has decent browser support. However, there are some other logical properties where support is still a work in progress, and border radius is one of them. In other words, we can set a different border-radius value for different corners of an element using logical properties, but browser support isn’t great.

Physical propertyLogical propertyborder-top-left-radiusborder-start-start-radiusborder-top-right-radiusborder-start-end-radiusborder-bottom-left-radiusborder-end-start-radiusborder-bottom-right-radiusborder-end-end-radius CodePen Embed Fallback

It’s worth noting that the spec doesn’t include shorthand properties, like border-start-radius and border-end-radius. But, like I said, we’re still in early days here, so that might be a space to watch.

Floats

Flow-relative values for logical floats have terrible browser support at the time I’m writing this. Only Firefox supports inline-start and inline-end as float values.

Physical valueLogical valuefloat: leftfloat: inline-startfloat: rightfloat: inline-endclear: leftclear: inline-startclear: rightclear: inline-end CodePen Embed Fallback Other logical properties

There are proposed logical properties for overflow and resize, but they currently have horrendous browser support.

PhysicalLogicalresize: verticalresize: blockresize: horizontalresize: inlineoverflow-yoverflow-blockoverflow-xoverflow-inline Digging deeper

We explored what it means for a property to be considered “logical” and then mapped out all of the new logical properties and values to their physical counterparts. That’s great! But if you want to go even deeper into CSS Logical Properties and Values, there are a number of resources worth checking out.

  • “RTL Styling 101” (Ahmad Shadeed): A great resource if you’re dealing with Arabic or other right-to-left languages. Ahmad covers everything, from logical properties to considerations when working with specific layout techniques, like flexbox and grid.
  • text-combine-upright (CSS-Tricks): If you’re dealing with vertical text, did you know that this property can rotate text and squeeze multiple characters into the space of a single character? It’s a nice touch of refinement in specific situations where some characters need to go together but still flow with a vertical writing mode.

If you want to view some nice real-world examples of vertical typography from across the web, take a look at the Web Awards for Horizontal and Vertical Writings. There’s a lot of great stuff in there.

Wrapping up

Do you need to rush and swap all of the physical properties out of your codebase? Nope. But it also doesn’t hurt to start using logical properties and values in your work. As we’ve seen, browser support is pretty much there. And even if you’re working on a site that’s just in English, there’s no reason to not use them.

The post CSS Logical Properties and Values appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

7 Commandments of Good UI Design

Usability Geek - Tue, 07/27/2021 - 2:44am
Imagine walking into the most beautiful building in the world—mosaic on every square inch of the place, stained-glass—the whole shebang. You walk into the first door, it’s a push one. Still...
Categories: Web Standards

An Interview with Ulrike Rausch

Typography - Mon, 07/26/2021 - 4:57pm

Read the book, Typographic Firsts

Ulrike Rausch is an author and award-winning type designer based in Berlin. She’s also the designer of one of my all-time favorite handwriting typefaces, the beautiful and authentic, LiebeHeide.

The post An Interview with Ulrike Rausch appeared first on I Love Typography.

Demystifying styled-components

Css Tricks - Mon, 07/26/2021 - 2:17pm

 Joshua Comeau digs into how styled-components works by re-building the basics. A fun and useful journey.

styled-components seems like the biggest player in the CSS-in-React market. Despite being in that world, I haven’t yet been fully compelled by it. I’m a big fan of the basics: scoped styles by way of unique class names. I also like that it works with hot module reloading as it all happens in JavaScript. But I get those through css-modules, and I like the file-separation and Sass support I get through css-modules. There are a few things I’m starting to come around on though (a little):

  • Even with css-modules, you still have to think of names. Even if it’s just like .root or whatever. With styled-components you attach the styles right to the component and don’t really name anything.
  • With css-modules, you’re applying the styles directly to an HTML element only. With styled-components you can apply the styles to custom components and it will slap the styles on by way of spreading props later.
  • Because the styles are literally in the JavaScript files, you get JavaScript stuff you can use—ternaries, prop access, fancy math, etc.

Direct Link to ArticlePermalink

The post Demystifying styled-components appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

How I Built a Cross-Platform Desktop Application with Svelte, Redis, and Rust

Css Tricks - Mon, 07/26/2021 - 4:36am

At Cloudflare, we have a great product called Workers KV which is a key-value storage layer that replicates globally. It can handle millions of keys, each of which is accessible from within a Worker script at exceptionally low latencies, no matter where in the world a request is received. Workers KV is amazing — and so is its pricing, which includes a generous free tier.

However, as a long-time user of the Cloudflare lineup, I have found one thing missing: local introspection. With thousands, and sometimes hundreds of thousands of keys in my applications, I’d often wish there was a way to query all my data, sort it, or just take a look to see what’s actually there.

Well, recently, I was lucky enough to join Cloudflare! Even more so, I joined just before the quarter’s “Quick Wins Week” — aka, their week-long hackathon. And given that I hadn’t been around long enough to accumulate a backlog (yet), you best believe I jumped on the opportunity to fulfill my own wish.

So, with the intro out of the way, let me tell you how I built Workers KV GUI, a cross-platform desktop application using Svelte, Redis, and Rust.

The front-end application

As a web developer, this was the familiar part. I’m tempted to call this the “easy part” but, given that you can use any and all HTML, CSS, and JavaScript frameworks, libraries, or patterns, choice paralysis can easily set in… which might be familiar, too. If you have a favorite front-end stack, great, use that! For this application, I chose to use Svelte because, for me, it certainly makes and keeps things easy.

Also, as web developers, we expect to bring all our tooling with us. You certainly can! Again, this phase of the project is no different than your typical web application development cycle. You can expect to run yarn dev (or some variant) as your main command and feel at home. Keeping with an “easy” theme, I’ve elected to use SvelteKit, which is Svelte’s official framework and toolkit for building applications. It includes an optimized build system, a great developer experience (including HMR!), a filesystem-based router, and all that Svelte itself has to offer.

As a framework, especially one that takes care of its own tooling, SvelteKit allowed me to purely think about my application and its requirements. In fact, as far as configuration is concerned, the only thing I had to do was tell SvelteKit that I wanted to build a single-page application (SPA) that only runs in the client. In other words, I had to explicitly opt out of SvelteKit’s assumption that I wanted a server, which is actually a fair assumption to make since most applications can benefit from server-side rendering. This was as easy as attaching the @sveltejs/adapter-static package, which is a configuration preset made exactly for this purpose. After installing, this was my entire configuration file:

// svelte.config.js import preprocess from 'svelte-preprocess'; import adapter from '@sveltejs/adapter-static'; /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: preprocess(), kit: { adapter: adapter({ fallback: 'index.html' }), files: { template: 'src/index.html' } }, }; export default config;

The index.html changes are a personal preference. SvelteKit uses app.html as a default base template, but old habits die hard.

It’s only been a few minutes, and my toolchain already knows it’s building a SPA, that there’s a router in place, and a development server is at the ready. Plus, TypeScript, PostCSS, and/or Sass support is there if I want it (and I do), thanks to svelte-preprocess. Ready to rumble!

The application needed two views:

  1. a screen to enter connection details (the default/welcome/home page)
  2. a screen to actually view your data

In the SvelteKit world, this translates to two “routes” and SvelteKit dictates that these should exist as src/routes/index.svelte for the home page and src/routes/viewer.svelte for the data viewer page. In a true web application, this second route would map to the /viewer URL. While this is still the case, I know that my desktop application won’t have a navigation bar, which means that the URL won’t be visible… which means that it doesn’t matter what I call this route, as long as it makes sense to me.

The contents of these files are mostly irrelevant, at least for this article. For those curious, the entire project is open source and if you’re looking for a Svelte or SvelteKit example, I welcome you to take a look. At the risk of sounding like a broken record, the point here is that I’m building a regular web app.

At this time, I’m just designing my views and throwing around fake, hard-coded data until I have something that seems to work. I hung out here for about two days, until everything looked nice and all interactivity (button clicks, form submissions, etc.) got fleshed out. I’d call this a “working” app, or a mockup.

Desktop application tooling

At this point, a fully functional SPA exists. It operates — and was developed — in a web browser. Perhaps counterintuitively, this makes it the perfect candidate to become a desktop application! But how?

You may have heard of Electron. It’s the most well-known tool for building cross-platform desktop applications with web technologies. There are a number of massively popular and successful applications built with it: Visual Studio Code, WhatsApp, Atom, and Slack, to name a few. It works by bundling your web assets with its own Chromium installation and its own Node.js runtime. In other words, when you’re installing an Electron-based application, it’s coming with an extra Chrome browser and an entire programming language (Node.js). These are embedded within the application contents and there’s no avoiding them, as these are dependencies for the application, guaranteeing that it runs consistently everywhere. As you might imagine, there’s a bit of a trade-off with this approach — applications are fairly massive (i.e. more than 100MB) and use lots of system resources to operate. In order to use the application, an entirely new/separate Chrome is running in the background — not quite the same as opening a new tab.

Luckily, there are a few alternatives — I evaluated Svelte NodeGui and Tauri. Both choices offered significant application size and utilization savings by relying on native renderers the operating system offers, instead of embedding a copy of Chrome to do the same work. NodeGui does this by relying on Qt, which is another Desktop/GUI application framework that compiles to native views. However, in order to do this, NodeGui requires some adjustments to your application code in order for it to translate your components into Qt components. While I’m sure this certainly would have worked, I wasn’t interested in this solution because I wanted to use exactly what I already knew, without requiring any adjustments to my Svelte files. By contrast, Tauri achieves its savings by wrapping the operating system’s native webviewer — for example, Cocoa/WebKit on macOS, gtk-webkit2 on Linux, and Webkit via Edge on Windows. Webviewers are effectively browsers, which Tauri uses because they already exist on your system, and this means that our applications can remain pure web development products.

With these savings, the bare minimum Tauri application is less than 4MB, with average applications weighing less than 20MB. In my testing, the bare minimum NodeGui application weighed about 16MB. A bare minimum Electron app is easily 120MB.

Needless to say, I went with Tauri. By following the Tauri Integration guide, I added the @tauri-apps/cli package to my devDependencies and initialized the project:

yarn add --dev @tauri-apps/cli yarn tauri init

This creates a src-tauri directory alongside the src directory (where the Svelte application lives). This is where all Tauri-specific files live, which is nice for organization.

I had never built a Tauri application before, but after looking at its configuration documentation, I was able to keep most of the defaults — aside from items like the package.productName and windows.title values, of course. Really, the only changes I needed to make were to the build config, which had to align with SvelteKit for development and output information:

// src-tauri/tauri.conf.json { "package": { "version": "0.0.0", "productName": "Workers KV" }, "build": { "distDir": "../build", "devPath": "http://localhost:3000", "beforeDevCommand": "yarn svelte-kit dev", "beforeBuildCommand": "yarn svelte-kit build" }, // ... }

The distDir relates to where the built production-ready assets are located. This value is resolved from the tauri.conf.json file location, hence the ../ prefix.

The devPath is the URL to proxy during development. By default, SvelteKit spawns a devserver on port 3000 (configurable, of course). I had been visiting the localhost:3000 address in my browser during the first phase, so this is no different.

Finally, Tauri has its own dev and build commands. In order to avoid the hassle of juggling multiple commands or build scripts, Tauri provides the beforeDevCommand and beforeBuildCommand hooks which allow you to run any command before the tauri command runs. This is a subtle but strong convenience!

The SvelteKit CLI is accessed through the svelte-kit binary name. Writing yarn svelte-kit build, for example, tells yarn to fetch its local svelte-kit binary, which was installed via a devDependency, and then tells SvelteKit to run its build command.

With this in place, my root-level package.json contained the following scripts:

{ "private": true, "type": "module", "scripts": { "dev": "tauri dev", "build": "tauri build", "prebuild": "premove build", "preview": "svelte-kit preview", "tauri": "tauri" }, // ... "devDependencies": { "@sveltejs/adapter-static": "1.0.0-next.9", "@sveltejs/kit": "1.0.0-next.109", "@tauri-apps/api": "1.0.0-beta.1", "@tauri-apps/cli": "1.0.0-beta.2", "premove": "3.0.1", "svelte": "3.38.2", "svelte-preprocess": "4.7.3", "tslib": "2.2.0", "typescript": "4.2.4" } }

After integration, my production command was still yarn build, which invokes tauri build to actually bundle the desktop application, but only after yarn svelte-kit build has completed successfully (via the beforeBuildCommand option). And my development command remained yarn dev which spawns the tauri dev and yarn svelte-kit dev commands to run in parallel. The development workflow is entirely within the Tauri application, which is now proxying localhost:3000, allowing me to still reap the benefits of a HMR development server.

Important: Tauri is still in beta at the time of this writing. That said, it feels very stable and well-planned. I have no affiliation with the project, but it seems like Tauri 1.0 may enter a stable release sooner rather than later. I found the Tauri Discord to be very active and helpful, including replies from the Tauri maintainers! They even entertained some of my noob Rust questions throughout the process. :)

Connecting to Redis

At this point, it’s Wednesday afternoon of Quick Wins week, and — to be honest — I’m starting to get nervous about finishing before the team presentation on Friday. Why? Because I’m already halfway through the week, and even though I have a good-looking SPA inside a working desktop application, it still doesn’t do anything. I’ve been looking at the same fake data all week.

You may be thinking that because I have access to a webview, I can use fetch() to make some authenticated REST API calls for the Workers KV data I want and dump it all into localStorage or an IndexedDB table… You’re 100% right! However, that’s not exactly what I had in mind for my desktop application’s use case.

Saving all the data into some kind of in-browser storage is totally viable, but it saves it locally to your machine. This means that if you have team members trying to do the same thing, everyone will have to fetch and save all the data on their own machines, too. Ideally, this Workers KV application should have the option to connect to and sync with an external database. That way, when working in team settings, everyone can tune into the same data cache to save time — and a couple bucks. This starts to matter when dealing with millions of keys which, as mentioned, is not uncommon with Workers KV.

Having thought about it for a bit, I decided to use Redis as my backing store because it also is a key-value store. This was great because Redis already treats keys as a first-class citizen and offers the sorting and filtering behaviors I wanted (aka, I can pass along the work instead of implementing it myself!). And then, of course, Redis is easy to install and run either locally or in a container, and there are many hosted-Redis-as-service providers out there if someone chooses to go that route.

But, how do I connect to it? My app is basically a browser tab running Svelte, right? Yes — but also so much more than that.

You see, part of Electron’s success is that, yes, it guarantees a web app is presented well on every operating system, but it also brings along a Node.js runtime. As a web developer, this was a lot like including a back-end API directly inside my client. Basically the “…but it works on my machine” problem went away because all of the users were (unknowingly) running the exact same localhost setup. Through the Node.js layer, you could interact with the filesystem, run servers on multiple ports, or include a bunch of node_modules to — and I’m just spit-balling here — connect to a Redis instance. Powerful stuff.

We don’t lose this superpower because we’re using Tauri! It’s the same, but slightly different.

Instead of including a Node.js runtime, Tauri applications are built with Rust, a low-level systems language. This is how Tauri itself interacts with the operating system and “borrows” its native webviewer. All of the Tauri toolkit is compiled (via Rust), which allows the built application to remain small and efficient. However, this also means that we, the application developers, can include any additional crates — the “npm module” equivalent — into the built application. And, of course, there’s an aptly named redis crate that, as a Redis client driver, allows the Workers KV GUI to connect to any Redis instance.

In Rust, the Cargo.toml file is similar to our package.json file. This is where dependencies and metadata are defined. In a Tauri setting, this is located at src-tauri/Cargo.toml because, again, everything related to Tauri is found in this directory. Cargo also has a concept of “feature flags” defined at the dependency level. (The closest analogy I can come up with is using npm to access a module’s internals or import a named submodule, though it’s not quite the same still since, in Rust, feature flags affect how the package is built.)

# src-tauri/Cargo.toml [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.0.0-beta.1", features = ["api-all", "menu"] } redis = { version = "0.20", features = ["tokio-native-tls-comp"] }

The above defines the redis crate as a dependency and opts into the "tokio-native-tls-comp" feature, which the documentation says is required for TLS support.

Okay, so I finally had everything I needed. Before Wednesday ended, I had to get my Svelte to talk to my Redis. After poking around a bit, I noticed that all the important stuff seemed to be happening inside the src-tauri/main.rs file. I took note of the #[command] macro, which I knew I had seen before in a Tauri example earlier in the day, so I studied copied the example file in sections, seeing which errors came and went according to the Rust compiler.

Eventually, the Tauri application was able to run again, and I learned that the #[command] macro is wrapping the underlying function in a way so that it can receive “context” values, if you choose to use them, and receive pre-parsed argument values. Also, as a language, Rust does a lot of type casting. For example:

use tauri::{command}; #[command] fn greet(name: String, age: u8) { println!("Hello {}, {} year-old human!", name, age); }

This creates a greet command which, when run,expects two arguments: name and age. When defined, the name value is a string value and age is a u8 data type — aka, an integer. However, if either are missing, Tauri throws an error because the command definition does not say anything is allowed to be optional.

To actually connect a Tauri command to the application, it has to be defined as part of the tauri::Builder composition, found within the main function.

use tauri::{command}; #[command] fn greet(name: String, age: u8) { println!("Hello {}, {} year-old human!", name, age); } fn main() { // start composing a new Builder chain tauri::Builder::default() // assign our generated "handler" to the chain .invoke_handler( // piece together application logic tauri::generate_handler![ greet, // attach the command ] ) // start/initialize the application .run( // put it all together tauri::generate_context!() ) // print <message> if error while running .expect("error while running tauri application"); }

The Tauri application compiles and is aware of the fact that it owns a “greet” command. It’s also already controlling a webview (which we’ve discussed) but in doing so, it acts as a bridge between the front end (the webview contents) and the back end, which consists of the Tauri APIs and any additional code we’ve written, like the greet command. Tauri allows us to send messages across this bridge so that the two worlds can communicate with one another.

The developer is responsible for webview contents and may optionally include custom Rust modules and/or define custom commands. Tauri controls the webviewer and the event bridge, including all message serialization and deserialization.

This “bridge” can be accessed by the front end by importing functionality from any of the (already included) @tauri-apps packages, or by relying on the window.__TAURI__ global, which is available to the entire client-side application. Specifically, we’re interested in the invoke command, which takes a command name and a set of arguments. If there are any arguments, they must be defined as an object where the keys match the parameter names our Rust function expects.

In the Svelte layer, this means that we can do something like this in order to call the greet command, defined in the Rust layer:

<!-- Greeter.svelte --> <script> function onclick() { __TAURI__.invoke('greet', { name: 'Alice', age: 32 }); } </script> <button on:click={onclick}>Click Me</button>

When this button is clicked, our terminal window (wherever the tauri dev command is running) prints:

Hello Alice, 32 year-old human!

Again, this happens because of the println! function, which is effectively console.log for Rust, that the greet command used. It appears in the terminal’s console window — not the browser console — because this code still runs on the Rust/system side of things.

It’s also possible to send something back to the client from a Tauri command, so let’s change greet quickly:

use tauri::{command}; #[command] fn greet(name: String, age: u8) { // implicit return, because no semicolon! format!("Hello {}, {} year-old human!", name, age) } // OR #[command] fn greet(name: String, age: u8) { // explicit `return` statement, must have semicolon return format!("Hello {}, {} year-old human!", name, age); }

Realizing that I’d be calling invoke many times, and being a bit lazy, I extracted a light client-side helper to consolidate things:

// @types/global.d.ts /// <reference types="@sveltejs/kit" /> type Dict<T> = Record<string, T>; declare const __TAURI__: { invoke: typeof import('@tauri-apps/api/tauri').invoke; } // src/lib/tauri.ts export function dispatch(command: string, args: Dict<string|number>) { return __TAURI__.invoke(command, args); }

The previous Greeter.svelte was then refactored into:

<!-- Greeter.svelte --> <script lang="ts"> import { dispatch } from '$lib/tauri'; async function onclick() { let output = await dispatch('greet', { name: 'Alice', age: 32 }); console.log('~>', output); //=> "~> Hello Alice, 32 year-old human!" } </script> <button on:click={onclick}>Click Me</button>

Great! So now it’s Thursday and I still haven’t written any Redis code, but at least I know how to connect the two halves of my application’s brain together. It was time to comb back through the client-side code and replace all TODOs inside event handlers and connect them to the real deal.

I will spare you the nitty gritty here, as it’s very application-specific from here on out — and is mostly a story of the Rust compiler giving me a beat down. Plus, spelunking for nitty gritty is exactly why the project is open source!

At a high-level, once a Redis connection is established using the given details, a SYNC button is accessible in the /viewer route. When this button is clicked (and only then — because of costs) a JavaScript function is called, which is responsible for connecting to the Cloudflare REST API and dispatching a "redis_set" command for each key. This redis_set command is defined in the Rust layer — as are all Redis-based commands — and is responsible for actually writing the key-value pair to Redis.

Reading data out of Redis is a very similar process, just inverted. For example, when the /viewer started up, all the keys should be listed and ready to go. In Svelte terms, that means I need to dispatch a Tauri command when the /viewer component mounts. That happens here, almost verbatim. Additionally, clicking on a key name in the sidebar reveals additional “details” about the key, including its expiration (if any), its metadata (if any), and its actual value (if known). Optimizing for cost and network load, we decided that a key’s value should only be fetched on command. This introduces a REFRESH button that, when clicked, interacts with the REST API once again, then dispatches a command so that the Redis client can update that key individually.

I don’t mean to bring things to a rushed ending, but once you’ve seen one successful interaction between your JavaScript and Rust code, you’ve seen them all! The rest of my Thursday and Friday morning was just defining new request-reply pairs, which felt a lot like sending PING and PONG messages to myself.

Conclusion

For me — and I imagine many other JavaScript developers — the challenge this past week was learning Rust. I’m sure you’ve heard this before and you’ll undoubtedly hear it again. Ownership rules, borrow-checking, and the meanings of single-character syntax markers (which are not easy to search for, by the way) are just a few of the roadblocks that I bumped into. Again, a massive thank-you to the Tauri Discord for their help and kindness!

This is also to say that using Tauri was not a challenge — it was a massive relief. I definitely plan to use Tauri again in the future, especially knowing that I can use just the webviewer if I want to. Digging into and/or adding Rust parts was “bonus material” and is only required if my app requires it.

For those wondering, because I couldn’t find another place to mention it: on macOS, the Workers KV GUI application weighs in at less than 13 MB. I am so thrilled with that!

And, of course, SvelteKit certainly made this timeline possible. Not only did it save me a half-day-slog configuring my toolbelt, but the instant, HMR development server probably saved me a few hours of manually refreshing the browser — and then the Tauri viewer.

If you’ve made it this far — that’s impressive! Thank you so much for your time and attention. A reminder that the project is available on GitHub and the latest, pre-compiled binaries are always available through its releases page.

The post How I Built a Cross-Platform Desktop Application with Svelte, Redis, and Rust appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Slinkity

Css Tricks - Mon, 07/26/2021 - 4:36am

Perhaps the #1 reason I love Astro is that it brings the JavaScript component authoring experience to the Static Site Generator world with zero JavaScript (except bits you very specifically opt-in to). That HTML-first approach is also why I like Eleventy. It’s just that, as awesome as Eleventy is, I’d prefer working in more modern components and modern templating. Plus I like the build-time JavaScript access in Astro components at my fingertips.

But what if Eleventy offered JavaScript templates also? Well it kinda does—there is a plugin eleventy-plugin-vue (official) that looks well on its way toward that end. But here’s another take on it from Ben Holmes: Slinkity. I’ll quote the bullet points:

  • &#x1f680; Unlocks component frameworks (React, Vue, and Svelte) for writing page templates and layout templates. So you can turn an existing .html or .liquid file into a .jsx file, and immediately start building routes on your site using React.
  • &#x1f516; Includes powerful shortcodes to insert components into existing pages. Add a line like this to your markdown, HTML, Nunjucks, etc, and watch the magic happen: {% react './path/to/component.jsx' %}
  • &#x1f4a7; Hydrates these component-driven pages on the client. In other words, all your dynamic state management will work in development and production with 0 extra setup.
  • &#x1f517; (Optionally) Turns your site into a single page app. This opens the door for animated page transitions, persistent state between pages, and more!

It seems like it’s bringing the Astro vibe to Eleventy. It even uses Snowpack, which is the build tool by the Astro team.

Direct Link to ArticlePermalink

The post Slinkity appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

My petite-vue review

Css Tricks - Fri, 07/23/2021 - 5:10am

Dave:

petite-vue is a new cut of the Vue project specifically built with progressive enhancement in mind. At 5kb, petite-vue is a lightweight Alpine (or jQuery) alternative that can be “sprinkled” over your project requiring no extra bundling steps or build processes. Add a <script> tag, set a v-scope, and you’re off to the races. This is up my alley.

Lots of us are still fond of jQuery, but didn’t like how fragile things could be, entirely separating interactive features and the HTML. “Separation of concerns” felt right at the time, but was ultimately too dogmatic. Authoring languages like JSX felt wrong at first, and now feel rather right, and a lot of JavaScript templating has fell in line. But heavy frameworks tend to be involved. Framework-free approaches began to show up, like Alpine.js, which allow us to sprinkle in interactive technology right into the HTML. Vue has always been sprinkable, to a degree, but now much moreso with petite-vue.

Direct Link to ArticlePermalink

The post My petite-vue review appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Organize your CSS declarations alphabetically

Css Tricks - Thu, 07/22/2021 - 12:33pm

Eric, again not mincin’ no words with blog post titles. This is me:

The most common CSS declaration organization technique I come across is none whatsoever.

Almost none, anyway. I tend to group them by whatever dumps out of my brain as I’m writing them, which usually ends up with somewhat logical groups, like box model stuff grouped together and color stuff grouped together. It just… hasn’t mattered to me. But that is strongly influenced by typically working on small teams or alone. Eric recommends the alphabetical approach because:

[…] it imposes a baseline sense of structure across a team. This ask is usually enough, especially if it means cleaning up what’s come before.

And his (probably bigger) point is that the imparted structure helps legitimize CSS in a world where CSS skills are undervalued. Not going to argue against that, but I would argue that hand-alphabetizing CSS on an existing project is very likely not a good use of time. Worse, it might break stuff if done blindly, which is why Prettier punted on it. If you and your team agree this is a good idea, I’d find a way to get this into an on-save function in your code editor and make it a pre-commit hook. Alphabetizing is a task for computers to do and the output can be verified as you are authoring.

Direct Link to ArticlePermalink

The post Organize your CSS declarations alphabetically appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Syndicate content
©2003 - Present Akamai Design & Development.