Tag: using

Using The New Constrained Layout In WordPress Block Themes

One of the main goals of the WordPress Site Editor (and, yes, that is now the “official” name) is to move basic block styling from CSS to structured JSON. JSON files are machine-readable, which makes it consumable by the JavaScript-based Site Editor for configuring a theme’s global styles directly in WordPress.

It’s not all the way there yet! If we look at the Twenty Twenty-Two (TT2) default theme, there were two main unresolved issues: styling interactions (like :hover, :active, :focus), and the margins and padding of layout containers. You can see how those were temporarily fixed in the TT2 style.css file rather than making it into the theme.json file.

WordPress 6.1 fixed those issues and what I want to do is look specifically at the latter. Now that we have JSON-ified styles for the margins and padding of layout containers, that opens us up to more flexible and robust ways to define spacing in our theme layouts.

What kind of spacing are we talking about?

First off, we already have root-level padding which is a fancy way of describing padding on the <body> element. That’s nice because it ensures consistent spacing on an element that is shared on all pages and posts.

But there’s more to it because now we have a way for blocks to bypass that padding and align themselves full-width. That’s thanks to padding-aware alignments which is a new opt-in feature in theme.json. So, even if you have root-level padding, you can still allow, say, an image (or some other block) to break out and go full-width.

That gets us to another thing we get: constrained layouts. The idea here is that any blocks nested in the layout respect the layout’s content width — which is a global setting — and do not flow outside of it. We can override that behavior on a block-by-block basis with alignments, but we’ll get to that.

Let’s start with…

Root-level padding

Again, this isn’t new. We’ve had the ability to set padding on the <body> element in theme.json since the experimental Gutenberg plugin introduced it in version 11.7. We set it on the styles.spacing object, where we have margin and padding objects to define the top, right, bottom, and left spacing on the body:

{   "version": 2,   "styles": {     "spacing": {       "margin": {         "top": "60px",         "right": "30px",         "bottom": "60px",         "left": "30px"       },       "padding": {         "top": "30px",         "right": "30px",         "bottom": "30px",         "left": "30px"       }     }   } }

This is a global setting. So, if we were to crack open DevTools and inspect the <body> element, we would see these CSS styles:

body {   margin-top: 60px;   margin-right: 30px;   margin-bottom: 60px;   margin-left: 30px;   padding-top: 30px;   padding-right: 30px;   padding-bottom: 30px;   padding-left: 30px; }

Cool. But herein lies the issue of how in the world we can allow some blocks to break out of that spacing to fill the full screen, edge-to-edge. That’s why the spacing is there, right? It helps prevent that from happening!

But there are indeed plenty of cases where you might want to break out of that spacing on a one-off instance when working in the Block Editor. Say we plop an Image block on a page and we want it to go full-width while the rest of the content respects the root-level padding?

Enter…

Padding-aware alignments

While attempting to create the first default WordPress theme that defines all styles in the theme.json file, lead designer Kjell Reigstad illustrates the challenging aspects of breaking out of root-level padding in this GitHub issue.

Root-level padding prevents blocks from taking up the full viewport width (left). But with padding-aware alignments, some blocks can “opt-out” of that spacing and take up the full viewport width (right). (Image credit: Kjell Reigstad)

New features in WordPress 6.1 were created to address this issue. Let’s dig into those next.

useRootPaddingAwareAlignments

A new useRootPaddingAwareAlignments property was created to address the problem. It was actually first introduced in the Gutenberg plugin v13.8. The original pull request is a nice primer on how it works.

{   "version": 2,   "settings": {     "appearanceTools": true,     "useRootPaddingAwareAlignments": true,     // etc.   },

Right off the bat, notice that this is a feature we have to opt into. The property is set to false by default and we have to explicitly set it to true in order to enable it. Also notice that we have appearanceTools set to true as well. That opts us into UI controls in the Site Editor for styling borders, link colors, typography, and, yes, spacing which includes margin and padding.

Setting appearanceTools set to true automatically opts blocks into margin and padding without having to set either settings.spacing.padding or setting.spacing.margin to true.

When we do enable useRootPaddingAwareAlignments, we are provided with custom properties with root padding values that are set on the <body> element on the front end. Interestingly, it also applies the padding to the .editor-styles-wrapper class so the spacing is displayed when working in the back-end Block Editor. Pretty cool!

I was able to confirm those CSS custom properties in DevTools while digging around.

Enabling useRootPaddingAwareAlignments also applies left and right padding to any block that supports the “content” width and “wide” width values in the Global Styles image above. We can also define those values in theme.json:

{   "version": 2,   "settings": {     "layout": {       "contentSize": "640px",       "wideSize": "1000px"     }   } }

If the Global Styles settings are different than what is defined in theme.json, then the Global Styles take precedence. You can learn all about managing block theme styles in my last article.

  • contentSize is the default width for blocks.
  • wideSize provides a “wide” layout option and establishes a wider column for blocks to stretch out.

So, that last code example will give us the following CSS:

/* The default content container */ .wp-container-[id] > * {   max-width: 640px;   margin-left: auto !important;   margin-right: auto !important; }  /* The wider content container */ .wp-container-[id] > .alignwide {   max-width: 1000px; }

[id] indicates a unique number automatically generated by WordPress.

But guess what else we get? Full alignment as well!

.wp-container-[id] .alignfull {   max-width: none; }

See that? By enabling useRootPaddingAwareAlignments and defining contentSize and wideSize, we also get a full alignment CSS class for a total of three container configurations for controlling the width of blocks that are added to pages and posts.

This applies to the following layout-specific blocks: Columns, Group, Post Content, and Query Loop.

Block layout controls

Let’s say we add any of those aforementioned layout-specific blocks to a page. When we select the block, the block settings UI offers us new layout settings based on the settings.layout values we defined in theme.json (or the Global Styles UI).

We’re dealing with very specific blocks here — ones that can have other blocks nested inside. So, these Layout settings are really about controlling the width and alignment of those nested blocks. The “Inner blocks use content width” setting is enabled by default. If we toggle it off, then we have no max-width on the container and the blocks inside it go edge-to-edge.

If we leave the toggle on, then nested blocks will adhere to either the contentWidth or wideWidth values (more on that in a bit). Or we can use the numeric inputs to define custom contentWidth and wideWidth values in this one-off instance. That’s great flexibility!

Wide blocks

The settings we just looked are set on the parent block. Once we’ve nested a block inside and select it, we have additional options in that block to use the contentWidth, wideWidth, or go full-width.

This Image block is set to respect the contentWidth setting, labeled “None” in the contextual menu, but can also be set to wideWidth (labeled “Wide width”) or “Full width”.

Notice how WordPress multiplies the root-level padding CSS custom properties by -1 to create negative margins when selecting the “Full width” option.

The .alignfull class sets negative margins on a nested block to ensure it takes up the full viewport width without conflicting with the root-level padding settings.

Using a constrained layout

We just covered the new spacing and alignments we get with WordPress 6.1. Those are specific to blocks and any nested blocks within blocks. But WordPress 6.1 also introduces new layout features for even more flexibility and consistency in a theme’s templates.

Case in point: WordPress has completely restructured its Flex and Flow layout types and gave us a constrained layout type that makes it easier to align block layouts in themes using the content width settings in the Site Editor’s Global Styles UI.

Flex, Flow, and Constrained layouts

The difference between these three layout types is the styles that they output. Isabel Brison has an excellent write-up that nicely outlines the differences, but let’s paraphrase them here for reference:

  • Flow layout: Adds vertical spacing between nested blocks in the margin-block direction. Those nested blocks can also be aligned to the left, right, or center.
  • Constrained layout: Same exact deal as a Flow layout, but with width constraints on nested blocks that are based on the contentWidth and wideWidth settings (either in theme.json or Global Styles).
  • Flex layout: This was unchanged in WordPress 6.1. It uses CSS Flexbox to create a layout that flows horizontally (in a row) by default, but can flow vertically as well so blocks stack one on top of another. Spacing is applied using the CSS gap property.

This new slate of layout types creates semantic class names for each layout:

Semantic layout class Layout type Supported blocks
.is-layout-flow Flow layout Columns, Group, Post Content, and Query Loop.
.is-layout-constrained Constrained layout Columns, Group, Post Content, and Query Loop.
.is-layout-flex Flex layout Columns, Buttons, Social Icons

Justin Tadlock has an extensive write-up on the different layout types and semantic classes, including use cases and examples.

Updating your theme to support constrained layouts

If you’re already using a block theme of your own making, you’re going to want to update it to support constrained layouts. All it takes is swapping out a couple of things in theme.json:

{   "version": 2,   "settings": {     "layout": {       "type": "constrained", // replaces `"inherit": true`       "type": "default", // replaces `"inherit": false`     }   } }

These are recently released block themes that have enabled spacing settings with useRootPaddingAwareAlignments and have an updated theme.json file that defines a constrained layout:

Theme Root-level padding Constrained layout features
TT3 Source code Source codeTemplates
ProWP Source code Source codeTemplates
Triangulate Source code Source codeTemplates
Oaknut Source code Source codeTemplates
Loudness Source code Source codeTemplates
Pixl Source code Source codeTemplates
Block Canvas Source code Source code, Templates
Rainfall Source code Source codeTemplates

Disabling layout styles

The base layout styles are default features that ship in WordPress 6.1 Core. In other words, they’re enabled right out of the box. But we can disable them if we need to with this little snippet in functions.php:

// Remove layout styles. add_theme_support( 'disable-layout-styles' );

Big warning here: disabling support for the default layout types also removes all of the base styling for those layouts. That means you’ll need to roll your own styles for spacing, alignments, and anything else needed to display content in different template and block contexts.

Wrapping up

As a great fan of full-width images, the new contained WordPress 6.1 layout and padding aware alignment features are two of my most favorites yet. Taken together with other tools including, better margin and padding control, fluid typography, and updated List and Quote blocks, among others, is solid proof that WordPress is moving towards a better content creation experience.

Now, we have to wait and look at how the imagination and creativity of ordinary designers and content creators use these incredible tools and take it to a new level.

Because of the site editor development iterations in progress, we should always anticipate a difficult path ahead. However, as an optimist, I am eager to see what will happen in the upcoming version of WordPress 6.2. Some of the thing, that I am keeping a close eye on are things like features being considered for inclusion, support for sticky positioning, new layout class names for inner block wrappers, updated footer alignment options, and adding constrained and flow layout options to Cover blocks.

This GitHub issues #44720 lists the layout related discussions slated for WordPress 6.2.

Additional resources

I consulted and referenced a lot of sources while digging into all of this. Here’s a big ol’ list of things I found helpful and think you might enjoy as well.

Tutorials

WordPress posts

GitHub pull requests and issues


Using The New Constrained Layout In WordPress Block Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,

Using Web Components With Next (or Any SSR Framework)

In my previous post we looked at Shoelace, which is a component library with a full suite of UX components that are beautiful, accessible, and — perhaps unexpectedly — built with Web Components. This means they can be used with any JavaScript framework. While React’s Web Component interoperability is, at present, less than ideal, there are workarounds.

But one serious shortcoming of Web Components is their current lack of support for server-side rendering (SSR). There is something called the Declarative Shadow DOM (DSD) in the works, but current support for it is pretty minimal, and it actually requires buy-in from your web server to emit special markup for the DSD. There’s currently work being done for Next.js that I look forward to seeing. But for this post, we’ll look at how to manage Web Components from any SSR framework, like Next.js, today.

We’ll wind up doing a non-trivial amount of manual work, and slightly hurting our page’s startup performance in the process. We’ll then look at how to minimize these performance costs. But make no mistake: this solution is not without tradeoffs, so don’t expect otherwise. Always measure and profile.

The problem

Before we dive in, let’s take a moment and actually explain the problem. Why don’t Web Components work well with server-side rendering?

Application frameworks like Next.js take React code and run it through an API to essentially “stringify” it, meaning it turns your components into plain HTML. So the React component tree will render on the server hosting the web app, and that HTML will be sent down with the rest of the web app’s HTML document to your user’s browser. Along with this HTML are some <script> tags that load React, along with the code for all your React components. When a browser processes these <script> tags, React will re-render the component tree, and match things up with the SSR’d HTML that was sent down. At this point, all of the effects will start running, the event handlers will wire up, and the state will actually… contain state. It’s at this point that the web app becomes interactive. The process of re-processing your component tree on the client, and wiring everything up is called hydration.

So, what does this have to do with Web Components? Well, when you render something, say the same Shoelace <sl-tab-group> component we visited last time:

<sl-tab-group ref="{tabsRef}">   <sl-tab slot="nav" panel="general"> General </sl-tab>   <sl-tab slot="nav" panel="custom"> Custom </sl-tab>   <sl-tab slot="nav" panel="advanced"> Advanced </sl-tab>   <sl-tab slot="nav" panel="disabled" disabled> Disabled </sl-tab>    <sl-tab-panel name="general">This is the general tab panel.</sl-tab-panel>   <sl-tab-panel name="custom">This is the custom tab panel.</sl-tab-panel>   <sl-tab-panel name="advanced">This is the advanced tab panel.</sl-tab-panel>   <sl-tab-panel name="disabled">This is a disabled tab panel.</sl-tab-panel> </sl-tab-group>

…React (or honestly any JavaScript framework) will see those tags and simply pass them along. React (or Svelte, or Solid) are not responsible for turning those tags into nicely-formatted tabs. The code for that is tucked away inside of whatever code you have that defines those Web Components. In our case, that code is in the Shoelace library, but the code can be anywhere. What’s important is when the code runs.

Normally, the code registering these Web Components will be pulled into your application’s normal code via a JavaScript import. That means this code will wind up in your JavaScript bundle and execute during hydration which means that, between your user first seeing the SSR’d HTML and hydration happening, these tabs (or any Web Component for that matter) will not render the correct content. Then, when hydration happens, the proper content will display, likely causing the content around these Web Components to move around and fit the properly formatted content. This is known as a flash of unstyled content, or FOUC. In theory, you could stick markup in between all of those <sl-tab-xyz> tags to match the finished output, but this is all but impossible in practice, especially for a third-party component library like Shoelace.

Moving our Web Component registration code

So the problem is that the code to make Web Components do what they need to do won’t actually run until hydration occurs. For this post, we’ll look at running that code sooner; immediately, in fact. We’ll look at custom bundling our Web Component code, and manually adding a script directly to our document’s <head> so it runs immediately, and blocks the rest of the document until it does. This is normally a terrible thing to do. The whole point of server-side rendering is to not block our page from processing until our JavaScript has processed. But once done, it means that, as the document is initially rendering our HTML from the server, the Web Components will be registered and will both immediately and synchronously emit the right content.

In our case, we’re just looking to run our Web Component registration code in a blocking script. This code isn’t huge, and we’ll look to significantly lessen the performance hit by adding some cache headers to help with subsequent visits. This isn’t a perfect solution. The first time a user browses your page will always block while that script file is loaded. Subsequent visits will cache nicely, but this tradeoff might not be feasible for you — e-commerce, anyone? Anyway, profile, measure, and make the right decision for your app. Besides, in the future it’s entirely possible Next.js will fully support DSD and Web Components.

Getting started

All of the code we’ll be looking at is in this GitHub repo and deployed here with Vercel. The web app renders some Shoelace components along with text that changes color and content upon hydration. You should be able to see the text change to “Hydrated,” with the Shoelace components already rendering properly.

Custom bundling Web Component code

Our first step is to create a single JavaScript module that imports all of our Web Component definitions. For the Shoelace components I’m using, my code looks like this:

import { setDefaultAnimation } from "@shoelace-style/shoelace/dist/utilities/animation-registry";  import "@shoelace-style/shoelace/dist/components/tab/tab.js"; import "@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js"; import "@shoelace-style/shoelace/dist/components/tab-group/tab-group.js";  import "@shoelace-style/shoelace/dist/components/dialog/dialog.js";  setDefaultAnimation("dialog.show", {   keyframes: [     { opacity: 0, transform: "translate3d(0px, -20px, 0px)" },     { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },   ],   options: { duration: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" }, }); setDefaultAnimation("dialog.hide", {   keyframes: [     { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },     { opacity: 0, transform: "translate3d(0px, 20px, 0px)" },   ],   options: { duration: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" }, });

It loads the definitions for the <sl-tab-group> and <sl-dialog> components, and overrides some default animations for the dialog. Simple enough. But the interesting piece here is getting this code into our application. We cannot simply import this module. If we did that, it’d get bundled into our normal JavaScript bundles and run during hydration. This would cause the FOUC we’re trying to avoid.

While Next.js does have a number of webpack hooks to custom bundle things, I’ll use Vite instead. First, install it with npm i vite and then create a vite.config.js file. Mine looks like this:

import { defineConfig } from "vite"; import path from "path";  export default defineConfig({   build: {     outDir: path.join(__dirname, "./shoelace-dir"),     lib: {       name: "shoelace",       entry: "./src/shoelace-bundle.js",       formats: ["umd"],       fileName: () => "shoelace-bundle.js",     },     rollupOptions: {       output: {         entryFileNames: `[name]-[hash].js`,       },     },   }, });

This will build a bundle file with our Web Component definitions in the shoelace-dir folder. Let’s move it over to the public folder so that Next.js will serve it. And we should also keep track of the exact name of the file, with the hash on the end of it. Here’s a Node script that moves the file and writes a JavaScript module that exports a simple constant with the name of the bundle file (this will come in handy shortly):

const fs = require("fs"); const path = require("path");  const shoelaceOutputPath = path.join(process.cwd(), "shoelace-dir"); const publicShoelacePath = path.join(process.cwd(), "public", "shoelace");  const files = fs.readdirSync(shoelaceOutputPath);  const shoelaceBundleFile = files.find(name => /^shoelace-bundle/.test(name));  fs.rmSync(publicShoelacePath, { force: true, recursive: true });  fs.mkdirSync(publicShoelacePath, { recursive: true }); fs.renameSync(path.join(shoelaceOutputPath, shoelaceBundleFile), path.join(publicShoelacePath, shoelaceBundleFile)); fs.rmSync(shoelaceOutputPath, { force: true, recursive: true });  fs.writeFileSync(path.join(process.cwd(), "util", "shoelace-bundle-info.js"), `export const shoelacePath = "/shoelace/$ {shoelaceBundleFile}";`);

Here’s a companion npm script:

"bundle-shoelace": "vite build && node util/process-shoelace-bundle",

That should work. For me, util/shoelace-bundle-info.js now exists, and looks like this:

export const shoelacePath = "/shoelace/shoelace-bundle-a6f19317.js";

Loading the script

Let’s go into the Next.js _document.js file and pull in the name of our Web Component bundle file:

import { shoelacePath } from "../util/shoelace-bundle-info";

Then we manually render a <script> tag in the <head>. Here’s what my entire _document.js file looks like:

import { Html, Head, Main, NextScript } from "next/document"; import { shoelacePath } from "../util/shoelace-bundle-info";  export default function Document() {   return (     <Html>       <Head>         <script src={shoelacePath}></script>       </Head>       <body>         <Main />         <NextScript />       </body>     </Html>   ); }

And that should work! Our Shoelace registration will load in a blocking script and be available immediately as our page processes the initial HTML.

Improving performance

We could leave things as they are but let’s add caching for our Shoelace bundle. We’ll tell Next.js to make these Shoelace bundles cacheable by adding the following entry to our Next.js config file:

async headers() {   return [     {       source: "/shoelace/shoelace-bundle-:hash.js",       headers: [         {           key: "Cache-Control",           value: "public,max-age=31536000,immutable",         },       ],     },   ]; }

Now, on subsequent browses to our site, we see the Shoelace bundle caching nicely!

DevTools Sources panel open and showing the loaded Shoelace bundle.

If our Shoelace bundle ever changes, the file name will change (via the :hash portion from the source property above), the browser will find that it does not have that file cached, and will simply request it fresh from the network.

Wrapping up

This may have seemed like a lot of manual work; and it was. It’s unfortunate Web Components don’t offer better out-of-the-box support for server-side rendering.

But we shouldn’t forget the benefits they provide: it’s nice being able to use quality UX components that aren’t tied to a specific framework. It’s aldo nice being able to experiment with brand new frameworks, like Solid, without needing to find (or hack together) some sort of tab, modal, autocomplete, or whatever component.


Using Web Components With Next (or Any SSR Framework) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , ,
[Top]

Using Grid Named Areas to Visualize (and Reference) Your Layout

Whenever we build simple or complex layouts using CSS Grid, we’re usually positioning items with line numbers. Grid layouts contain grid lines that are automatically indexed with positive and negative line numbers (that is unless we explicitly name them). Positioning items with line numbers is a fine way to lay things out, though CSS Grid has numerous ways to accomplish the same with an undersized cognitive encumbrance. One of those ways is something I like to think of as the “ASCII” method.

The ASCII method in a nutshell

The method boils down to using grid-template-areas to position grid items using custom-named areas at the grid container level rather than line numbers.

When we declare an element as a grid container using display: grid, the grid container, by default, generates a single-column track and rows that sufficiently hold the grid items. The container’s child elements that participate in the grid layout are converted to grid items, irrespective of their display property.

For instance, let’s create a grid by explicitly defining columns and rows using the grid-template-columns and grid-template-rows properties.

.grid {   display: grid;   grid-template-columns: 1fr 1fr;   grid-template-rows: repeat(3, 200px); }

This little snippet of CSS creates a 3×2 grid where the grid items take up equal space in the columns, and where the grid contains three rows with a track size of 200px.

We can define the entire layout with named grid areas using the grid-template-areas property. According to the spec, the initial value of grid-template-areas is none.

grid-template-areas = none | <string>+

<string>+ is listing the group of strings enclosed with a quote. Each string is represented as a cell, and each quoted string is represented as a row. Like this:

grid-template-areas: "head head" "nav main" "foot foot";

The value of grid-template-areas describes the layout as having four grid areas. They are,

  • head
  • nav
  • main
  • foot

head and foot span two column tracks and one row track. The remaining nav and main each span one column track and one row track. The value of grid-template-areas is a lot like arranging ASCII characters, and as Chris suggested a while back, we can get a visualization of the overall structure of the layout from the CSS itself which is the most trouble-free way to understand it.

(Full size GIF)

OK, so we created our layout with four named grid areas: head, nav, main, foot.

Now, let’s start to position the grid items against named grid areas instead of line numbers. Specifically, let’s place a header element into the named grid area head and specify the named grid area head in the header element using the grid-area property.

Named grid areas in a grid layout are called idents. So, what we just did was create a custom ident named head that we can use to place items into certain grid tracks.

header { grid-area: head; }

We can other HTML elements using other custom idents:

nav { grid-area: nav; } main { grid-area: main; } footer { grid-area: foot; }

Writing named area values

According to CSS Grid Layout Module Level 1, all strings must be defined under the following tokens:

  • Named cell token: This represents the named grid area in the grid. For instance, head is a named cell token.
  • Null cell token: This represents the unnamed grid area in the grid container. For instance, an empty cell in the grid is a null cell token.
  • Trash token: This is a syntax error, such as an invalid declaration. For instance, a disparate number of cells and rows compared to the number of grid items would make a declaration invalid.

In grid-template-area, every quoted string (the rows) must have the same number of cells and define the complete grid without ignoring any cell.

We can ignore a cell or leave it as an empty cell using the full-stop character (.)

.grid {    display: grid;   grid-template-areas:     "head head"     "nav main"     "foot ."; }

If that feels visually awkward or imbalanced to you, we can use multiple full-stop characters without any whitespaces separating them:

.grid {   display: grid;   grid-template-areas:     "head head"     "nav main"     "foot ...."; }

A named cell token can span multiple grid cells, But those cells must form a rectangular layout. In other words, we’re unable to create “L” or “T”-shaped layouts, although the spec does hint at support for non-rectangular layouts with disconnected regions in the future.

ASCII is better than line-based placement

Line-based placement is where we use the grid-column and grid-row properties to position an element on the grid using grid line numbers that are automatically indexed by a number:

.grid-item {   grid-column: 1 / 3; /* start at grid column line 1 and span to line 3 */ }

But grid item line numbers can change if our layout changes at a breakpoint. In those cases, it’s not like we can rely on the same line numbers we used at a specific breakpoint. This is where it takes extra cognitive encumbrance to understand the code.

That’s why I think an ASCII-based approach works best. We can redefine the layout for each breakpoint using grid-template-areas within the grid container, which offers a convenient visual for how the layout will look directly in the CSS — it’s like self-documented code!

.grid {   grid-template-areas:     "head head"     "nav main"     "foot ...."; /* much easier way to see the grid! */ }  .grid-item {   grid-area: foot; /* much easier to place the item! */ }

We can actually see a grid’s line numbers and grid areas in DevTools. In Firefox, for example, go to the Layout panel. Then, under the Grid tab, locate the “Grid display settings” and enable the “Display line number” and “Display area names” options.

Enabling grid settings.

This ASCII approach using named areas requires a lot less effort to visualize and easily find the placement of elements.

Line-based placement versus ASCII Art placement.

Let’s look at the “universal” use case

Whenever I see a tutorial on named grid areas, the common example is generally some layout pattern containing header, main, sidebar, and footer areas. I like to think of this as the “universal” use case since it casts such a wide net.

The Holy Grail layout in rectangles.

It’s a great example to illustrate how grid-template-areas works, but a real-life implementation usually involves media queries set to change the layout at certain viewport widths. Rather than having to re-declare grid-area on each grid item at each breakpoint to re-position everything, we can use grid-template-areas to “respond” to the breakpoint instead — and get a nice visual of the layout at each breakpoint in the process!

Before defining the layout, let’s assign an ident to each element using the grid-area property as a base style.

header {   grid-area: head; }  .left-side {   grid-area: left; }  main {   grid-area: main; }  .right-side {   grid-area: right; }  footer {   grid-area: foot; }

Now, let’s define the layout again as a base style. We’re going with a mobile-first approach so that things will stack by default:

.grid-container {   display: grid;   grid-template-areas:     "head"     "left"     "main"     "right"     "foot"; }

Each grid item is auto-sized in this configuration — which seems a little bit weird — so we can set min-height: 100vh on the grid container to give us more room to work with:

.grid-container {   display: grid;   grid-template-areas:     "head"     "left"     "main"     "right"     "foot";   min-height: 100vh; }

Now let’s say we want the main element to sit to the right of the stacked left and right sidebars when we get to a slightly wider viewport width. We re-declare grid-template-areas with an updated ASCII layout to get that:

@media (min-width: 800px) {   .parent {     grid-template-columns: 0.5fr 1fr;     grid-template-rows: 100px 1fr 1fr 100px;     grid-template-areas:       "head head"       "left main"       "right main"       "foot foot";   } }

I tossed some column and row sizing in there purely for aesthetics.

As the browser gets even wider, we may want to change the layout again, so that main is sandwiched between the left and right sidebars. Let’s write the layout visually!

.grid-container {   grid-template-columns: 200px 1fr 200px; /* again, just for sizing */   grid-template-areas:     "head head head"     "left main right"     "left main right"     "foot foot foot"; }

Leveraging implicit line names for flexibility

According to the spec, grid-template-areas automatically generates names for the grid lines created by named grid areas. We call these implicitly-named grid lines because they are named for us for free without any additional work.

Every named grid area gets four implicitly-named grid lines, two in the column direction and two in the row direction, where -start and -end are appended to the ident. For example, a grid area named head gets head-start and head-end lines in both directions for a total of four implicitly-named grid lines.

Implicitly assigned line names.

We can use these lines to our advantage! For instance, if we want an element to overlay the main, left, and right areas of our grid. Earlier, we talked about how layouts have to be rectangular — no “T” and “L” shaped layouts allowed. Consequently, we’re unable to use the ASCII visual layout method to place the overlay. We can, however, use our implicit line names using the same grid-area property on the overlay that we use to position the other elements.

Did you know that grid-area is a shorthand property, sort of the same way that margin and padding are shorthand properties? It takes multiple values the same way, but instead of following a “clockwise” direction like, margin — which goes in order of margin-block-start, margin-inline-end, margin-block-end, and margin-inline-startgrid-area goes like this:

grid-area: block-start / inline-start / block-end / inline-end;
Showing the block and inline flow directions in a left-to-right writing mode.

But we’re talking about rows and columns, not block and inline directions, right? Well, they correspond to one another. The row axis corresponds to the block direction, and the column axis corresponds to the inline direction:

grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end;
Block and inline axis.

Back to positioning that overlay element as a grid item in our layout. The grid-area property will be helpful to position the element using our implicitly-named grid lines:

.overlay {   grid-area: left-start / left-start / right-end / main-end; }

Creating a minimal grid system

When we focus on layouts like the “universal” use case we just saw, it’s tempting to think of grid areas in terms of one area per element. But it doesn’t have to work like that. We can repeat idents to reserve more space for them in the layout. We saw that when we repeated the head and foot idents in the last example:

.grid-container {   grid-template-areas:     "head head head"     "left main right"     "left main right"     "foot foot foot"; }

Notice that main, left, and right are also repeated but in the block direction.

Let’s forget about full page layouts and use named grid areas on a component. Grid is just as good for component layouts as full pages!

Here’s a pretty standard hero component that sports a row of images followed by different blocks of text:

A row of weightlifting photos above a heading, blurb, then a row of three links.

The HTML is pretty simple:

<div class="hero">   <div class="image">     <img src="..." alt="" />   </div>   <div class="text">     <!-- ... -->   </div> </div>

We could do this for a real fast stacked layout:

.hero {   grid-template-areas:     "image"     "text"; }

But then we have to reach for some padding, max-width or whatever to get the text area narrower than the row of images. How about we expand our ASCII layout into a four-column grid instead by repeating our idents on both rows:

.hero {   display: grid;   grid-template-columns: repeat(4, 1fr); /* maintain equal sizing */   grid-template-areas:     "image image image image"     "text  text  text  text"; }

Alright, now we can place our grid items into those named areas:

.hero .image {   grid-area: image; }  .hero .text {   grid-area: text; }

So far, so good — both rows take up the entire width. We can use that as our base layout for small screens.

Showing grid lines on the stacked mobile version of the page.

But maybe we want to introduce the narrower text when the viewport reaches a larger width. We can use what we know about the full-stop character to “skip” columns. Let’s have the text ident skip the first and last columns in this case.

@media (min-width: 800px) {   main {     grid-template-columns: repeat(6, 1fr); /* increase to six columns */     grid-template-areas:       "image image image image image image"       "..... text  text  text  text  .....";   } }

Now we have the spacing we want:

Showing grid lines for a table-sized layout of the page.

If the layout needs additional tweaking at even larger breakpoints, we can add more columns and go from there:

.hero {   grid-template-columns: repeat(8, 1fr);   grid-template-areas:     "image image image image image image image image"     "..... text  text  text  text  text  text  ....."; }

Dev tool visualization:

Showing grid lines for a large table sized layout of the page.

Remember when 12-column and 16-column layouts were the big things in CSS frameworks? We can quickly scale up to that and maintain a nice visual ASCII layout in the code:

main {   grid-template-columns: repeat(12, 1fr);   grid-template-areas:     "image image image image image image image image image image image image"     "..... text  text  text  text  text  text  text  text  text  text  ....."; }

Let’s look at something more complex

We’ve looked at one fairly generic example and one relatively straightforward example. We can still get nice ASCII layout visualizations with more complex layouts.

Let’s work up to this:

Three images positioned around a fancy heading.

I’ve split this up into two elements in the HTML, a header and a main:

<header>   <div class="logo"> ... </div>   <div class="menu"> ... </div> </header> <main>   <div class="image"> ... </div>   <h2> ... </h2>   <div class="image"> ... </div>   <div class="image"> ... </div> </main>

I think flexbox is more appropriate for the header since we can space its child elements out easily that way. So, no grid there:

header {   display: flex;   justify-content: space-between;   /* etc. */ }

But grid is well-suited for the main element’s layout. Let’s define the layout and assign the idents to the corresponding elements that we need to position the .text and three .image elements. We’ll start with this as our baseline for small screens:

.grid {   display: grid;   grid-template-columns: repeat(4, 1fr);   grid-template-areas:     "image1 image1 .....  image2"     "texts  texts  texts  texts"     ".....  image3 image3 ....."; }

You can already see where we’re going with this, right? The layout is visualized for us, and we can drop the grid items into place with the custom idents:

.image:nth-child(1) {   grid-area: image1; }  .image:nth-last-child(2) {   grid-area: image2; }  .image:nth-last-child(1) {   grid-area: image3; }  h2 {   grid-area: texts; }
Showing grid lines on a mobile layout of the page.

That’s our base layout, so let’s venture into a wider breakpoint:

@media (min-width: 800px) {   .grid {     grid-template-columns: repeat(8, 1fr);     grid-template-areas:       ". image1 image1 ...... ......  ...... image2 ."       ". texts  texts  texts  texts   texts  image2 ."       ". .....  image3 image3 image3  image3 ...... .";   } }

I bet you know exactly how that will look because the layout is right there in the code!

Showing grid lines for a table-sized layout of the page.

Same deal if we decide to scale up even further:

.grid {   grid-template-columns: repeat(12, 1fr);   grid-template-areas:     ". image1 image1 .....  .....   .....  .....  .....  .....  .....  .....  ."     ". texts  texts  texts  texts   texts  texts  texts  texts  texts  image2 ."     ". .....  image3 image3 image3  image3 .....  .....  .....  .....  .....  ."; }
Showing grid lines for a desktop-sized layout of the page.

Here’s the full demo:

I’m using the “negative margin hack” to get the first image to overlap the heading.

Wrapping up

I’m curious if anyone else is using grid-template-areas to create named areas for the benefit of having an ASCII visual of the grid layout. Having that as a reference in my CSS code has helped de-mystify some otherwise complex designs that may have been even more complex when dealing with line numbers.

But if nothing else, defining grid layouts this way teaches us some interesting things about CSS Grid that we saw throughout this post:

  • The grid-template-areas property allows us to create custom idents — or “named areas” — and use them to position grid items using the grid-area property.
  • There are three types of “tokens” that grid-template-areas accepts as values, including named cell tokens, null cell tokens, and trash cell tokens.
  • Each row that is defined in grid-template-areas needs the same number of cells. Ignoring a single cell doesn’t create a layout; it is considered a trash token.
  • We can get a visual ASCII-like diagram of the grid layout in the grid-template-areas property value by using required whitespaces between named cell tokens while defining the grid layout.
  • Make sure there is no whitespace inside a null cell token (e.g. .....). Otherwise, a single whitespace between null cell tokens creates unnecessary empty cells, resulting in an invalid layout.
  • We can redefine the layout at various breakpoints by re-positioning the grid items using grid-area, then re-declaring the layout with grid-template-areas on the grid container to update the track listing, if needed. There’s no need to touch the grid items.
  • Custom named grid areas automatically get four implicitly assigned line names — <custom-ident>-start and <custom-ident>-end in both the column and row directions.

Using Grid Named Areas to Visualize (and Reference) Your Layout originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , , ,
[Top]

Using CSS Cascade Layers to Manage Custom Styles in a Tailwind Project

If a utility class only does one thing, chances are you don’t want it to be overridden by any styles coming from elsewhere. One approach is to use !important to be 100% certain the style will be applied, regardless of specificity conflicts.

The Tailwind config file has an !important option that will automatically add !important to every utility class. There’s nothing wrong with using !important this way, but nowadays there are better ways to handle specificity. Using CSS Cascade Layers we can avoid the heavy-handed approach of using !important.

Cascade layers allow us to group styles into “layers”. The precedence of a layer always beats the specificity of a selector. Specificity only matters inside each layer. Establishing a sensible layer order helps avoid styling conflicts and specificity wars. That’s what makes CSS Cascade Layers a great tool for managing custom styles alongside styles from third-party frameworks, like Tailwind.

A Tailwind source .css file usually starts something like this:

@tailwind base; @tailwind components; @tailwind utilities; @tailwind variants;

Let’s take a look at the official Tailwind docs about directives:

Directives are custom Tailwind-specific at-rules you can use in your CSS that offer special functionality for Tailwind CSS projects. Use the @tailwind directive to insert Tailwind’s base, components, utilities and variants styles into your CSS.

In the output CSS file that gets built, Tailwind’s CSS reset — known as Preflight — is included first as part of the base styles. The rest of base consists of CSS variables needed for Tailwind to work. components is a place for you to add your own custom classes. Any utility classes you’ve used in your markup will appear next. Variants are styles for things like hover and focus states and responsive styles, which will appear last in the generated CSS file.

The Tailwind @layer directive

Confusingly, Tailwind has its own @layer syntax. This article is about the CSS standard, but let’s take a quick look at the Tailwind version (which gets compiled away and doesn’t end up in the output CSS). The Tailwind @layer directive is a way to inject your own extra styles into a specified part of the output CSS file.

For example, to append your own styles to the base styles, you would do the following:

@layer base {   h1 {     font-size: 30px;   } }

The components layer is empty by default — it’s just a place to put your own classes. If you were doing things the Tailwind way, you’d probably use @apply (although the creator of Tailwind recently advised against it), but you can also write classes the regular way:

@layer components {   .btn-blue {     background-color: blue;     color: white;   } }

The CSS standard is much more powerful. Let’s get back to that…

Using the CSS standard @layer

Here’s how we can rewrite this to use the CSS standard @layer:

@layer tailwind-base, my-custom-styles, tailwind-utilities;  @layer tailwind-base {   @tailwind base; }  @layer tailwind-utilities {   @tailwind utilities;   @tailwind variants; } 

Unlike the Tailwind directive, these don’t get compiled away. They’re understood by the browser. In fact, DevTools in Edge, Chrome, Safari, and Firefox will even show you any layers you’ve defined.

CSS Cascade Layers with Tailwind CSS layers in DevTools.

You can have as many layers as you want — and name them whatever you want — but in this example, all my custom styles are in a single layer (my-custom-styles). The first line establishes the layer order:

@layer tailwind-base, my-custom-styles, tailwind-utilities;

This needs to be provided upfront. Be sure to include this line before any other code that uses @layer. The first layer in the list will be the least powerful, and the last layer in the list will be the most powerful. That means tailwind-base is the least powerful layer and any code in it will be overridden by all the subsequent layers. That also means tailwind-utilities will always trump any other styles — regardless of source order or specificity. (Utilities and variants could go in separate layers, but the maintainers of Tailwind will ensure variants always trump utilities, so long as you include the variants below the utilities directive.)

Anything that isn’t in a layer will override anything that is in a layer (with the one exception being styles that use !important). So, you could also opt to leave utilities and variants outside of any layer:

@layer tailwind-base, tailwind-components, my-custom-styles;  @layer tailwind-base {   @tailwind base; }  @layer tailwind-components {   @tailwind components; }  @tailwind utilities; @tailwind variants;

What did this actually buy us? There are plenty of times when advanced CSS selectors come in pretty handy. Let’s create a version of :focus-within that only responds to keyboard focus rather than mouse clicks using the :has selector (which lands in Chrome 105). This will style a parent element when any of its children receive focus. Tailwind 3.1 introduced custom variants — e.g. <div class="[&:has(:focus-visible)]:outline-red-600"> — but sometimes it’s easier to just write CSS:

@layer tailwind-base, my-custom-styles; @layer tailwind-base {   @tailwind base; }  @tailwind utilities;  @layer my-custom-styles {   .radio-container {     padding: 4px 24px;     border: solid 2px rgb(230, 230, 230);   }   .radio-container:has(:focus-visible) {     outline: solid 2px blue;   } }

Let’s say in just one instance we want to override the outline-color from blue to something else. Let’s say the element we’re working with has both the Tailwind class .outline-red-600 and our own .radio-container:has(:focus-visible) class:

<div class="outline-red-600 radio-container"> ... </div>

Which outline-color will win?

Ordinarily, the higher specificity of .radio-container:has(:focus-visible) would mean the Tailwind class has no effect — even if it’s lower in the source order. But, unlike the Tailwind @layer directive that relies on source order, the CSS standard @layer overrules specificity.

As a result, we can use complex selectors in our own custom styles but still override them with Tailwind’s utility classes when we need to — without having to resort to heavy-handed !important usage to get what we want.


Using CSS Cascade Layers to Manage Custom Styles in a Tailwind Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , , , ,
[Top]

Tricks to Cut Corners Using CSS Mask and Clip-Path Properties

We recently covered creating fancy borders with CSS mask properties, and now we are going to cut the corners with CSS mask and clip-path! A lot of techniques exist to cut different shapes from the corners of any element. In this article, we will consider modern techniques to create unique corner shapes while trying to work from reusable code that allows us to produce different results by adjusting variables.

Check this online tool to get an idea of what we are building. It’s a CSS generator where you select the shape, the corners, and the size then you get the code in no time!

We mainly have two types of cuts: a circular one and an angled one. For each, we can get the full shape or the border-only shape, not to mention that we can select the corners we want to cut. A lot of combinations!

Like in the previous article, we will make lots of use of the CSS mask property. So, if you are not familiar with it, I recommend reading the quick primer I wrote before continuing.

Circular cut-out

For a circular or rounded cut, we will use radial-gradient(). To cut four corners, the logical solution is to create four gradients, one for each corner:

Each gradient is taking a quarter of the element’s dimensions. The syntax of the gradient is self-explanatory:

radial-gradient(circle 30px at top left, #0000 98%, red) top left;

Translated, this renders a circle at the top-left corner with a 30px radius. The main color is transparent (#0000) and the remaining is red. The whole gradient is also placed so that it starts at the element’s top-left corner. Same logic for the three other gradients. The keyword circle can be omitted since we explicitly specified one value for the radius.

Like I did in the previous article, I will be using slightly bigger or smaller values this time around in order to avoid bad visual result. Here, I am using 98% instead of 100% to avoid jagged edges and 51% instead of 50% to create an overlap between gradients and avoid white spaces. This logic will follow throughout this article. In fact, you will find that adding or removing 1% or 1deg typically results in a nice visual.

We apply this to the CSS mask property and we are done!

We can actually optimize that code a little:

--g: #0000 98%,#000; --r: 30px; mask:   radial-gradient(var(--r) at 0    0   ,var(--g)) 0    0,   radial-gradient(var(--r) at 100% 0   ,var(--g)) 100% 0,   radial-gradient(var(--r) at 0    100%,var(--g)) 0    100%,   radial-gradient(var(--r) at 100% 100%,var(--g)) 100% 100%; mask-size: 51% 51%; mask-repeat: no-repeat;

This way, we use custom properties for the redundant values and, as a personal preference, I am using numeric values for the positions instead of keywords.

In the generator, I will use the following syntax:

--g: #0000 98%,#000; --r: 30px; mask:   radial-gradient(var(--r) at 0    0   ,var(--g)) 0    0   /51% 51% no-repeat,   radial-gradient(var(--r) at 100% 0   ,var(--g)) 100% 0   /51% 51% no-repeat,   radial-gradient(var(--r) at 0    100%,var(--g)) 0    100%/51% 51% no-repeat,   radial-gradient(var(--r) at 100% 100%,var(--g)) 100% 100%/51% 51% no-repeat;

The shorthand syntax is easier to generate plus the whole value can be used as one custom property.

Can we use fewer gradients if we want?

Sure! One gradient can do the job. Hover the below to see the trick:

Here, we define one radial-gradient() with no size (by default it is 100% height and 100% width). This gives us a hole in the center. We translate/move the gradient by half the width and height of the image to move the hole to one corner. Since, by default, the CSS mask repeats, we get the same on each corner. We have four cut corners with only one gradient!

The only drawback of this method is that we need to know the width and height of the element in advance.

Can’t we use -50% instead of half the width and height?

Unfortunately, we’re unable to do that here because percentages doesn’t behave the same as pixel values when used with the CSS mask-position property. They’re tricky.

I have a detailed Stack Overflow answer that explains the difference. It deals with background-position but the same logic applies to the CSS mask-position property.

However, we can use some tricks to make it work with percentage values and without the need to know the width or the height. When a gradient (or a background layer) has a width and height equal to the element, we cannot move it using percentage values. So we need to change its size!

I will define a size equal to 99.5% 99.5%. I am reducing 0.5% from the width and the height to have a value different from 100% and at the same time keep the same visual result since we won’t notice a big difference between 100% and 99.5%. Now that our gradient has a size different from 100% we can move it using percentage values.

I will not detail all the math, but to move it by half the width and the height we need to use this equation:

100% * (50/(100 - 99.5)) = 100% * 100 = 10000%

It’s a strange value but it does the job:

As you can see, the trick works just fine. Whatever the size of the element is, we can cut four corners using only one gradient. However, this method has a small drawback when the width or the height of the element is a decimal value. Here is an example with an image having a width equal to 150.5px:

The use of 99.5% combined with 150.5px will create rounding issues that will break the calculation, resulting in the mask being misaligned. So, use this method with caution.

To overcome the rounding issue, we can combine the last trick with a pseudo-element. Here is a step-by-step illustration to understand the idea:

Here’s what going on in there:

  1. We define a pseudo-element that behaves as our background layer. Logically, we should use inset:0 to make it cover the entire area, but we will create a small overflow by using inset: -10% meaning that the pseudo element will overflow each side by 10%.
  2. We set our CSS mask to the pseudo-element. The mask size needs to match the size of the main element, not the pseudo-element. In other words, it will be smaller than the size of the pseudo-element and this is what we want to be able to move using percentage values. After we do the math, the size needs to be 100%/1.2. Notice in the demo above that the CSS mask is within the green border so that it matches the size of the container.
  3. Now, we need to move it in a way that simulates cutting the corner of the main element. The center of the hole needs to be in the corner of the main element, as illustrated in the demo. To do this, we use mask-position: 300% 300% ( 300% = 50%/(1 - 1/1.2) ).
  4. We remove no-repeat to activate the repetition and get the same effect for every corner.
  5. We clip the overflow and we get our final result!

I know it’s a bit overkill, but it does work and it requires only one gradient instead of four.

Let’s quickly recap the three methods we just covered:

  • The first method uses four gradients and has no drawbacks as far as usage. Sure, it’s verbose but it works with any kind of element and size. I recommend using this one.
  • The second method uses one gradient and works with any element, but it can break in some particular cases. It’s suitable with fixed-size elements. It’s ok to use, but maybe less frequently.
  • The third method uses one gradient and requires a pseudo-element. It won’t work with <img> and other elements that unable to support a pseudo-element.

The generator only supports the first and third methods.

Now that we saw the case with all the corners, let’s disable some of them. Using the first method, any corner we want to keep uncut we simply remove its gradient and adjust the size of what remains.

To disable the top-right corner:

  • We remove the top-right gradient (the blue one).
  • We have an empty corner, so we increase the size of the red gradient (or the purple one) to cover that leftover space.

Done!

You probably see just how many possibilities and combinations we can do here. If we want to cut N corners (where N ranges from 1 to 4), we use N gradients. All we need is to correctly set the size of each one to leave no space.

What about the other methods where there’s only one gradient? We will need another gradient! Those two methods use only one radial-gradient() to cut the corners, so we will rely on another gradient to “hide” the cut. We can use a conic-gradient() with four sections for this task:

conic-gradient(red 25%, blue 0 50%, green 0 75%, purple 0)

We add it on the top of the radial gradient to get the following:

The conic-gradient() covers the radial-gradient() and no corner is cut. Let’s change one color in the conic-gradient() to transparent. The one at the top-right, for example:

Did you see that? We revealed one corner of the radial-gradient() and we end with one cut corner!

Now let’s do the same thing, but for the bottom-left corner.

I think you probably get the trick by now. By changing the colors of the conic-gradient() from opaque to transparent, we reveal the corners we want to cut and gain all kinds of possible combinations. The same can be done with the third method.

Circular border-only cut-out

Let’s make the border-only version of the previous shape. In other words, we achieve the same shape but knock out the fill so all we’re left with is a border of the shape.

This is a bit tricky because we have different cases with different code. Fair warning, I will be using a lot of gradients here while finding opportunities to trim the number of them.

It should be noted that we will consider a pseudo-element in this case. Showing only the border means we need to hide the inner “fill” of the shape. Applying this to the main element will also hide the content — that’s why this is a nice use case for a pseudo-element.

One cut corner

This one needs one radial gradient and two conic gradients:

The first example illustrates the radial gradient (in red) and both conic gradients (in blue and green). In the second example, we apply all of them inside the CSS mask property to create the border-only shape with one cut corner.

Diagram zoomed in on two corners of a rectangle and another where the CSS mask is placed.
Here’s a diagram of the game plan.

As the diagram shows, the radial-gradient() creates the quarter of a circle and each conic-gradient() creates two perpendicular segments to cover two sides. It should be noted that overlapping gradients is not an issue since we are not going to change the CSS mask-composite property value.

Using the same code an adjusting a few variables, we can get the shape for the other corners.

Two cut corners

For the two-corner configuration we have two situations taking place.

In the first situation, there are two opposite corners where we need two radial gradients and two conic gradients.

The configuration is almost the same as cutting only one corner: we add an extra gradient and update a few variables.

In the second situation, there are two adjacent corners and, in this case, we need two radial gradients, one conic gradient, and one linear gradient.

“Wait!” you might exclaim. “How come the conic gradient covers three sides?” If you check the code, notice the repeat-y. In all of the examples, we always used no-repeat for the gradients, but for this we can repeat one of them to cover more sides and reduce the number of gradients we use.

Here is an example with only the conic-gradient() to understand the repetition. The trick is to have a height equal to 100% minus the border size so that the gradient fills that space when repeating, which covers the third side in the process.

Three cut corners

For this configuration, we need three radial gradients, one conic gradient, and two linear gradients.

Four corners cut

It takes four radial gradients and two linear gradients to cut all four corners.

I can hear you screaming, “How the heck am I supposed to memorize all these cases?!” You don’t need to memorize anything since you can easily generate the code for each case using the online generator. All you need is to understand the overall trick rather than each individual case. That’s why I’ve only gone into fine detail on the first configurations — the rest are merely iterations that tweak the initial foundation of the trick.

Notice there’s a general pattern we’ve been following throughout the examples:

  1. We add a radial-gradient() on the corners we want to cut.
  2. We fill the sides using either a conic-gradient() or a linear-gradient() to create the final shape.

It should be noted that we can find different ways to create the same shape. What I am showing in this post are the methods I found to be best after trying lots of other ideas. You may have a different approach you consider to be better! If so, definitely share it in the comments!

Angled cut-out

Let’s tackle another type of cut shape: the angled cut.

We have two parameters: the size and angle of the cut. To get the shape, we need a conic-gradient() for each corner. This configuration is very similar to the example that kicked off this article.

Here is an illustration of one corner to understand the trick:

The difference between each corner is an extra offset of 90deg in from and the at position. The full code is like below:

--size: 30px; --angle: 130deg;  --g: #0000 var(--angle), #000 0; mask:   conic-gradient(from calc(var(--angle)/-2 -  45deg)      at top    var(--size) left  var(--size),var(--g)) top left,   conic-gradient(from calc(var(--angle)/-2 + 45deg)      at top    var(--size) right var(--size),var(--g)) top right,   conic-gradient(from calc(var(--angle)/-2 - 135deg)      at bottom var(--size) left  var(--size),var(--g)) bottom left,   conic-gradient(from calc(var(--angle)/-2 + 135deg)      at bottom var(--size) right var(--size),var(--g)) bottom right; mask-size: 51% 51%; mask-repeat: no-repeat;

If we want to disable one corner, we remove the conic-gradient() for that corner and update the size of another one to fill the remaining space exactly like we did with the circular cut. Here’s how that looks for one corner:

We can do the exact same thing for all the other corners to get the same effect.

In addition to CSS mask, we can also use the CSS clip-path property to cut the corners. Each corner can be defined with three points.

Zooming in on a corner of the shape showing the three points that form the angled cut.
The shape consists of two points at each end of the cut, and one between them to form the angle.

The other corners will have the same value with an offset of 100%. This gives us the final code with a total of 12 points — three per corner.

/* I will define T = [1-tan((angle-90)/2)]*size */ clip-path: polygon(   /* Top-left corner */   0 T, size size,0 T, /* OR 0 0 */   /* Top-right corner */   calc(100% - T) 0,calc(100% - size) size,100% T, /* OR  100% 0 */   /* Bottom-right corner*/   100% calc(100% - T),calc(100% - size) calc(100% - size), calc(100% - T) 100%, /* OR 100% 100% */   /* Bottom-left corner */    T 100%, size calc(100% - size),0 calc(100% - T) /* OR 0 100% */ )

Notice the OR comments in that code. It defines the code we have to consider if we want to disable a particular corner. To cut a corner, we use three points. To uncut a corner, we use one point — which is nothing but the coordinate of that corner.

Border-only angled cut

Oof, we have reached the last and trickiest shape at last! This one can be achieved with either gradients or clip-path, but let’s go with the clip-path approach.

Things would get complex and verbose if we go with the gradient approach. Here’s a demo that illustrates that point:

There are nine gradients total, and I am still not done with the calculation. As you can tell, the thickness of the border is incorrect, plus the final result is unsatisfying due to the nature of gradients and their anti-aliasing issues. This approach might be a good exercise to push the limit of gradients, but I don’t recommend it in a production environment.

So, back to the clip-path method. We will still wind up with verbose code, but less of a big deal since the generator can do the job for us with a cleaner end result.

Here is an overview of the path. I am adding a small gap to better see the different points but we should have an overlap of points instead.

We have 13 outer points (the ones in black) and 13 inner points (the ones in blue).

The way we calculate the outer points is the same as how we did it for the regular angled cut. For the inner points, however, we need more math. Don’t worry, I’ll spare you some “boring” geometry explanation for this one. I know most of you don’t want it, but in case you need to dig into this, you can check the JavaScript file of the generator to find the code and the math I am using to generate the shape.

The 180deg special case

Before we end, there’s a special case for the angle cut I want to call out. It’s where we use an angle equal to 180deg. Here’s what that produces:

We have a straight line on the corner so we can optimize the clip-path code. For the full shape, we can use eight points (two points per corner) instead of 12. And for the border-only version, we can use 18 points (nine inner points and outer points) instead of 26. In other words, we can remove the middle point.

The border-only shape can also be made using gradients. But rather than using nine gradients like we did before, we can get away with only four linear gradients and a clean result.

Conclusion

We just combined CSS masks with gradients to create some fancy shapes without resorting to hacks and a lot of code! We also experienced just how much it takes to strike the right balance of code to get the right results. We even learned a few tricks along the way, like changing values by one or even half a unit. CSS is super powerful!

But, as we discussed, the online generator I made is a great place to get the code you need rather than writing it out by hand. I mean, I went through all the work of figuring out how all of this works and I would likely still need to reference this very article to remember how it’s all put together. If you can memorize all of this, kudos! But it’s nice to have a generator to fall back on.


Tricks to Cut Corners Using CSS Mask and Clip-Path Properties originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

Superior Image Optimization: An Ideal Solution Using Gatsby & ImageEngine

(This is a sponsored post.)

In recent years, the Jamstack methodology for building websites has become increasingly popular. Performance, scalable, and secure, it’s easy to see why it’s becoming an attractive way to build websites for developers.

GatsbyJS is a static site generator platform. It’s powered by React, a front-end JavaScript library, for building user interfaces. And uses GraphQL, an open-source data query and manipulation language, to pull structured data from other sources, typically a headless CMS like Contentful.

While GatsbyJS and similar platforms have revolutionized much about the web development process, one stubborn challenge remains: image optimization. Even using a modern front-end development framework like GatsbyJS, it tends to be a time-intensive and frustrating exercise.

For most modern websites, it doesn’t help much if you run on a performant technology but your images aren’t optimized. Today, images are the largest contributor to page weight, and growing, and have been singled out by Google as presenting the most significant opportunity for improving performance.

With that in mind, I want to discuss how using an image CDN as part of your technology stack can bring improvements both in terms of website performance and the entire development process.

A Quick Introduction to Gatsby

GatsbyJS is so much more than the conventional static site generators of old. Yes, you still have the ability to integrate with a software version control platform, like Git, as well as to build, deploy, and preview Gatsby projects. However, its services consist of a unified cloud platform that includes high-speed, scalable, and secure hosting as well as expert technical support and powerful third-party integrations.

What’s more, all of it comes wrapped in a user-friendly development platform that shares many similarities with the most popular CMSs of the day. For example, you can leverage pre-designed site templates or pre-configured functions (effectively website elements and modules) to speed up the production process.

It also offers many benefits for developers by allowing you to work with leading frameworks and languages, like JavaScript, React, WebPack, and GraphQL as well as baked-in capabilities to deal with performance, development iterations, etc.

For example, Gatsby does a lot to optimize your performance without any intervention. It comes with built-in code-splitting, prefetching resources, and lazy-loading. Static sites are generally known for being inherently performant, but Gatsby kicks it up a notch.

Does Gatsby Provide Built-in Image Optimization?

Gatsby does, in fact, offer built-in image optimization capabilities.

In fact, it recently upgraded in this regard, replacing the now deprecated gatsby-image package with the brand-new Gatsby image plugin. This plugin consists of two components for static and dynamic images, respectively. Typically, you would use the dynamic component if you’re handling images from a CMS, like Contentful.

Installing this plugin allows you to programmatically pass commands to the underlying framework in the form of properties, shown below:

Option Default Description
layout constrained / CONSTRAINED Determines the size of the image and its resizing behavior.
width/height Source image size Change the size of the image.
aspectRatio Source image aspect ratio Force a specific ratio between the image’s width and height.
placeholder "dominantColor" / DOMINANT_COLOR Choose the style of temporary image shown while the full image loads.
formats ["auto", "webp"] / [AUTO,WEBP] File formats of the images generated.
transformOptions [fit: "cover", cropFocus: "attention"] Options to pass to sharp to control cropping and other image manipulations.
sizes Generated automatically The <img> sizes attribute, passed to the img tag. This describes the display size of the image, and does not affect generated images. You are only likely to change this if you are using full width images that do not span the full width of the screen.
quality 50 The default image quality generated. This is override by any format-specific option.
outputPixelDensities For fixed images: [1, 2]

For constrained: [0.25, 0.5, 1, 2]

A list of image pixel densities to generate. It will never generate images larger than the source, and will always include a 1✕ image. The image is multiple by the image width, to give the generated sizes. For example, a 400px wide constrained image would generate 100, 200, 400 and 800px wide images by default. Ignored for full width layout images, which use breakpoints instead.
breakpoints [750, 1000, 1366, 1920] Output widths to generate for full width images. Default is to generate widths for common device resolutions. It will never generate an image larger than the source image. The browser will automatically choose the most appropriate.
blurredOptions None Options for the low-resolution placeholder image. Ignored unless placeholder is blurred.
tracedSVGOptions None Options for traced placeholder SVGs. See potrace options. Ignored unless placeholder is traced SVG.
jpgOptions None Options to pass to sharp when generating JPG images.

As you can see, that’s quite the toolkit to help developers process images in a variety of ways. The various options can be used to transform, style, and optimize images for performance as well as make images behave dynamically in a number of ways.

In terms of performance optimization, there are a few options that are particularly interesting:

  • Lazy-loading: Defers loading of off-screen images until they are scrolled into view.
  • Width/height: Resize image dimensions according to how they will be used.
  • Placeholder: When lazy-loading or while an image is loading in the background, use a placeholder. This can help to avoid performance penalties for core web vitals, like Cumulative Layout Shift (CLS).
  • Format: Different formats have inherently more efficient encoding. GatsbyJS supports WebP and AVIF, two of the most performant next-gen image formats.
  • Quality: Apply a specified level of quality compression to the image between 0 and 100.
  • Pixel density: A lower pixel density will save image data and can be optimized according to the screen size and PPI (pixels per inch).
  • Breakpoints: Breakpoints are important for ensuring that you serve a version of an image that’s sized appropriately for a certain threshold of screen sizes, especially that you serve smaller images for smaller screen sizes, like tablets or mobile phones. This is called responsive syntax.

So, all in all, Gatsby provides developers with a mature and sophisticated framework to process and optimize image content. The only important missing feature that’s missing is some type of built-in support for client hints.

However, there is one big catch: All of this has to be implemented manually. While GatsbyJS does use default settings for some image properties, it doesn’t offer built-in intelligence to automatically and dynamically process and serve optimized images tailored to the accessing device.

If you want to create an ideal image optimization solution, your developers will firstly have to implement device detection capabilities. They will then need to develop the logic to dynamically select optimization operations based on the specific device accessing your web app.

Finally, this code will continually need to be changed and updated. New devices come out all the time with differing properties. What’s more, standards regarding performance as well as image optimization are continually evolving. Even significant changes, additions, or updates to your own image assets may trigger the need to rework your implementation. Not to mention the time it takes to simply stay abreast of the latest information and trends and to make sure development is carried out accordingly.

Another problem is that you will need to continually test and refine your implementation. Without the help of an intelligent optimization engine, you will need to “feel out” how your settings will affect the visual quality of your images and continually fine-tune your approach to get the right results.

This will add a considerable amount of overhead to your development workload in the immediate and long term.

Gatsby also admits that these techniques are quite CPU intensive. In that case, you might want to preoptimize images. However, this also needs to be manually implemented in-code on top of being even less dynamic and flexible.

But, what if there was a better way to optimize your image assets while still enjoying all the benefits of using a platform like Gatsby? The solution I’m about to propose will help solve a number of key issues that arise from using Gatsby (and any development framework, for that matter) for the majority of your image optimization:

  • Reduce the impact optimizing images have on the development and design process in the immediate and long term.
  • Remove an additional burden and responsibility from your developers’ shoulders, freeing up time and resources to work on the primary aspects of your web app.
  • Improve your web app’s ability to dynamically and intelligently optimize image assets for each unique visitor.
  • All of this, while still integrating seamlessly with GatsbyJS as well as your CMS (in most cases).

Introducing a Better Way to Optimize Image Assets: ImageEngine

In short, ImageEngine is an intelligent, device-aware image CDN.

ImageEngine works just like any other CDN (content delivery network), such as Fastly, Akamai, or Cloudflare. However, it specializes in optimizing and serving image content specifically. 

Like its counterparts, you provide ImageEngine with the location where your image files are stored, it pulls them to its own image optimization servers, and then generates and serves optimized variants of images to your site visitors.

In doing this, ImageEngine is designed to decrease image payload, deliver optimized images tailored to each unique device, and serve images from edge nodes across its global CDN

Basically, image CDNs gather information on the accessing device by analyzing the ACCEPT header. A typical ACCEPT header looks like this (for Chrome):

image/avif,image/webp,image/apng,image/*,*/*;q=0.8

As you can see, this only provides the CDN with the accepted image formats and the recommended quality compression.

More advanced CDNs, ImageEngine, included, can also leverage client hints for more in-depth data points, such as the DPR (device pixel ratio) and Viewport-Width. This allows a larger degree of intelligent decision-making to more effectively optimize image assets while preserving visual quality.

However, ImageEngine takes things another step further by being the only mainstream image CDN that has built-in WURFL device detection. This gives ImageEngine the ability to read more information on the device, such as the operating system, resolution, and PPI (pixels per inch).

Using AI and machine-learning algorithms, this extra data means ImageEngine has virtually unparalleled decision-making power. Without any manual intervention, ImageEngine can perform all of the following image optimization operations automatically:

  • Resize your images according to the device screen size without the need for responsive syntax.
  • Intelligently compress the quality of the image to reduce the payload while preserving visual quality, using metrics like the Structural Similarity Index Method (SSIM).
  • Convert images to the most optimal, next-gen encoding formats. On top of WebP and AVIF, ImagEngine also supports JPEG 2000 (Safari), JPEG XR (Internet Explorer & Edge), and MP4 (for aGIFs).

These settings also play well with GatsbyJS’ built-in capabilities. So, you can natively implement breakpoints, lazy-loading, and image placeholders that don’t require any expertise or intelligent decision-making using Gatsby. Then, you can let ImageEngine handle the more advanced and intelligence-driven operations, like quality compression, image formatting, and resizing.

The best part is that ImageEngine does all of this automatically, making it a completely hands-off image optimization solution. ImageEngine will automatically adjust its approach with time as the digital image landscape and standards change, freeing you from this concern.

In fact, ImageEngine recommends using default settings for getting the best results in most situations.

What’s more, this logic is built into the ImageEngine edge servers. Firstly, with over 20 global PoPs, it means that the images are processed and served as close to the end-user as possible. It also means that the majority of processing happens server-side. With the exception of installing the ImageEngine Gatsby plugin, there is virtually no processing overhead at build or runtime.

This type of dynamic, intelligent decision-making will only become more important in the near and medium-term. Thanks to the number and variety of devices growing by the year, it’s becoming harder and harder to implement image optimization in a way that’s sensitive to every device.

That’s why ImageEngine can give you the edge in a mobile-first future that’s continually evolving. Simply put, ImageEngine will help futureproof your Gatsby web app.

How to Integrate ImageEngine with Gatsby: A Quick Guide

Integrating ImageEngine with GatsbyJS is trivial if you have experience installing any other third-party plugins. However, the steps will differ somewhat based on which backend CMS you use with GatsbyJS and where you store your image assets.

For example, you could use it alongside WordPress, Drupal, Contentful, and a range of other popular CMSs.

Usually, your stack would look something like this:

  • A CMS, like Contentful, to host your “space” where you’ll manage your assets and create structured data. Your images will be uploaded and stored in your space.
  • A versioning platform, like Github, to host your code and manage your versions and branches.
  • GatsbyJS to host your workspace, where you’ll build, deploy, and host the front end of your website.

So, the first thing you need to do is set up a site, or project, using GatsbyJS and link it to your CMS.

Next, you’ll install the ImageEngine plugin for GatsbyJS:

npm install @imageengine/gatsby-plugin-imageengine

You’ll also need to create a delivery address for your images via ImageEngine. You can get one by signing up for the 30-day trial here. The only thing you need to do is supply ImageEngine with the host origin URL. For Contentful, it’s images.ctfassets.net and for Sanity.io, it’s cdn.sanity.io.

ImageEngine will then provide you with a delivery address, usually in the format of {random_string}.cdn.imgeng.in.

You’ll use this delivery address to configure the ImageEngine plugin in your gatsby-config.js file. As part of this, you’ll indicate the source (Contentful, e.g.) as well as provide the ImageEngine delivery address. You can find examples of how that’s done in the documentation here.

Note that the ImageEngine plugin features built-in support for Contentful and Sanity.io as asset sources. You can also configure the plugin to pull locally stored images or from another custom source.

Once that’s done, development can begin!

Basically, Gatsby will create Graphql Nodes for the elements created in your CMS (e.g., ContentfulAsset, allSanityImageAsset, etc.). ImageEngine will then create a child node of childImageEngineAsset for each applicable element node.

You’ll then use GraphQL queries in the code for your Gatsby pages to specify the properties of the image variants you want to serve. For example, you can display an image that’s 500 ✕ 300px in the WebP format using the following query:

gatsbyImageData(width: 500, height: 300, format: jpg)

Once again, you should refer to the documentation for a more thorough treatment. You can find guides for integrating ImageEngine with Contentful, Sanity.io, and any other Gatsby project.

For a competent Gatsby user, integrating ImageEngine will only take a few minutes. And, ongoing maintenance will be minimal. If you know how to use GraphQL, then the familiar syntax to send directives and create specific image variants will be nearly effortless and should take about the same time as manually optimizing images using standard Gatsby React.

Conclusion

For most web projects, ImageEngine can reduce image payloads by up to 80%. That number can go up if you have especially high-res images.

However, you can really get the most out of your image optimization by combining the best parts of a static front-end development framework like Gatsby and an image CDN like ImageEngine. Specifically, you can use both to target Google’s core web vitals:

  • ImageEngine’s dynamic, intelligent, run-time optimization will optimize payloads to improve LCP, SI, FCP, and other data size-related metrics.
  • Using Gatsby, you can optimize for CLS and FID using best practices and by natively implementing lazy loading and image placeholders.

ImageEngine provides an Image Speed Test tool where you can quickly evaluate your current performance and see the impact of ImageEngine on key metrics. Even for a simple GatsbyJS project, the results in the Demo tool can be impressive. If you extrapolate those percentages for a larger, image-heavy site, combining Gatsby with ImageEngine could have a dramatic impact on the performance and user experience of your web app. What’s more, your developers will thank you for sparing them from the challenging and time-consuming chore of manual image optimization.


Superior Image Optimization: An Ideal Solution Using Gatsby & ImageEngine originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , , , ,
[Top]

Using Different Color Spaces for Non-Boring Gradients

A little gradient generator tool from Tom Quinonero. You’d think fading one color to another would be an obvious, simple, solved problem — it’s actually anything but!

Tom’s generator does two things that help make a gradient better:

  1. You can pick an “interpolation space.” Gradients that use the sRGB color space (pretty much all the color stuff we have in CSS today) have a bad habit of going through a gray dead zone, and if you interpolate the gradient in another color space, it can turn out nicer (and yet convert it back to RGB to use today).
  2. Easing the colors, though the use of multiple color-stops, which can result in a less abrupt and more pleasing look.

Showing a color wheel with a line indicating the two colors in a gradient that goes from yellow to light blue. The resulting gradient is at top showing some gray tones as a result of the color space.
See the gray in the middle there?

Different gradient apps with different color spaces

Josh has another similar app, as does Erik Kennedy. So stinkin’ interesting how different gradients are in different color spaces. Think of the color spaces as a physical map where individual colors are points on the map. Gradients are dumb. They just walk straight from one point on the map to the next. The colors underneath their feet as they walk make a massive difference in how the gradient turns out.

To Shared LinkPermalink on CSS-Tricks


Using Different Color Spaces for Non-Boring Gradients originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

User Registration and Auth Using Firebase and React

The ability to identify users is vital for maintaining the security of any applications. Equally important is the code that’s written to manage user identities, particularly when it comes to avoiding loopholes for unauthorized access to data held by an application. Writing authentication code without a framework or libraries available can take a ton of time to do right — not to mention the ongoing maintainance of that custom code.

This is where Firebase comes to the rescue. Its ready-to-use and intuitive methods make setting up effective user identity management on a site happen in no time. This tutorial will work us through on how to do that: implementing user registration, verification, and authentication.

Firebase v9 SDK introduces a new modular API surface, resulting in a change to several of its services, one of which is Firebase Authentication. This tutorial is current to the changes in v9.

To follow along with this tutorial, you should be familiar with React, React hooks, and Firebase version 8. You should also have a Google account and Node installed on your machine.

Table of Contents

Setting up Firebase

Before we start using Firebase for our registration and authentication requirements, we have to first set up our Firebase project and also the authentication method we’re using.

To add a project, make sure you are logged into your Google account, then navigate to the Firebase console and click on Add project. From there, give the project a name (I’m using “Firebase-user-reg-auth”) and we should be all set to continue.

You may be prompted to enable Google Analytics at some point. There’s no need for it for this tutorial, so feel free to skip that step.

Firebase has various authentication methods for both mobile and web, but before we start using any of them, we have to first enable it on the Firebase Authentication page. From the sidebar menu, click on the Authentication icon, then, on the next page, click on Get started.

We are going to use Email/Password authentication. Click on it and we will be prompted with a screen to enable it, which is exactly what we want to do.

Cloning and setting up the starter repo

I have already created a simple template we can use for this tutorial so that we can focus specifically on learning how to implement the functionalities. So what we need to do now is clone the GitHub repo.

Fire up your terminal. Here’s what we can run from the command line:

git clone -b starter https://github.com/Tammibriggs/Firebase_user_auth.git  cd Firebase_user_auth  npm install

I have also included Firebase version 9 in the dependency object of the package.json file. So, by running the npm install command, Firebase v9 — along with all other dependencies — will be installed.

With done that, let’s start the app with npm start!

Integrating Firebase into our React app

To integrate Firebase, we need to first get the web configuration object and then use it to initialize Firebase in our React app. Go over to the Firebase project page and we will see a set of options as icons like this:

Click on the web (</>) icon to configure our Firebase project for the web, and we will see a page like this:

Enter firebase-user-auth as the name of the web app. After that, click on the Register app button, which takes us to the next step where our firebaseConfig object is provided.

Copy the config to the clipboard as we will need it later on to initialize Firebase. Then click on the Continue to console button to complete the process.

Now, let’s initialize Firebase and Firebase Authentication so that we can start using them in our app. In the src directory of our React app, create a firebase.js file and add the following imports:

// src/firebase.js import { initializeApp } from 'firebase/app' import {getAuth} from 'firebase/auth'

Now, paste the config we copied earlier after the imports and add the following lines of code to initialize Firebase and Firebase Authentication.

// src/firebase.js const app = initializeApp(firebaseConfig) const auth = getAuth(app)  export {auth}

Our firebase.js file should now look something like this:

// src.firebase.js import { initializeApp } from "firebase/app" import { getAuth } from "firebase/auth"  const firebaseConfig = {   apiKey: "API_KEY",   authDomain: "AUTH_DOMAIN",   projectId: "PROJECT_ID",   storageBucket: "STORAGE_BUCKET",   messagingSenderId: "MESSAGING_SENDER_ID",   appId: "APP_ID" }  // Initialize Firebase and Firebase Authentication const app = initializeApp(firebaseConfig) const auth = getAuth(app) export {auth}

Next up, we’re going to cover how to use the ready-to-use functions provided by Firebase to add registration, email verification, and login functionality to the template we cloned.

Creating User Registration functionality

In Firebase version 9, we can build functionality for user registration with the createUserWithEmailAndPassword function. This function takes three arguments:

  • auth instance/service
  • email
  • password

Services are always passed as the first arguments in version 9. In our case, it’s the auth service.

To create this functionality, we will be working with the Register.js file in the src directory of our cloned template. What I did in this file is create three form fields — email, password, and confirm password — and input is controlled by the state. Now, let’s get to business.

Let’s start by adding a function that validates the password and confirm password inputs, checking if they are not empty and are the same: Add the following lines of code after the states in the Register component:

// src/Register.js // ...  const validatePassword = () => {   let isValid = true   if (password !== '' && confirmPassword !== ''){     if (password !== confirmPassword) {       isValid = false       setError('Passwords does not match')     }   }   return isValid }  // ...

In the above function, we return an isValid variable which can return either true or false based on the validity of the passwords. Later on, we will use the value of this variable to create a condition where the Firebase function responsible for registering users will only be invoked if isValid is true.

To create the registration functionality, let’s start by making the necessary imports to the Register.js file:

// src/Register.js import {auth} from './firebase' import {createUserWithEmailAndPassword} from 'firebase/auth'

Now, add the following lines of code after the validatePassword password function:

// src/Register.js // ...  const register = e => {   e.preventDefault()   setError('')   if(validatePassword()) {     // Create a new user with email and password using firebase       createUserWithEmailAndPassword(auth, email, password)       .then((res) => {           console.log(res.user)         })       .catch(err => setError(err.message))   }   setEmail('')   setPassword('')   setConfirmPassword('') }  // ...

In the above function, we set a condition to call the createUserWithEmailAndPassword function only when the value returning from validatePassword is true.

For this to start working, let’s call the register function when the form is submitted. We can do this by adding an onSubmit event to the form. Modify the opening tag of the registration_form to look like this:

// src/Register.js <form onSubmit={register} name='registration_form'>

With this, we can now register a new user on our site. To test this by going over to http://localhost:3000/register in the browser, filling in the form, then clicking on the Register button.

Showing a user registration form with fields to enter an email. a password, and password confirmation. A gray button labeled Register is below the three stacked fields.

After clicking the Register button, if we open the browser’s console we will see details of the newly registered user.

Managing User State with React Context API

Context API is a way to share data with components at any level of the React component tree without having to pass it down as props. Since a user might be required by a different component in the tree, using the Context API is great for managing the user state.

Before we start using the Context API, there are a few things we need to set up:

  • Create a context object using the createContext() method
  • Pass the components we want to share the user state with as children of Context.Provider
  • Pass the value we want the children/consuming component to access as props to Context.Provider

Let’s get to it. In the src directory, create an AuthContext.js file and add the following lines of code to it:

// src/AuthContext.js import React, {useContext} from 'react'  const AuthContext = React.createContext()  export function AuthProvider({children, value}) {   return (     <AuthContext.Provider value={value}>       {children}     </AuthContext.Provider>   ) }  export function useAuthValue(){   return useContext(AuthContext) }

In the above code, we created a context called AuthContext along with that we also created two other functions that will allow us to easily use the Context API which is AuthProvider and useAuthValue.

The AuthProvider function allows us to share the value of the user’s state to all the children of AuthContext.Provider while useAuthValue allows us to easily access the value passed to AuthContext.Provider.

Now, to provide the children and value props to AuthProvider, modify the App.js file to look something like this:

// src/App.js // ... import {useState} from 'react' import {AuthProvider} from './AuthContext'  function App() {   const [currentUser, setCurrentUser] = useState(null)    return (     <Router>       <AuthProvider value={{currentUser}}>         <Switch>          ...         </Switch>       </AuthProvider>     </Router>   ); }  export default App;

Here, we’re wrapping AuthProvider around the components rendered by App. This way, the currentUser value supplied to AuthProvider will be available for use by all the components in our app except the App component.

That’s it as far as setting up the Context API! To use it, we have to import the useAuthValue function and invoke it in any of the child components of AuthProvider, like Login. The code looks something like this:

import { useAuthValue } from "./AuthContext"  function childOfAuthProvider(){   const {currentUser} = useAuthValue()   console.log(currentUser)    return ... }

Right now, currentUser will always be null because we are not setting its value to anything. To set its value, we need to first get the current user from Firebase which can be done either by using the auth instance that was initialized in our firebase.js file (auth.currentUser), or the onAuthStateChanged function, which actually happens to be the recommended way to get the current user. That way, we ensure that the Auth object isn’t in an intermediate state — such as initialization — when we get the current user.

In the App.js file, add a useEffect import along with useState and also add the following imports:

// src/App.js import {useState, useEffect} from 'react' import {auth} from './firebase' import {onAuthStateChanged} from 'firebase/auth'

Now add the following line of code after the currentUser state in the App component:

// src/App.js // ...  useEffect(() => {   onAuthStateChanged(auth, (user) => {     setCurrentUser(user)    }) }, [])  // ...

In the above code, we are getting the current user and setting it in the state when the component renders. Now when we register a user the currentUser state will be set with an object containing the user’s info.

Send a verification email to a registered user

Once a user is registered, we want them to verify their email address before being able to access the homepage of our site. We can use the sendEmailVerification function for this. It takes only one argument which is the object of the currently registered user. When invoked, Firebase sends an email to the registered user’s email address with a link where the user can verify their email.

Let’s head over to the Register.js file and modify the Link and createUserWithEmailAndPassword import to look like this:

// src/Register.js import {useHistory, Link} from 'react-router-dom' import {createUserWithEmailAndPassword, sendEmailVerification} from 'firebase/auth'

In the above code, we have also imported the useHistory hook. This will help us access and manipulate the browser’s history which, in short, means we can use it to switch between pages in our app. But before we can use it we need to call it, so let’s add the following line of code after the error state:

// src/Register.js // ... const history = useHistory()  // ...

Now, modify the .then method of the createUserWithEmailAndPassword function to look like this:

// src/Register.js // ... .then(() => {   sendEmailVerification(auth.currentUser)   .then(() => {     history.push('/verify-email')   }).catch((err) => alert(err.message)) }) // ...

What’s happening here is that when a user registers a valid email address, they will be sent a verification email, then taken to the verify-email page.

There are several things we need to do on this page:

  • Display the user’s email after the part that says “A verification email has been sent to:”
  • Make the Resend Email button work
  • Create functionality for disabling the Resend Email button for 60 seconds after it is clicked
  • Take the user to their profile page once the email has been verified

We will start by displaying the registered user’s email. This calls for the use of the AuthContext we created earlier. In the VerifyEmail.js file, add the following import:

// src/VerifyEmail.js import {useAuthValue} from './AuthContext'

Then, add the following code before the return statement in the VerifyEmail component:

// src/VerifyEmail.js const {currentUser} = useAuthValue()

Now, to display the email, add the following code after the <br/> tag in the return statement.

// src/VerifyEmail.js // ... <span>{currentUser?.email}</span> // ...

In the above code, we are using optional chaining to get the user’s email so that when the email is null our code will throw no errors.

Now, when we refresh the verify-email page, we should see the email of the registered user.

Let’s move to the next thing which is making the Resend Email button work. First, let’s make the necessary imports. Add the following imports to the VerifyEmail.js file:

// src/VerifyEmail.js import {useState} from 'react' import {auth} from './firebase' import {sendEmailVerification} from 'firebase/auth'

Now, let’s add a state that will be responsible for disabling and enabling the Resend Email button based on whether or not the verification email has been sent. This code goes after currentUser in the VerifyEmail component:

// src/VerifyEmail.js const [buttonDisabled, setButtonDisabled] = useState(false)

For the function that handles resending the verification email and disabling/enabling the button, we need this after the buttonDisabled state:

// src/VerifyEmail.js // ...  const resendEmailVerification = () => {   setButtonDisabled(true)   sendEmailVerification(auth.currentUser)   .then(() => {     setButtonDisabled(false)   }).catch((err) => {     alert(err.message)     setButtonDisabled(false)   }) }  // ...

Next, in the return statement, modify the Resend Email button like this:

// ... <button    onClick={resendEmailVerification}   disabled={buttonDisabled}   >Resend Email</button> // ...

Now, if we go over to the verify-email page and click the button, another email will be sent to us. But there is a problem with how we created this functionality because if we try to click the button again in less than a minute, we get an error from Firebase saying we sent too many requests. This is because Firebase has a one minute interval before being able to send another email to the same address. That’s the net thing we need to address.

What we need to do is make the button stay disabled for 60 seconds (or more) after a verification email is sent. We can enhance the user experience a bit by displaying a countdown timer in Resend Email button to let the user know the button is only temporarily disabled.

In the VerifyEmail.js file, add a useEffect import:

import {useState, useEffect} from 'react'

Next, add the following after the buttonDisabled state:

// src/VerifyEmail.js const [time, setTime] = useState(60) const [timeActive, setTimeActive] = useState(false)

In the above code, we have created a time state which will be used for the 60-second countdown and also a timeActive state which will be used to control when the count down will start.

Add the following lines of code after the states we just created:

// src/VerifyEmail.js // ...  useEffect(() => {   let interval = null   if(timeActive && time !== 0 ){     interval = setInterval(() => {       setTime((time) => time - 1)     }, 1000)   }else if(time === 0){     setTimeActive(false)     setTime(60)     clearInterval(interval)   }   return () => clearInterval(interval); }, [timeActive, time])  // ...

In the above code, we created a useEffect hook that only runs when the timeActive or time state changes. In this hook, we are decreasing the previous value of the time state by one every second using the setInterval method, then we are stopping the decrementing of the time state when its value equals zero.

Since the useEffect hook is dependent on the timeActive and time state, one of these states has to change before the time count down can start. Changing the time state is not an option because the countdown has to start only when a verification email has been sent. So, instead, we need to change the timeActive state.

In the resendEmailVerification function, modify the .then method of sendEmailVerification to look like this:

// src/VerifyEmail.js // ... .then(() => {   setButtonDisabled(false)   setTimeActive(true) }) // ...

Now, when an email is sent, the timeActive state will change to true and the count down will start. In the code above we need to change how we are disabling the button because, when the count down is active, we want the disabled button.

We will do that shortly, but right now, let’s make the countdown timer visible to the user. Modify the Resend Email button to look like this:

// src/VerifyEmail.js <button    onClick={resendEmailVerification}   disabled={buttonDisabled} >Resend Email {timeActive && time}</button>

To keep the button in a disabled state while the countdown is active, let’s modify the disabled attribute of the button to look like this:

disabled={timeActive}

With this, the button will be disabled for a minute when a verification email is sent. Now we can go ahead and remove the buttonDisabled state from our code.

Although this functionality works, there is still one problem with how we implemented it: when a user registers and is taken to the verify-email page when they have not received an email yet, they may try to click the Resend Email button, and if they do that in less than a minute, Firebase will error out again because we’ve made too many requests.

To fix this, we need to make the Resend Email button disabled for 60 seconds after an email is sent to the newly registered user. This means we need a way to change the timeActive state within the Register component. We can also use the Context API for this. It will allow us to globally manipulate and access the timeActive state.

Let’s make a few modifications to our code to make things work properly. In the VerifyEmail component, cut the timeActive state and paste it into the App component after the currentUser state.

// src/App.js function App() {   // ...   const [timeActive, setTimeActive] = useState(false)    // ...

Next, put timeActive and setTimeActive inside the object of AuthProvider value prop. It should look like this:

// src/App.js // ... <AuthProvider value={{currentUser, timeActive, setTimeActive}}> // ... 

Now we can access timeActive and setTimeActive within the children of AuthProvider. To fix the error in our code, go to the VerifyEmail.js file and de-structure both timeActive and setTimeActive from useAuthProvider:

// src/VerifyEmail.js const {timeActive, setTimeActive} = useAuthValue()

Now, to change the timeActive state after a verification email has been sent to the registered user, add the following import in the Register.js file:

// src/Register.js import {useAuthValue} from './AuthContext'

Next, de-structure setTimeActive from useAuthValue with this snippet among the other states in the Register component:

// src/Register.js const {setTimeActive} = useAuthValue()

Finally, in the register function, set the timeActive state with the .then the method of sendEmailVerification:

// src/Register.js // ... .then(() => {   setTimeActive(true)   history.push('/verify-email') }) // ...

With this, a user will be able to send a verification email without getting any errors from Firebase.

The last thing to fix concerning user verification is to take the user to their profile page after they have verified their email. To do this, we will use a reload function in the currentUser object. It allows us to reload the user object coming from Firebase, that way we will know when something has changed.

First, let’s make the needed imports. In the VerifyEmail.js file, let’s add this:

// src/VerifyEmail.js import {useHistory} from 'react-router-dom'

We are importing useHistory so that we can use to navigate the user to the profile page. Next, add the following line of code after the states:

// src/VerifyEmail.js const history = useHistory()

And, finally, add the following lines of code after the history variable:

// src/VerifyEmail.js // ...  useEffect(() => {   const interval = setInterval(() => {     currentUser?.reload()     .then(() => {       if(currentUser?.emailVerified){         clearInterval(interval)         history.push('/')       }     })     .catch((err) => {       alert(err.message)     })   }, 1000) }, [history, currentUser])  // ...

In the above code, we are running the reload function every one second until the user’s email has been verified, and, if it has, we are navigating the user to their profile page.

To test this, let’s verify our email by following the instructions in the email sent from Firebase. If all is good, we will be automatically taken to our profile page.

Right now the profile page is showing no user data and he Sign Out link does not work. That’s ur next task.

Working on the user profile page

Let’s start by displaying the Email and Email verified values. For this, we will make use of the currentUser state in AuthContext. What we need to do is import useAuthValue, de-structure currentUser from it, and then display the Email and Email verified value from the user object.

Here is what the Profile.js file should look like:

// src/Profile.js import './profile.css' import {useAuthValue} from './AuthContext'  function Profile() {   const {currentUser} = useAuthValue()    return (     <div className='center'>       <div className='profile'>         <h1>Profile</h1>         <p><strong>Email: </strong>{currentUser?.email}</p>         <p>           <strong>Email verified: </strong>           {`$ {currentUser?.emailVerified}`}         </p>         <span>Sign Out</span>       </div>     </div>   ) }  export default Profile

With this, the Email and Email verified value should now be displayed on our profile page.

To get the sign out functionality working, we will use the signOut function. It takes only one argument, which is the auth instance. So, in Profile.js. let’s add those imports.

// src/Profile.js import { signOut } from 'firebase/auth'  import { auth } from './firebase'

Now, in the return statement, modify the <span> that contains “Sign Out” so that is calls the signOut function when clicked:

// src/Profile.js // ... <span onClick={() => signOut(auth)}>Sign Out</span> // ...

Creating a Private Route for the Profile component

Right now, even with an unverified email address, a user can access the profile page. We don’t want that. Unverified users should be redirected to the login page when they try to access the profile. This is where private routes come in.

In the src directory, let’s create a new PrivateRoute.js file and add the following code to it:

// src/PrivateRoute.js import {Route, Redirect} from 'react-router-dom' import {useAuthValue} from './AuthContext'  export default function PrivateRoute({component:Component, ...rest}) {   const {currentUser} = useAuthValue()    return (     <Route       {...rest}       render={props => {         return currentUser?.emailVerified ? <Component {...props} /> : <Redirect to='/login' />     }}>     </Route>   ) }

This PrivateRoute is almost similar to using the Route. The difference is that we are using a render prop to redirect the user to the profile page if their email is unverified.

We want the profile page to be private, so well import PrivateRoute:

// src/App.js import PrivateRoute from './PrivateRoute'

Then we can replace Route with PrivateRoute in the Profile component. The Profile route should now look like this:

// src/App.js <PrivateRoute exact path="/" component={Profile} />

Nice! We have made the profile page accessible only to users with verified emails.

Creating login functionality

Since only users with verified emails can access their profile page when logged in with the signInWithEmailAndPassword function, we also need to check if their email has been verified and, if it is unverified, the user should be redirected to the verify-email page where the sixty-second countdown should also start.

These are the imports we need to add to the Login.js file:

import {signInWithEmailAndPassword, sendEmailVerification} from 'firebase/auth' import {auth} from './firebase' import {useHistory} from 'react-router-dom' import {useAuthValue} from './AuthContext'

Next, add the following line of code among the states in the Login component.

// src/Login.js const {setTimeActive} = useAuthValue() const history = useHistory()

Then add the following function after the history variable:

// src/Login.js // ...  const login = e => {   e.preventDefault()   signInWithEmailAndPassword(auth, email, password)   .then(() => {     if(!auth.currentUser.emailVerified) {       sendEmailVerification(auth.currentUser)       .then(() => {         setTimeActive(true)         history.push('/verify-email')       })     .catch(err => alert(err.message))   }else{     history.push('/')   }   })   .catch(err => setError(err.message)) }  // ...

This logs in a user and then check if whether they are verified or not. If they are verified, we navigate them to their profile page. But if they are unverified, we send a verification email, then redirect them to the verify-email page.

All we need to do to make this work is call the login function when the form is submitted. So, let’s modify the opening tag of the login_form to this:

// src/Login.js <form onSubmit={login} name='login_form'>

And, hey, we’re done!

Conclusion

In this tutorial, we have learned how to use version 9 of the Firebase Authentication to build a fully functioning user registration and authentication service in React. Is it super easy? No, there are a few thing we need to juggle. But is it a heck of a lot easier than building our own service from scratch? You bet it is! And that’s what I hope you got from reading this.

References


User Registration and Auth Using Firebase and React originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

Fancy CSS Borders Using Masks

Have you ever tried to make CSS borders in a repeating zig-zag pattern? Like where a colored section of a website ends and another differently colored section begins — not with a straight line, but angled zig zags, rounded humps, or waves. There are a number of ways you could do this sort of CSS border, dating all the way back to using a background-image. But we can get more modern and programmatic with it. In this article, we’ll look at some modern CSS mask techniques to achieve the look.

Before we dig into the technical parts, though, let’s take a look at what we are building. I have made a CSS border generator where you can easily generate any kind of border within a few seconds and get the CSS code.

Did you see that? With the CSS mask property and a few CSS gradients, we get a responsive and cool-looking border — all with CSS by itself. Not only this, but such effect can be applied to any element where we can have any kind of coloration (e.g. image, gradient, etc). We get all this without extra elements, pseudo elements, or magic numbers coming from nowhere!

Oh great! All I have to do is to copy/paste code and it’s done!

True, but it’s good to understand the logic to be able to manually adjust the code if you need to.

Masking things

Since all our effects rely on the CSS mask property, let’s take a quick refresh on how it works. Straight from the spec:

The effect of applying a mask to a graphical object is as if the graphical object will be painted onto the background through a mask, thus completely or partially masking out parts of the graphical object.

If we check the formal syntax of the mask property we can see it accepts an <image> as a value, meaning either a URL of an image or a color gradient. Gradients are what we’ll be using here. Let’s start with basic examples:

In the first example of this demo, a gradient is used to make it appear as though the image is fading away. The second example, meanwhile, also uses a gradient, but rather than a soft transition between colors, a hard color stop is used to hide (or mask) half of the image. That second example illustrates the technique we will be using to create our fancy borders.

Oh, and the CSS mask property can take multiple gradients as long as they are comma-separated. That means we have even more control to mask additional parts of the image.

That example showing multiple masking gradients may look a bit tricky at first glance, but what’s happening is the same as applying the multiple gradients on the background property. But instead of using a color that blends in with the page background, we use a “transparent” black value (#0000) for the hidden parts of the image and full black (#000) for the visible parts.

That’s it! Now we can tackle our fancy borders.

Zig-Zag CSS borders

As we saw in the video at the start of this article, the generator can apply borders on one side, two sides, or all sides. Let’s start with the bottom side using a step-by-step illustration:

  1. We start by adding the first gradient layer with a solid color (red) at the top. A height that’s equal to calc(100% - 40px) is used to leave 40px of empty space at the bottom.
  2. We add a second gradient placed at the bottom that takes the remaining height of the container. There’s a little bit of geometry happening to make this work.
Diagram showing how the shape of a zig-zag is created in CSS. An upside down triangle in blue represents the shape and green areas to the left and right of it show the leftover space that is masked out with CSS.
  1. Next, we repeat the last gradient horizontally (replacing no-repeat with repeat-x). We can already see the zig-zag shape!
  2. Gradients are known to have anti-aliasing issues creating jagged edges (especially on Chrome). To avoid this, we add a slight transition between the colors, changing blue 90deg, green 0 to green, blue 1deg 89deg, green 90deg.
  3. Then we update the colors to have a uniform shape
  4. Last, we use everything inside the mask property!

We can extract two variables from those steps to define our shape: size (40px) and angle (90deg). Here’s how we can express that using placeholders for those variables. I will be using JavaScript to replace those variables with their final values.

mask:   linear-gradient(red 0 0) top/100% calc(100% - {size}) no-repeat,   conic-gradient(     from {-angle/2} at bottom,     #0000, #000 1deg {angle - 1} ,#0000 {angle}   ) bottom/{size*2*tan(angle/2)} {size} repeat-x;

We can use CSS custom properties for the size and the angle, but trigonometric functions are unsupported features at this moment. In the future, we’ll be able to do something like this:

--size: 40px; --angle: 90deg; mask:   linear-gradient(red 0 0) top/100% calc(100% - var(--size)) no-repeat,   conic-gradient(     from calc(var(--angle)/-2) at bottom,     #0000, #000 1deg calc(var(--angle) - 1deg), #0000 var(--angle)   ) bottom/calc(var(--size)*2*tan(var(--angle)/2)) var(--size) repeat-x;

Similar to the bottom border, the top one will have almost the same code with a few adjustments:

mask:   linear-gradient(red 0 0) bottom/100% calc(100% - {size}) no-repeat,   conic-gradient(     from {180deg - angle/2} at top,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) top/{size*2*tan(angle/2)} {size} repeat-x;

We changed bottom with top and top with bottom, then updated the rotation of the gradient to 180deg - angle/2 instead of -angle/2. As simple as that!

That’s the pattern we can use for the rest of the sides, like the left:

mask:   linear-gradient(red 0 0) right/calc(100% - {size}) 100% no-repeat,   conic-gradient(     from {90deg - angle/2} at left,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) left/{size} {size*2*tan(angle/2)} repeat-y;

…and the right:

mask:   linear-gradient(red 0 0) left/calc(100% - {size}) 100% no-repeat,   conic-gradient(     from {-90deg - angle/2} at right,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) right/{size} {size*2*tan(angle/2)} repeat-y;

Let’s make the borders for when they’re applied to two sides at once. We can actually reuse the same code. To get both the top and bottom borders, we simply combine the code of both the top and bottom border.

We use the conic-gradient() of the top side, the conic-gradient() of the bottom side plus a linear-gradient() to cover the middle area.

mask:   linear-gradient(#000 0 0) center/100% calc(100% - {2*size}) no-repeat,   conic-gradient(     from {-angle/2} at bottom,     #0000, #000 1deg {angle - 1},     #0000 {angle}   ) bottom/{size*2*tan(angle/2)} {size} repeat-x;   conic-gradient(     from {180deg - angle/2} at top,      #0000, #000 1deg {angle - 1}, #0000 {angle}   ) top/{size*2*tan(angle/2)} {size} repeat-x;

The same goes when applying borders to the left and right sides together:

mask:   linear-gradient(#000 0 0) center/calc(100% - {2*size}) 100% no-repeat,   conic-gradient(     from {90deg - angle/2} at left,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) left/{size} {size*2*tan(angle/2)} repeat-y,   conic-gradient(     from {-90deg - angle/2} at right,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) right/{size} {size*2*tan(angle/2)} repeat-y;

So, if we want to apply borders to all of the sides at once, we add all the gradients together, right?

Exactly! We have four conic gradients (one on each side) and one linear-gradient() in the middle. We set a fixed angle equal to 90deg because it the only one that results in nicer corners without weird overlapping. Note that I’m also using space instead of repeat-x or repeat-y to avoid bad result on corners like this:

Resizing a container with four sides configuration

Rounded CSS borders

Now let’s tackle rounded borders!

Oh no! another long explanation with a lot of calculation?!

Not at all! There is nothing to explain here. We take everything from the zig-zag example and update the conic-gradient() with a radial-gradient(). It’s even easier because we don’t have any angles to deal with — only the size variable.

Here is an illustration for one side to see how little we need to do to switch from the zig-zag border to the rounded border:

Again, all I did there was replace the conic-gradient() with this (using placeholders for size):

background:    radial-gradient(circle farthest-side, #0000 98%, #000)    50% calc(100% + {size})/{1.85*size} {2*size} repeat-x

And this for the second one:

background:   radial-gradient(circle farthest-side, #000 98%, #0000)    bottom/{1.85*size} {2*size} repeat-x

What is the logic behind the magic numbers 1.85 and 98%?

Logically, we should use 100% instead of 98% to have a circle that touches the edges of the background area; but again, it’s the anti-aliasing issue and those jagged edges. We use a slightly smaller value to prevent weird overlapping.

The 1.85 value is more of a personal preference than anything. I initially used 2 which is the logical value to get a perfect circle, but the result doesn’t look quite as nice, so the smaller value creates a more seamless overlap between the circles.

Here’s the difference:

Now we need to replicate this for the rest of the sides, just as we did with the zig-zag CSS border.

There is a small difference, however, when applying all four sides at once. You will notice that for one of the rounded borders, I used only one radial-gradient() instead of four. That makes sense since we can repeat a circular shape over all the sides using one gradient like illustrated below:

Here’s the final CSS:

mask:   linear-gradient(#000 0 0) center/calc(100% - {1.85*size}) calc(100% - {1.85*size}) no-repeat,   radial-gradient(farthest-side,#000 98%,#0000) 0 0/{2*size} {2*size} round;

Note how I’m using the round value instead of repeat. That’s to make sure we don’t cut off any of the circles. And, again, that 1.85 value is a personal preference value.

For the other type of rounded border, we still have to use four radial gradients, but I had to introduce the CSS clip-path property to correct an overlapping issue at the corners. You can see the difference between with and without clip-path in the following demo:

It’s an eight-point path to cut the corners:

clip-path: polygon(    {2*size} 0,calc(100% - {2*size}) 0,    100% {2*size},100% calc(100% - {2*size}),    calc(100% - {2*size}) 100%,{2*size} 100%,    0 calc(100% - {2*size}),0 {2*size} );

Wavy CSS borders

Both the zig-zag and rounded CSS borders needed one gradient to get the shapes we wanted. What about a wavy sort of border? That take two gradients. Below is an illustration to understand how we create one wave with two radial gradients.

Showing three diagrams of CSS borders, each with a piece of the border and an accompanying snippet of CSS to achieve the effect.It shows how one part cuts a circular white shape out of a red rectangle. The second part showing how to create a red circle shape. The third part shows two radial gradients used to position the two circles so they combine to create the wave shape.

We repeat that shape at the bottom plus a linear gradient at the top and we get the wavy border at the bottom side.

mask:    linear-gradient(#000 0 0) top/100% calc(100% - {2*size}) no-repeat,   radial-gradient(circle {size} at 75% 100%,#0000 98%,#000) 50% calc(100% - {size})/{4*size} {size} repeat-x,   radial-gradient(circle closest-side at 25% 50%,#000 99%,#0000 101%) bottom/{4*size} {2*size} repeat-x;

We do the same process for the other sides as we did with the zig-zag and rounded CSS borders. All we need is to update a few variables to have a different wave for each side.

Showing part of the CSS for each side. You can find the full code over at the generator.

What about applying a wavy CSS border on all four sides? Will we have 9 gradients in total??”

Nope, and that’s because there is no demo where a wavy border is applied to all four sides. I was unable to find a combination of gradients that gives a good result on the corners. Maybe someone reading this knows a good approach? 😉

That’s borderline great stuff!

So, you know the ins and outs of my cool little online CSS border generator! Sure, you can use the code it spits out and do just fine — but now you have the secret sauce recipe that makes it work.

Specifically, we saw how gradients can be used to mask portions of an element. Then we went to work on multiple gradients to make certain shapes from those gradient CSS masks. And the result is a pattern that can be used along the edges of an element, creating the appearance of fancy borders that you might otherwise result to background-image for. Only this way, all it takes is swapping out some values to change the appearance rather than replace an entire raster image file or something.


Fancy CSS Borders Using Masks originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , ,
[Top]

Using SVG in WordPress (2 Helpful Plugin Recommendations)

There is a little legwork to do if you plan on using SVG in WordPress. For fair-enough reasons, WordPress doesn’t allow SVG out of the box. SVG is a markup syntax that has lots of power, including the ability to load other resources and run JavaScript. So, if WordPress were to blanket-ly allow SVG by default, users even with quite limited roles could upload SVG and cause problems, like XSS vulnerabilities.

But say that’s not a problem for your site and you just use SVG gosh darn it. First, let’s be clear what we mean by using SVG in WordPress: uploading SVG through the media uploader and using the SVG images within post content and as featured images.

There is nothing stopping you from, say, using SVG in your templates. Meaning inline <svg> or SVG files you link up as images in your template from your CSS or whatnot. That’s completely fine and you don’t need to do anything special for that to work in WordPress.

Example of Using SVG in WordPress. the media library is open and shows tile previews of different SVG files.

Taking matters into your own hands

What prevents you from using SVG in WordPress is that the Media Library Uploader rejects the file’s MIME type. To allow SVG in WordPress, you really just need this filter. This would go in your functions.php or a functionality plugin:

function cc_mime_types($ mimes) {   $ mimes['svg'] = 'image/svg+xml';   return $ mimes; } add_filter('upload_mimes', 'cc_mime_types');

But the problem after that is that the SVG file usually won’t display correctly in the various places it needs to, like the Media Library’s image previews, the Featured Image widget, and possibly even the classic or Block Editor. I have a snippet of CSS that can be injected to fix this. But — and this is kinda why I’m writing this new post — that doesn’t seem to work for me anymore, which has got me thinking.

Plugins for using SVG in WordPress

I used to think, eh, why bother, it’s so little code to allow this might that I may as well just do it myself with the function. But WordPress, of course, has a way of shifting over time, and since supporting SVG isn’t something WordPress is going to do out of the box, this is actually a great idea for a plugin to handle. That way, the SVG plugin can evolve to handle quirks as WordPress evolves and, theoretically, if enough people use the SVG plugin, it will be maintained.

So, with that, here are a couple of plugin recommendations for using SVG in WordPress.

SVG Support

This is the one I’ve been using lately and it seems to work great for me.

Screenshot of the SVG Support plugin for WordPress in the WordPress Plugin Directory.

I just install it, activate it, and do nothing else. It does have a settings screen, but I don’t need any of those things. I really like how it asks you if it’s OK to load additional CSS on the front-end (for me, it’s not OK, so I leave it off) — although even better would be for the plugin to show you what it’s going to load so you can add it to your own CSS if you want.

The setting to restrict uploading SVG in WordPress to admins is smart, although if you want to be more serious about SVG safety, you could use this next plugin instead…

Safe SVG

This one hasn’t been updated in years, but it goes the extra mile for SVG safety in that it literally sanitizes SVG files as you upload them, and even optimizes them while it adds the SVG in WordPress.

Screenshot of the Safe SVG plugin in the WordPress Plugin Directory.

We have fairly tight editorial control over authors and such here on this site, so the security aspects of this SVG plugin aren’t a big worry to me. Plus, I like to be in charge of my own SVG optimization, so this one isn’t as perfect for me, though I’d probably recommend it to a site with less technical expertise at the site owner level.


Looks like there is Easy SVG Support as well, but it doesn’t seem to be as nice as the Support SVG plugin and hasn’t been updated recently, so I can’t recommend that.

What plugins have you successfully tried for using SVG in WordPress? Any recommendations you’d like to add?


Using SVG in WordPress (2 Helpful Plugin Recommendations) originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , ,
[Top]