Tag: Adding

Adding Box Shadows to WordPress Blocks and Elements

I stumbled across this tweet from Ana Segota looking for a way to add a CSS box-shadow to a button’s hover state in WordPress in the theme.json file.

She’s asking because theme.json is where WordPress wants us to start moving basic styles for block themes. Traditionally, we’d do any and all styling in style.css when working in a “classic” theme. But with the default Twenty Twenty-Three (TT3) theme that recently shipped with WordPress 6.1 moving all of its styles to theme.json, we’re getting closer and closer to being able to do the same with our own themes. I covered this in great detail in a recent article.

I say “closer and closer” because there are still plenty of CSS properties and selectors that are unsupported in theme.json. For example, if you’re hoping to style something with like perspective-origin in theme.json, it just won’t happen — at least as I’m writing this today.

Ana is looking at box-shadow and, luckily for her, that CSS property is supported by theme.json as of WordPress 6.1. Her tweet is dated Nov. 1, the same exact day that 6.1 released. It’s not like support for the property was a headline feature in the release. The bigger headlines were more related to spacing and layout techniques for blocks and block themes.

Here’s how we can apply a box-shadow to a specific block — say the Featured Image block — in theme.json:

{   "version": 2,   "settings": {},   // etc.   "styles": {     "blocks" :{       "core/post-featured-image": {         "shadow": "10px 10px 5px 0px rgba(0, 0, 0, 0.66)"       }     }   } }

Wondering if the new color syntax works? Me too! But when I tried — rgb(0 0 0 / 0.66) — I got nothing. Perhaps that’s already in the works or could use a pull request.

Easy, right? Sure, it’s way different than writing vanilla CSS in style.css and takes some getting used to. But it is indeed possible as of the most recent WordPress release.

And, hey, we can do the same thing to individual “elements”, like a button. A button is a block in and of itself, but it can also be a nested block within another block. So, to apply a box-shadow globally to all buttons, we’d do something like this in theme.json:

{   "version": 2,   "settings": {},   // etc.   "styles": {     "elements": {       "button": {         "shadow": "10px 10px 5px 0px rgba(0,0,0,0.66)"       }     }   } }

But Ana wants to add the shadow to the button’s :hover state. Thankfully, support for styling interactive states for certain elements, like buttons and links, using pseudo-classes — including :hover, :focus, :active, and :visited — also gained theme.json support in WordPress 6.1.

{   "version": 2,   "settings": {},   // etc.   "styles": {     "elements": {       "button": {         ":hover": {           "shadow": "10px 10px 5px 0px rgba(0,0,0,0.66)"         }       }     }   } }

If you’re using a parent theme, you can certainly override a theme’s styles in a child theme. Here, I am completely overriding TT3’s button styles.

View full code
{   "version": 2,   "settings": {},   // etc.   "styles": {     "elements": {       "button": {         "border": {           "radius": "0"         },         "color": {           "background": "var(--wp--preset--color--tertiary)",           "text": "var(--wp--preset--color--contrast)"         },         "outline": {           "offset": "3px",           "width": "3px",           "style": "dashed",           "color": "red"         },         "typography": {           "fontSize": "var(--wp--preset--font-size--medium)"         },         "shadow": "5px 5px 5px 0px rgba(9, 30, 66, 0.25), 5px 5px 5px 1px rgba(9, 30, 66, 0.08)",         ":hover": {           "color": {             "background": "var(--wp--preset--color--contrast)",             "text": "var(--wp--preset--color--base)"           },           "outline": {             "offset": "3px",             "width": "3px",             "style": "solid",             "color": "blue"           }         },         ":focus": {           "color": {             "background": "var(--wp--preset--color--contrast)",             "text": "var(--wp--preset--color--base)"           }         },         ":active": {           "color": {             "background": "var(--wp--preset--color--secondary)",             "text": "var(--wp--preset--color--base)"           }         }       }     }   } }

Here’s how that renders:

Showing two red buttons with box shadows.
The button’s natural state (left) and it’s hovered state (right)

Another way to do it: custom styles

The recently released Pixl block theme provides another example of real-world usage of the box-shadow property in theme.json using an alternative method that defines custom values. In the theme, a custom box-shadow property is defined as .settings.custom.shadow:

{   "version": 2,   "settings": {     // etc.      "custom": {       // etc.       "shadow": "5px 5px 0px -2px var(--wp--preset--color--background), 5px 5px var(--wp--preset--color--foreground)"     },     // etc.   } }

Then, later in the file, the custom shadow property is called on a button element:

{   "version": 2,   "settings": {     // etc.   },   "styles": {     "elements": {       "button": {         // etc.         "shadow": "var(--wp--custom--shadow) !important",         // etc.         ":active": {           // etc.           "shadow": "2px 2px var(--wp--preset--color--primary) !important"         }       },     // etc.   } }

I’m not totally sure about the use of !important in this context. My hunch is that it’s an attempt to prevent overriding those styles using the Global Styles UI in the Site Editor, which has high specificity than styles defined in theme.json. Here’s an anchored link to more information from my previous article on managing block theme styles.

Update: Turns out there was a whole discussion about this in Pull Request #34689, which notes that it was addressed in WordPress 5.9.

And there’s more…

In addition to shadows, the CSS outline property also gained theme.json support in WordPress 6.1 and can be applied to buttons and their interactive states. This GitHub PR shows a good example.

"elements": {   "button": {     "outline": {       "offset": "3px",       "width": "3px",       "style": "dashed",       "color": "red"     },     ":hover": {       "outline": {         "offset": "3px",         "width": "3px",         "style": "solid",         "color": "blue"       }     }   } }

You can also find the real examples of how the outline property works in other themes, including Loudness, Block Canvas, and Blockbase.

Wrapping up

Who knew there was so much to talk about with a single CSS property when it comes to block theming in WordPress 6.1? We saw the officially supported methods for setting a box-shadow on blocks and individual elements, including the interactive states of a button element. We also checked out how we could override shadows in a child theme. And, finally, we cracked open a real-world example that defines and sets shadows in a custom property.

You can find more detailed in-depth discussions about the WordPress and it’s box-shadow implementation in this GitHub PR. There is also a GitHub proposal for adding UI directly in WordPress to set shadow values on blocks — you can jump directly to an animated GIF showing how that would work.

Speaking of which, Justin Tadlock recently developed a block that renders a progress bar and integrated box shadow controls into it. He shows it off in this video:

More information

If you’d like to dig deeper into the box-shadow and other CSS properties that are supported by the theme.json file in a block theme, here are a couple of resources you can use:


Adding Box Shadows to WordPress Blocks and Elements originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , ,

Adding Fluid Typography Support to WordPress Block Themes

Fluid typography is a fancy way of “describing font properties, such as size or line height, that scale fluidly according to the size of the viewport”. It’s also known by other names, like responsive typography, flexible type, fluid type, viewport sized typography, fluid typography, and even responsive display text.

Here is an example of fluid typography that you can play live (courtesy of MDN documentation). CSS-Tricks has covered fluid typography extensively as well. But the point here is not to introduce you to fluid typography, but how to use it. More specifically, I want to show you how to implement fluid typography in WordPress 6.1 which recently introduced a fluid type feature directly in the WordPress Block Editor.

Open up your style.css file, slap in a style rule with fancy clamp()-ing on the font-size property, and good to go, right? Sure, that’ll give you fluid text, but to get Block Editor controls that make it possible to apply fluid type anywhere on your WordPress site? That requires a different approach in these block-ified days.

Fluid typography support in Gutenberg

Some WordPress theme developers have been using the clamp() function to define a fluid font-size, in their WordPress themes, even in newer “block” themes such as Twenty Twenty-Two, Twenty Twenty-Three, and others.

But the Gutenberg plugin — the one that contains experimental development for WordPress Block and Site Editor features — introduced support for fluid typography starting in version 13.8. That opened the door for implementing at a theme level so that fluid type can be applied to specific elements and blocks directly in the Block Editor. CSS-Tricks was even given a shout-out in the Pull Request that merged the feature.

That work became part of WordPress Core in WordPress 6.1. Rich Tabor, one of the earlier advocates of fluid typography in the Block Editor says:

[Fluid typography] is also a part of making WordPress more powerful, while not more complicated (which we all know is quite the challenge). […] Fluid typography just works. Actually, I think it works great.

This Make WordPress post highlights the approach taken to support the feature at the block level so that a fluid font size is applied to blocks dynamically by default. There are some benefits to this, of course:

  • It provides a way for theme authors to activate fluid typography without worrying about implementing it in code.
  • It applies fluid typography to specific typographical entities, such as elements or blocks in a maintainable and reusable way.
  • It allows flexibility in terms of font size units (e.g. px, rem, em, and %).

Now that this new feature is available in the WordPress Block Editor by default, theme authors can apply uniform fluid typography without writing additional code.

Blocks that support typography and spacing settings

Gutenberg 14.1 released on September 16, 2022, and introduced typographic settings on a bunch of blocks. That means the text settings for those blocks were set in CSS before and had to be changed in CSS as well. But those blocks now provide font and spacing controls in the Block Editor interface.

Illustrated list of WordPress blocks that received font and spacing controls in the Gutenberg plugin. There are 31 total blocks.

That work is currently slated to be added to WordPress 6.1, as detailed in this Make WordPress blog post. And with it is an expanded number of blocks that with typography support.

Illustrated list of 60 WordPress blocks gaining typography and font size support in WordPress 6.1.
WordPress blocks that will support typography settings in the upcoming WordPress 6.1 release.

Declaring fluid type in a WordPress block theme

So, how do we put this new fluid typography to use in WordPress? The answer is in theme.json, a new-ish file specific to block themes that contains a bunch of theme configurations in key:value pairs.

Let’s look at a rule for a large font in theme.json where contentSize: 768px and we’re working with a widesize: 1600px layout. This is how we can specify a CSS font-size using the clamp() function:

"settings": {   "appearanceTools": true,   "layout": {     "contentSize": "768px",     "wideSize": "1600px"   },   "typography": {     "fontSizes": [        {         "name": "Large",         "size": "clamp(2.25rem, 6vw, 3rem)",         "slug": "large"       }     ]   } }

As of WordPress 6.1, only rem, em and px units are supported.

That’s great and works, but with the new fluid type feature we would actually use a different approach. First, we opt into fluid typography on settings.typography, which has a new fluid property:

"settings": {   "typography": {     "fluid": true   } }

Then we specify our settings.fontSizes like before, but with a new fluidSize property where we can set the min and max size values for our fluid type range.

"settings": {   "appearanceTools": true,   "layout": {     "contentSize": "768px",     "wideSize": "1600px"   },   "typography": {     "fontSizes": [        {         "size": "2.25rem",         "fluidSize": {           "min": "2.25rem",           "max": "3rem"         },         "slug": "large",         "name": "Large"       }     ]   } }

That’s really it. We just added fluid type to a font size called “Large” with a range that starts at 2.25rem and scales up to 3rem. Now, we can apply the “Large” font to any block with font settings.

How does this works under the hood? Rich Tabor offers a nice explanation, as does this Pull Request in GitHub. In short, WordPress converts the theme.json properties into the following CSS rule:

.has-large-font-size {   font-size: clamp(36px, calc(2.25rem + ((1vw - 7.68px) * 1.4423)), 48px); }

…which is applied to the element, say a Paragraph Block:

<p class="has-large-font-size">...</p>

Initially, I found it hard to understand and wrap around in my mind the concept of the CSS clamp() function without also learning about the min(), max(), and calc() functions. This calculator tool helped me quite a bit, especially for determining which values to use in my own theme projects.

For demonstration purposes, let’s use the calculator to define our font-size range so that the size is 36px at a 768px viewport width and 48px at a 1600px viewport width.

Entering values into the online calculator for fluid typography.

The calculator automatically generates the following CSS:

/* 36px @ 768px increasing to 48px @ 1600px */ font-size: clamp(36px, calc(2.25rem + ((1vw - 7.68px) * 1.4423)), 48px);

The calculator provide options to select input units as px, rem, and em. If we select rem unit, the calculator generates the following clamp() value:

/* 2.25rem @ 48rem increasing to 3rem @ 100rem */ font-size: clamp(2.25rem, calc(2.25rem + ((1vw - 0.48rem) * 1.4423)), 3rem);

So, those minimum and maximum values correspond to the the fluidSize.min and fluidSize.max values in theme.json. The min value is applied at viewports that are 768px wide and below. Then the font-size scales up as the viewport width grows. If the viewport is wider than 1600px, the max is applied and the font-size is capped there.

Examples

There are detailed testing instructions in the merged Pull Request that introduced the feature. There are even more testing instructions from Justin Tadlock’s pre-prelease post on Make WordPress.

Example 1: Setting a new fluid font setting

Let’s start with Justin’s set of instructions. I used in a modified version of the default Twenty Twenty-Three theme that is currently under development.

First, let’s make sure we’re running the Gutenberg plugin (13.8 and up) or WordPress 6.1, then opt into fluid type on the settings.typography.fluid property in the theme.json file:

{   "version": 2,   "settings": {     "appearanceTools": true,     "layout": {       "contentSize": "768px",       "wideSize": "1600px"     },     "typography": {       "fluid": true     }   } }

Now, let’s drop the settings.typography.fontSizes examples in there:

{   "version": 2,   "settings": {     "appearanceTools": true,     "layout": {       "contentSize": "768px",       "wideSize": "1600px"     },     "typography": {       "fluid": true       "fontSizes": [         {           "name": "Normal",           "size": "1.125rem",           "fluid": {             "min": "1rem",             "max": "1.5rem"           },           "slug": "normal"         }       ]     }   } }

If everything is working correctly, we can now head into the WordPress Block Editor and apply the “Normal” font setting to our block:

The WordPress Block Editor interface showing a paragraph block and the fluid typography settings for it.

Nice! And if we save and inspect that element on the front end, this is the markup:

Inspecting the WordPress Paragraph block in DevTools.

Very good. Now let’s make sure the CSS is actually there:

DevTools showing the font-size custom property for the WordPress Paragraph block's fluid typography.

Good, good. Let’s expose that CSS custom property to see if it’s really clampin’ things:

Revealing the custom property value in DevTools, showing a CSS clamp function.

Looks like everything is working just as we want it! Let’s look at another example…

Example 2: Excluding a font setting from fluid type

This time, let’s follow the instructions from the merged Pull Request with a nod to this example by Carolina Nymark that shows how we can disable fluid type on a specific font setting.

I used the empty theme as advised in the instructions and opened up the theme.json file for testing. First, we opt into fluid type exactly as we did before:

{   "version": 2,   "settings": {     "appearanceTools": true,     "layout": {       "contentSize": "768px",       "wideSize": "1000px"     },     "typography": {       "fluid": true     }   } }

This time, we’re working with smaller wideSize value of 1000px instead of 1600px. This should allow us to see fluid type working in an exact range.

OK, on to defining some custom font sizes under settings.typography.fontSizes:

{   "version": 2,   "settings": {     "typography": {       "fluid": true,       "fontSizes": [         {           "size": ".875rem",           "fluid": {             "min": "0.875rem",             "max": "1rem"         },           "slug": "small",           "name": "Small"         },         {           "size": "1rem",           "fluid": {             "min": "1rem",             "max": "1.5rem"           },           "slug": "normal",           "name": "Normal"         },         {           "size": "1.5rem",           "fluid": {             "min": "1.5rem",             "max": "2rem"           },           "slug": "large",           "name": "Large"         },         {           "size": "2.25rem",           "fluid": false,           "slug": "x-large",           "name": "Extra large"         }       ]     }   } }

Notice that we’ve applied the fluid type feature only on the “Normal”, “Medium”, and “Large” font settings. “Extra Large” is the odd one out where the fluid object is set to false.

the WordPress Block Editor interface with four Paragraph blocks, each at a different font size setting.

What WordPress does from here — via the Gutenberg style engine — is apply the properties we set into CSS clamp() functions for each font size setting that has opted into fluid type and a single size value for the Extra Large setting:

--wp--preset--font-size--small: clamp(0.875rem, 0.875rem + ((1vw - 0.48rem) * 0.24), 1rem); --wp--preset--font-size--medium: clamp(1rem, 1rem + ((1vw - 0.48rem) * 0.962), 1.5rem); --wp--preset--font-size--large: clamp(1.5rem, 1.5rem + ((1vw - 0.48rem) * 0.962), 2rem); --wp--preset--font-size--x-large: 2.25rem;

Let’s check the markup on the front end:

Inspecting the WordPress Paragraph blocks in DevTools.

Good start! Let’s confirm that the .has-x-large-font-size class is excluded from fluid type:

Showing the font-size custom property for the Extra Large font setting in DevTools.

If we expose the --wp--preset--font-size--x-large variable, we’ll see it’s set to 2.25rem.

Revealing the Extra Large font size custom property value, showing 2.25rem.

That’s exactly what we want!

Block themes that support fluid typography

Many WordPress themes already make use of the clamp() function for fluid type in both block and classic themes. A good example of fluid typography use is the recently released Twenty Twenty-Three default theme.

I’ve reviewed all the block themes from WordPress Block Theme directory, examining theme.json file of each theme and to see just how many block themes currently support fluid typography — not the new feature since it’s still in the Gutenberg plugin as of this writing — using the CSS clamp() function. Of the 146 themes I reviewed, the majority of them used a clamp() function to define spacing. A little more than half of them used clamp() to define font sizes. The Alara theme is the only one to use clamp() for defining the layout container sizes.

Understandably, only a few recently released themes contain the new fluid typography feature. But here are the ones I found that define it in theme.json:

And if you read my previous post here on CSS-Tricks, the TT2 Gopher Blocks theme I used for the demo has also been updated to support the fluid typography feature.

Selected reactions to the WordPress fluid typography features

Having fluid typography in WordPress at the settings level is super exciting! I thought I’d share some thoughts from folks in the WordPress developer community who have commented on it.

Matias Ventura, the lead architect of the Gutenberg project:

Rich Tabor:

As one of the bigger efforts towards making publishing beautifully rich pages in WordPress, fluid typography is a pretty big experience win for both the folks building with WordPress — and those consuming the content.

Automattic developer Ramon Dodd commented in the Pull Request:

Contrast that idea with font sizes that respond to specific viewport sizes, such as those defined by media queries, but do nothing in between those sizes. theme.json already allows authors to insert their own fluid font size values. This won’t change, but this PR offers it to folks who don’t want to worry about the implementation details.

Nick Croft, author of GenesisWP:

Brian Garner, designer and principal developer advocate at WPEngine:

A few developers think some features should be an opt-in. Jason Crist of Automattic says:

I love the power of fluid typography, however I also don’t believe that it should just be enabled by default. It’s usage (and the details of it) are important design decisions that should be made carefully.

You can also find a bunch more comments in the official testing instructions for the feature.

Wrapping up

The fluid typography feature in WordPress is still in active development at the time of this writing. So, right now, theme authors should proceed to use it, but with caution and expect some possible changes before it is officially released. Justin cautions theme authors using this feature and suggests to keep eye on the following two GitHub issues:

There is also still lots of ongoing work on typography and other design-related WordPress tools. If you’re interested, watch this typography tracking GitHub ticket and design tools related GitHub issues.

Resources

I used the following articles when researching fluid type and how WordPress is implementing it as a feature.

Tutorials and opinions

CSS-Tricks


Adding Fluid Typography Support to WordPress Block Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , , ,
[Top]

Adding Custom GitHub Badges to Your Repo

If you’ve spent time looking at open-source repos on GitHub, you’ve probably noticed that most of them use badges in their README files. Take the official React repository, for instance. There are GitHub badges all over the README file that communicate important dynamic info, like the latest released version and whether the current build is passing.

Showing the header of React's repo displaying GitHub badges.

Badges like these provide a nice way to highlight key information about a repository. You can even use your own custom assets as badges, like Next.js does in its repo.

Showing the Next.js repo header with GitHub badges.

But the most useful thing about GitHub badges by far is that they update by themselves. Instead of hardcoding values into your README, badges in GitHub can automatically pick up changes from a remote server.

Let’s discuss how to add dynamic GitHub badges to the README file of your own project. We’ll start by using an online generator called badgen.net to create some basic badges. Then we’ll make our badges dynamic by hooking them up to our own serverless function via Napkin. Finally, we’ll take things one step further by using our own custom SVG files.

Showing three examples of custom GitHub badges including Apprentice, Intermediate, and wizard skill levels.

First off: How do badges work?

Before we start building some badges in GitHub, let’s quickly go over how they are implemented. It’s actually very simple: badges are just images. README files are written in Markdown, and Markdown supports images like so:

![alt text](path or URL to image)

The fact that we can include a URL to an image means that a Markdown page will request the image data from a server when the page is rendered. So, if we control the server that has the image, we can change what image is sent back using whatever logic we want!

Thankfully, we have a couple options to deploy our own server logic without the whole “setting up the server” part. For basic use cases, we can create our GitHub badge images with badgen.net using its predefined templates. And again, Napkin will let us quickly code a serverless function in our browser and then deploy it as an endpoint that our GitHub badges can talk to.

Making badges with Badgen

Let’s start off with the simplest badge solution: a static badge via badgen.net. The Badgen API uses URL patterns to create templated badges on the fly. The URL pattern is as follows:

https://badgen.net/badge/:subject/:status/:color?icon=github

There’s a full list of the options you have for colors, icons, and more on badgen.net. For this example, let’s use these values:

  • :subject : Hello
  • :status: : World
  • :color: : red
  • :icon: : twitter

Our final URL winds up looking like this:

https://badgen.net/badge/hello/world/red?icon=twitter

Adding a GitHub badge to the README file

Now we need to embed this badge in the README file of our GitHub repo. We can do that in Markdown using the syntax we looked at earlier:

![my badge](https://badgen.net/badge/hello/world/red?icon=twitter)

Badgen provides a ton of different options, so I encourage you to check out their site and play around! For instance, one of the templates lets you show the number of times a given GitHub repo has been starred. Here’s a star GitHub badge for the Next.js repo as an example:

https://badgen.net/github/stars/vercel/next.js

Pretty cool! But what if you want your badge to show some information that Badgen doesn’t natively support? Luckily, Badgen has a URL template for using your own HTTPS endpoints to get data:

https://badgen.net/https/url/to/your/endpoint

For example, let’s say we want our badge to show the current price of Bitcoin in USD. All we need is a custom endpoint that returns this data as JSON like this:

{   "color": "blue",   "status": "$ 39,333.7",   "subject": "Bitcoin Price USD" }

Assuming our endpoint is available at https://some-endpoint.example.com/bitcoin, we can pass its data to Badgen using the following URL scheme:

https://badgen.net/https/some-endpoint.example.com/bitcoin
GitHub badge. On the left is a gray label with white text. On the right is a blue label with white text showing the price of Bitcoin.
The data for the cost of Bitcoin is served right to the GitHub badge.

Even cooler now! But we still have to actually create the endpoint that provides the data for the GitHub badge. 🤔 Which brings us to…

Badgen + Napkin

There’s plenty of ways to get your own HTTPS endpoint. You could spin up a server with DigitalOcean or AWS EC2, or you could use a serverless option like Google Cloud Functions or AWS Lambda; however, those can all still become a bit complex and tedious for our simple use case. That’s why I’m suggesting Napkin’s in-browser function editor to code and deploy an endpoint without any installs or configuration.

Head over to Napkin’s Bitcoin badge example to see an example endpoint. You can see the code to retrieve the current Bitcoin price and return it as JSON in the editor. You can run the code yourself from the editor or directly use the endpoint.

To use the endpoint with Badgen, work with the same URL scheme from above, only this time with the Napkin endpoint:

https://badgen.net/https/napkin-examples.npkn.net/bitcoin-badge

More ways to customize GitHub badges

Next, let’s fork this function so we can add in our own custom code to it. Click the “Fork” button in the top-right to do so. You’ll be prompted to make an account with Napkin if you’re not already signed in.

Once we’ve successfully forked the function, we can add whatever code we want, using any npm modules we want. Let’s add the Moment.js npm package and update the endpoint response to show the time that the price of Bitcoin was last updated directly in our GitHub badge:

import fetch from 'node-fetch' import moment from 'moment'  const bitcoinPrice = async () => {   const res = await fetch("<https://blockchain.info/ticker>")   const json = await res.json()   const lastPrice = json.USD.last+""    const [ints, decimals] = lastPrice.split(".")    return ints.slice(0, -3) + "," + ints.slice(-3) + "." + decimals }  export default async (req, res) => {   const btc = await bitcoinPrice()    res.json({     icon: 'bitcoin',     subject: `Bitcoin Price USD ($ {moment().format('h:mma')})`,     color: 'blue',     status: `$ $ {btc}`   }) }
Deploy the function, update your URL, and now we get this.

You might notice that the badge takes some time to refresh the next time you load up the README file over at GitHub. That’s is because GitHub uses a proxy mechanism to serve badge images.

GitHub serves the badge images this way to prevent abuse, like high request volume or JavaScript code injection. We can’t control GitHub’s proxy, but fortunately, it doesn’t cache too aggressively (or else that would kind of defeat the purpose of badges). In my experience, the TTL is around 5-10 minutes.

OK, final boss time.

Custom SVG badges with Napkin

For our final trick, let’s use Napkin to send back a completely new SVG, so we can use custom images like we saw on the Next.js repo.

A common use case for GitHub badges is showing the current status for a website. Let’s do that. Here are the two states our badge will support:

Badgen doesn’t support custom SVGs, so instead, we’ll have our badge talk directly to our Napkin endpoint. Let’s create a new Napkin function for this called site-status-badge.

The code in this function makes a request to example.com. If the request status is 200, it returns the green badge as an SVG file; otherwise, it returns the red badge. You can check out the function, but I’ll also include the code here for reference:

import fetch from 'node-fetch'  const site_url = "<https://example.com>"  // full SVGs at <https://napkin.io/examples/site-status-badge> const customUpBadge = '' const customDownBadge = ''  const isSiteUp = async () => {   const res = await fetch(site_url)   return res.ok }  export default async (req, res) => {   const forceFail = req.path?.endsWith('/400')    const healthy = await isSiteUp()   res.set('content-type', 'image/svg+xml')   if (healthy && !forceFail) {     res.send(Buffer.from(customUpBadge).toString('base64'))   } else {     res.send(Buffer.from(customDownBadge).toString('base64'))   } }

Odds are pretty low that the example.com site will ever go down, so I added the forceFail case to simulate that scenario. Now we can add a /400 after the Napkin endpoint URL to try it:

![status up](https://napkin-examples.npkn.net/site-status-badge/) ![status down](https://napkin-examples.npkn.net/site-status-badge/400)

Very nice 😎


And there we have it! Your GitHub badge training is complete. But the journey is far from over. There’s a million different things where badges like this are super helpful. Have fun experimenting and go make that README sparkle! ✨


Adding Custom GitHub Badges to Your Repo originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Adding Tailwind CSS to New and Existing WordPress Themes

In the 15 or so years since I started making WordPress websites, nothing has had more of an impact on my productivity — and my ability to enjoy front-end development — than adding Tailwind CSS to my workflow (and it isn’t close).

When I began working with Tailwind, there was an up-to-date, first-party repository on GitHub describing how to use Tailwind with WordPress. That repository hasn’t been updated since 2019. But that lack of updates isn’t a statement on Tailwind’s utility to WordPress developers. By allowing Tailwind to do what Tailwind does best while letting WordPress still be WordPress, it’s possible to take advantage of the best parts of both platforms and build modern websites in less time.

The minimal setup example in this article aims to provide an update to that original setup repository, revised to work with the latest versions of both Tailwind and WordPress. This approach can be extended to work with all kinds of WordPress themes, from a forked default theme to something totally custom.

Why WordPress developers should care about Tailwind

Before we talk about setup, it’s worth stepping back and discussing how Tailwind works and what that means in a WordPress context.

Tailwind allows you to style HTML elements using pre-existing utility classes, removing the need for you to write most or all of your site’s CSS yourself. (Think classes like hidden for display: hidden or uppercase for text-transform: uppercase.) If you’ve used frameworks like Bootstrap and Foundation in the past, the biggest difference you’ll find with Tailwind CSS is its blank-slate approach to design combined with the lightness of being CSS-only, with just a CSS reset included by default. These properties allow for highly optimized sites without pushing developers towards an aesthetic built into the framework itself.

Also unlike many other CSS frameworks, it’s infeasible to load a “standard” build of Tailwind CSS from an existing CDN. With all of its utility classes included, the generated CSS file would simply be too large. Tailwind offers a “Play CDN,” but it’s not meant for production, as it significantly reduces Tailwind’s performance benefits. (It does come in handy, though, if you want to do some rapid prototyping or otherwise experiment with Tailwind without actually installing it or setting up a build process.)

This need to use Tailwind’s build process to create a subset of the framework’s utility classes specific to your project makes it important to understand how Tailwind decides which utility classes to include, and how this process affects the use of utility classes in WordPress’s editor.

And, finally, Tailwind’s aggressive Preflight (its version of a CSS reset) means some parts of WordPress are not well-suited to the framework with its default settings.

Let’s begin by looking at where Tailwind works well with WordPress.

Where Tailwind and WordPress work well together

In order for Tailwind to work well without significant customization, it needs to act as the primary CSS for a given page; this eliminates a number of use cases within WordPress.

If you’re building a WordPress plugin and you need to include front-end CSS, for example, Tailwind’s Preflight would be in direct conflict with the active theme. Similarly, if you need to style the WordPress administration area — outside of the editor — the administration area’s own styles may be overridden.

There are ways around both of these issues: You can disable Preflight and add a prefix to all of your utility classes, or you could use PostCSS to add a namespace to all of your selectors. Either way, your configuration and workflow are going to get more complicated.

But if you’re building a theme, Tailwind is an excellent fit right out of the box. I’ve had success creating custom themes using both the classic editor and the block editor, and I’m optimistic that as full-site editing matures, there will be a number of full-site editing features that work well alongside Tailwind.

In her blog post “Gutenberg Full Site Editing does not have to be full,” Tammie Lister describes full-site editing as a set of separate features that can be adopted in part or in full. It’s unlikely full-site editing’s Global Styles functionality will ever work with Tailwind, but many other features probably will.

So: You’re building a theme, Tailwind is installed and configured, and you’re adding utility classes with a smile on your face. But will those utility classes work in the WordPress editor?

With planning, yes! Utility classes will be available to use in the editor so long as you decide which ones you’d like to use in advance. You’re unable to open up the editor and use any and all Tailwind utility classes; baked into Tailwind’s emphasis on performance is the limitation of only including the utility classes your theme uses, so you need to let Tailwind know in advance which ones are required in the editor despite them being absent elsewhere in your code.

There are a number of ways to do this: You can create a safelist within your Tailwind configuration file; you can include comments containing lists of classes alongside the code for custom blocks you’ll want to style in the block editor; you could even just create a file listing all of your editor-specific classes and tell Tailwind to include it as one of the source files it monitors for class names.

The need to commit to editor classes in advance has never held me back in my work, but this remains the aspect of the relationship between Tailwind and WordPress I get asked about the most.

A minimal WordPress theme with a minimal Tailwind CSS integration

Let’s start with the most basic WordPress theme possible. There are only two required files:

  • style.css
  • index.php

We’ll generate style.css using Tailwind. For index.php, let’s start with something simple:

<!doctype html> <html lang="en">   <head>     <?php wp_head(); ?>     <link rel="stylesheet" href="<?php echo get_stylesheet_uri(); ?>" type="text/css" media="all" />   </head>   <body>     <?php     if ( have_posts() ) {       while ( have_posts() ) {         the_post();         the_title( '<h1 class="entry-title">', '</h1>' );         ?>         <div class="entry-content">           <?php the_content(); ?>         </div>         <?php       }     }     ?>   </body> </html>

There are a lot of things a WordPress theme should do that the above code doesn’t — things like pagination, post thumbnails, enqueuing stylesheets instead of using link elements, and so on — but this will be enough to display a post and test that Tailwind is working as it should.

On the Tailwind side, we need three files:

  • package.json
  • tailwind.config.js
  • An input file for Tailwind

Before we go any further, you’re going to need npm. If you’re uncomfortable working with it, we have a beginner’s guide to npm that is a good place to start!

Since there is no package.json file yet, we’ll create an empty JSON file in the same folder with index.php by running this command in our terminal of choice:

echo {} > ./package.json

With this file in place, we can install Tailwind:

npm install tailwindcss --save-dev

And generate our Tailwind configuration file:

npx tailwindcss init

In our tailwind.config.js file, all we need to do is tell Tailwind to search for utility classes in our PHP files:

module.exports = {   content: ["./**/*.php"],   theme: {     extend: {},   },   plugins: [], }

If our theme used Composer, we’d want to ignore the vendor directory by adding something like "!**/vendor/**" to the content array. But if all of your PHP files are part of your theme, the above will work!

We can name our input file anything we want. Let’s create a file called tailwind.css and add this to it:

/*! Theme Name: WordPress + Tailwind */  @tailwind base; @tailwind components; @tailwind utilities;

The top comment is required by WordPress to recognize the theme; the three @tailwind directives add each of Tailwind’s layers.

And that’s it! We can now run the following command:

npx tailwindcss -i ./tailwind.css -o ./style.css --watch

This tells the Tailwind CLI to generate our style.css file using tailwind.css as the input file. The --watch flag will continuously rebuild the style.css file as utility classes are added or removed from any PHP file in our project repository.

That’s as simple as a Tailwind-powered WordPress theme could conceivably be, but it’s unlikely to be something you’d ever want to deploy to production. So, let’s talk about some pathways to a production-ready theme.

Adding TailwindCSS to an existing theme

There are two reasons why you might want to add Tailwind CSS to an existing theme that already has its own vanilla CSS:

  • To experiment with adding Tailwind components to an already styled theme
  • To transition a theme from vanilla CSS to Tailwind

We’ll demonstrate this by installing Tailwind inside Twenty Twenty-One, the WordPress default theme. (Why not Twenty Twenty-Two? The most recent WordPress default theme is meant to showcase full-site editing and isn’t a good fit for a Tailwind integration.)

To start, you should download and install the theme in your development environment if it isn’t installed there. We only need to follow a handful of steps after that:

  • Navigate to the theme folder in your terminal.
  • Because Twenty Twenty-One already has its own package.json file, install Tailwind without creating a new one:
npm install tailwindcss --save-dev
  • Add your tailwind.config.json file:
npx tailwindcss init
  • Update your tailwind.config.json file to look the same as the one in the previous section.
  • Copy Twenty Twenty-One’s existing style.css file to tailwind.css.

Now we need to add our three @tailwind directives to the tailwind.css file. I suggest structuring your tailwind.css file as follows:

/* The WordPress theme file header goes here. */  @tailwind base;  /* All of the existing CSS goes here. */  @tailwind components; @tailwind utilities;

Putting the base layer immediately after the theme header ensures that WordPress continues to detect your theme while also ensuring the Tailwind CSS reset comes as early in the file as possible.

All of the existing CSS follows the base layer, ensuring that these styles take precedence over the reset.

And finally, the components and utilities layers follow so they can take precedence over any CSS declarations with the same specificity.

And now, as with our minimal theme, we’ll run the following command:

npx tailwindcss -i ./tailwind.css -o ./style.css --watch

With your new style.css file now being generated each time you change a PHP file, you should check your revised theme for minor rendering differences from the original. These are caused by Tailwind’s CSS reset, which resets things a bit further than some themes might expect. In the case of Twenty Twenty-One, the only fix I made was to add text-decoration-line: underline to the a element.

With that rendering issue resolved, let’s add the Header Banner Component from Tailwind UI, Tailwind’s first-party component library. Copy the code from the Tailwind UI site and paste it immediately following the “Skip to content” link in header.php:

Showing a Tailwind CSS component on the front end of a WordPress theme.

Pretty good! Because we’re now going to want to use utility classes to override some of the existing higher-specificity classes built into the theme, we’re going to add a single line to the tailwind.config.js file:

module.exports = {   important: true,   content: ["./**/*.php"],   theme: {     extend: {},   },   plugins: [], }

This marks all Tailwind CSS utilities as !important so they can override existing classes with a higher specificity. (I’ve never set important to true in production, but I almost certainly would if I were in the process of converting a site from vanilla CSS to Tailwind.)

With a quick no-underline class added to the “Learn more” link and bg-transparent and border-0 added to the dismiss button, we’re all set:

Showing a Tailwind CSS component in the front end of a WordPress theme, but with more refined styles for the buttons and links.

It looks a bit jarring to see Tailwind UI’s components merged into a WordPress default theme, but it’s a great demonstration of Tailwind components and their inherent portability.

Starting from scratch

If you’re creating a new theme with Tailwind, your process will look a lot like the minimal example above. Instead of running the Tailwind CLI directly from the command line, you’ll probably want to create separate npm scripts for development and production builds, and to watch for changes. You may also want to create a separate build specifically for the WordPress editor.

If you’re looking for a starting point beyond the minimal example above — but not so far beyond that it comes with opinionated styles of its own — I’ve created a Tailwind-optimized WordPress theme generator inspired by Underscores (_s), once the canonical WordPress starter theme. Called _tw, this is the quick-start I wish I had when I first combined Tailwind with WordPress. It remains the first step in all of my client projects.

If you’re willing to go further from the structure of a typical WordPress theme and add Laravel Blade templates to your toolkit, Sage is a great choice, and they have a setup guide specific to Tailwind to get you started.


However you choose to begin, I encourage you to take some time to acclimate yourself to Tailwind CSS and to styling HTML documents using utility classes: It may feel unusual at first, but you’ll soon find yourself taking on more client work than before because you’re building sites faster than you used to — and hopefully, like me, having more fun doing it.


Adding Tailwind CSS to New and Existing WordPress Themes originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Adding CDN Caching to a Vite Build

Content delivery networks, or CDNs, allow you to improve the delivery of your website’s static resources, most notably, with CDN caching. They do this by serving your content from edge locations, which are located all over the world. When a user browses to your site, and your site requests resources from the CDN, the CDN will route that request to the nearest edge location. If that location has the requested resources, either from that user’s prior visit, or from another person, then the content will be served from cache. If not, the CDN will request the content from your underlying domain, cache it, and serve it.

There are countless CDNs out there, but for this post we’ll be using AWS CloudFront. We’ll look at setting up a CloudFront distribution to serve all our site’s assets: JavaScript files, CSS files, font files, etc. Then we’ll see about integrating it into a Vite build. If you’d like to learn more about Vite, I have an introduction here.

Setting up a CloudFront CDN distribution

Let’s jump right in and set up our CloudFront CDN distribution.

For any serious project, you should be setting up your serverless infrastructure with code, using something like the Serverless Framework, or AWS’s CDK. But to keep things simple, here, we’ll set up our CDN using the AWS console.

Head on over to the CloudFront homepage. At the top right, you should see an orange button to create a new distribution.

CloudFront CDN Distributions screen.

The creation screen has a ton of options, but for the most part the default selections will be fine. First and foremost, add the domain where your resources are located.

CloudFront CDN distribution creation screen.

Next, scroll down and find the Response headers policy dropdown, and choose “CORS-With-Preflight.”

CloudFront response headers settings.

Lastly, click the Create Distribution button at the bottom, and hopefully you’ll see your new distribution.

CloudFront CDN distribution overview screen.

Integrating the CDN with Vite

It’s one thing for our CDN to be set up and ready to serve our files. But it’s another for our site to actually know how to request them from our CDN. I’ll walk through integrating with Vite, but other build systems, like webpack or Rollup, will be similar.

When Vite builds our site, it maintains a “graph” of all the JavaScript and CSS files that various parts of our site import, and it injects the appropriate <script> tags, <link> tags, or import() statements to load what’s needed. What we need to do is tell Vite to request these assets from our CDN when in production. Let’s see how.

Open up your vite.config.ts file. First, we’ll need to know if we’re on the live site (production) or in development (dev).

const isProduction = process.env.NODE_ENV === "production"; 

This works since Vite sets this environment variable when we run vite build, which is what we do for production, as opposed to dev mode with hot module reloading.

Next we tell Vite to draw our assets from our CDN like so, setting the base property of our config object:

export default defineConfig({   base: isProduction ? process.env.REACT_CDN : "",

Be sure to set your REACT_CDN environment variable to your CDN’s location, which in this case, will be our CloudFront distribution’s location. Mine looks something (but not exactly) like this:

https://distributiondomainname.cloudfront.net

Watch your VitePWA settings!

As one final piece of cleanup, if you happen to be using the VitePWA plugin, be sure to reset your base property like this:

VitePWA({   base: "/",

Otherwise, your web.manifest file will have invalid settings and cause errors.

Let’s see the CDN work

Once you’re all set up, browse to your site, and inspect any of the network requests for your script or CSS files. For starters, the protocol should be h2.

Showing the assets served via CDN caching in DevTools. Each file name includes a unique random string of letters and numbers.

From there, you can peek into the response headers of any one of those files, and you should see some CloudFront data in there:

Screenshot of a response header.

Cache busting

It’s hard to talk about CDNs without mentioning cache busting. CDNs like CloudFront have functionality to manually “eject” items from cache. But for Vite-built assets, we get this “for free” since Vite adds fingerprinting, or hash codes, to the filenames of the assets it produces.

So Vite might turn a home.js file into home-abc123.js during a build, but then if you change that file and rebuild, it might become home-xyz987.js. That’s good, as it will “break the cache,” and the newly built file will not be cached, so the CDN will have to turn to our host domain for the actual content.

CDN caching for other static assets

JavaScript, CSS, and font files aren’t the only kinds of assets that can benefit from CDN caching. If you have an S3 bucket you’re serving images out of, consider setting up a CloudFront distribution for it as well. There are options specifically for S3 which makes it a snap to create. Not only will you get the same edge caching, but HTTP/2 responses, which S3 does not provide.

Advanced CDN practices

Integrating a CDN here was reasonably straightforward, but we’re only enjoying a fraction of the potential benefits. Right now, users will browse to our app, our server will serve our root HTML file, and then the user’s browser will connect to our CDN to start pulling down all our static assets.

Going further, we would want to serve our entire site from a CDN. That way, it can communicate with our web server as needed for non-static and non-cached assets.

Conclusion

CDNs are a great way to improve the performance of your site. They provide edge caching and HTTP/2 out of the box. Not only that, but they’re reasonably easy to set up. Now you have a new tool in your belt to both set up a CDN and integrate it with Vite.


Adding CDN Caching to a Vite Build originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , ,
[Top]

Adding Vite to Your Existing Web App

Vite (pronounced “veet”) is a newish JavaScript bundler. It comes batteries-included, requires almost no configuration to be useful, and includes plenty of configuration options. Oh—and it’s fast. Incredibly fast.

This post will walk through the process of converting an existing project to Vite. We’ll cover things like aliases, shimming webpack’s dotenv handling, and server proxying. In other words, we’re looking at how to move a project from its existing bundler to Vite. If you’re looking instead to start a fresh project, you’ll want to jump to their documentation.

Long story, short: the CLI will ask for your framework of choice—React, Preact, Svelte, Vue, Vanilla, or even lit-html—and whether you want TypeScript, then give you a fully functioning project.

Scaffold first! If you are interested in learning about integrating Vite into a legacy project, I’d still recommend scaffolding an empty project and poking around it a bit. At times, I’ll be pasting some clumps of code, but most of that comes straight from the default Vite template.

Our use case

What we’re looking at is based on my own experience migrating the webpack build of my booklist project (repo). There isn’t anything particularly special about this project, but it’s fairly big and old, and leaned hard on webpack. So, in that sense, it’s a good opportunity to see some of Vite’s more useful configuration options in action as we migrate to it.

What we won’t need

One of the most compelling reasons to reach for Vite is that it already does a lot right out of the box, incorporating many of the responsibilities from other frameworks so there are fewer dependencies and a more established baseline for configurations and conventions.

So, instead of starting by calling out what we need to get started, let’s go over all the common webpack things we don’t need because Vite gives them to us for free.

Static asset loading

We usually need to add something like this in webpack:

{   test: /.(png|jpg|gif|svg|eot|woff|woff2|ttf)$ /,   use: [     {       loader: "file-loader"     }   ] }

This takes any references to font files, images, SVG files, etc., and copies them over to your dist folder so they can be referenced from your new bundles. This comes standard in Vite.

Styles

I say “styles” as opposed to “css” intentionally here because, with webpack, you might have something like this:

{   test: /.s?css$ /,   use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"] },  // later  new MiniCssExtractPlugin({ filename: "[name]-[contenthash].css" }),

…which allows the application to import CSS or SCSS files. You’ll grow tired of hearing me say this, but Vite supports this out of the box. Just be sure to install Sass itself into your project, and Vite will handle the rest.

Transpilation / TypeScript

It’s likely your code is using TypeScript, and or non-standard JavaScript features, like JSX. If that’s the case, you’ll need to transpile your code to remove those things and produce plain old JavaScript that a browser (or JavaScript parser) can understand. In webpack that would look something like this:

{   test: /.(t|j)sx?$ /,   exclude: /node_modules/,   loader: "babel-loader" },

…with a corresponding Babel configuration to specify the appropriate plugins which, for me, looked like this:

{   "presets": ["@babel/preset-typescript"],   "plugins": [     "@babel/plugin-proposal-class-properties",     "@babel/plugin-syntax-dynamic-import",     "@babel/plugin-proposal-optional-chaining",     "@babel/plugin-proposal-nullish-coalescing-operator"   ] }

While I could have probably stopped using those first two plugins years ago, it doesn’t really matter since, as I’m sure you’ve guessed, Vite does this all for us. It takes your code, removes any TypeScript and JSX, and produces code supported by modern browsers.

If you’d like to support older browsers (and I’m not saying you should), then there’s a plugin for that.

node_modules

Surprisingly, webpack requires you to tell it to resolve imports from node_modules, which we do with this:

resolve: {   modules: [path.resolve("./node_modules")] }

As expected, Vite already does this.

Production mode

One of the common things we do in webpack is distinguish between production and development environments by manually passing a mode property, like this:

mode: isProd ? "production" : "development",

…which we normally surmise with something like this:

const isProd = process.env.NODE_ENV == "production";

And, of course, we set that environment variable via our build process.

Vite handles this a bit differently and gives us different commands to run for development builds versus those for production, which we’ll get into shortly.

File extensions

At the risk of belaboring the point, I’ll quickly note that Vite also doesn’t require you to specify every file extension you’re using.

resolve: {   extensions: [".ts", ".tsx", ".js"], }

Just set up the right kind of Vite project, and you’re good to go.

Rollup plugins are compatible!

This is such a key point I wanted to call it out in its own section. If you still wind up with some webpack plugins you need to replace in your Vite app when you finish this blog post, then try to find an equivalent Rollup plugin and use that. You read that correctly: Rollup plugins are already (or usually, at least) compatible with Vite. Some Rollup plugins, of course, do things that are incompatible with how Vite works—but in general, they should just work.

For more info, check out the docs.

Your first Vite project

Remember, we’re moving an existing legacy webpack project to Vite. If you’re building something new, it’s better to start a new Vite project and go from there. That said, the initial code I’m showing you is basically copied right from what Vite scaffolds from a fresh project anyway, so taking a moment to scaffold a new project might also a good idea for you to compare processes.

The HTML entry point

Yeah, you read that right. Rather than putting HTML integration behind a plugin, like webpack does, Vite is HTML first. It expects an HTML file with a script tag to your JavaScript entrypoint, and generates everything from there.

Here’s the HTML file (which Vite expects to be called index.html) we’re starting with:

<!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF-8" />     <title>The GOAT of web apps</title>   </head>   <body>     <div id="home"></div>     <script type="module" src="/reactStartup.tsx"></script>   </body> </html>

Note that the <script> tag points to /reactStartup.tsx. Adjust that to your own entry as needed.

Let’s install a few things, like a React plugin:

npm i vite @vitejs/plugin-react @types/node

We also create the following vite.config.ts right next to the index.html file in the project directory.

import { defineConfig } from "vite"; import react from "@vitejs/plugin-react";  export default defineConfig({   plugins: [react()] });

Lastly, let’s add a few new npm scripts:

"dev": "vite", "build": "vite build", "preview": "vite preview",

Now, let’s start Vite’s development server with npm run dev. It’s incredibly fast, and incrementally builds whatever it needs to, based on what’s requested.

But, unfortunately, it fails. At least for right now.

Screenshot of a terminal screen with a dark background and light text. There is an error in read that says there was an error when starting the development server.

We’ll get to how to set up aliases in a moment, but for now, let’s instead modify our reactStartup file (or whatever your entry file is called) as follows:

import React from "react"; import { render } from "react-dom";  render(   <div>     <h1>Hi there</h1>   </div>,   document.getElementById("home") );

Now we can run it our npm run dev command and browse to localhost:3000.

Screenshot of a terminal window with a black background and light text. Green text says the development server is running at localhost.

Screenshot of a blank white page that says hi there in black in a default serif font.

Hot module reloading (HMR)

Now that the development server is running, try modifying your source code. The output should update almost immediately via Vite’s HMR. This is one of Vite’s nicest features. It makes the development experience so much nicer when changes seem to reflect immediately rather than having to wait, or even trigger them ourselves.

The rest of this post will go over all the things I had to do to get my own app to build and run with Vite. I hope some of them are relevant for you!

Aliases

It’s not uncommon for webpack-based projects to have some config like this:

resolve: {   alias: {     jscolor: "util/jscolor.js"   },   modules: [path.resolve("./"), path.resolve("./node_modules")] }

This sets up an alias to jscolor at the provided path, and tells webpack to look both in the root folder (./) and in node_modules when resolving imports. This allows us to have imports like this:

import { thing } from "util/helpers/foo"

…anywhere in our component tree, assuming there’s a util folder at the very top.

Vite doesn’t allow you to provide an entire folder for resolution like this, but it does allow you to specify aliases, which follow the same rules as the @rollup/plugin-alias:

import { defineConfig } from "vite"; import react from "@vitejs/plugin-react";  import path from "path";  export default defineConfig({   resolve: {     alias: {       jscolor: path.resolve("./util/jscolor.js"),       app: path.resolve("./app"),       css: path.resolve("./css"),       util: path.resolve("./util")     }   },   plugins: [react()] });

We’ve added a resolve.alias section, including entries for everything we need to alias. Our jscolor util is set to the relevant module, and we have aliases for our top-level directories. Now we can import from app/, css*/*, and util/ from any component, anywhere.

Note that these aliases only apply to the root of the import, e.g. util/foo. If you have some other util folder deeper in your tree, and you reference it with this:

import { thing } from "./helpers/util";

…then the alias above will not mess that up. This distinction is not well documented, but you can see it in the Rollup alias plugin. Vite’s alias matches that same behavior.

Environment variables

Vite, of course, supports environment variables. It reads config values out of your .env files in development, or process.env, and injects them into your code. Unfortunately, things work a bit differently than what you might be used to. First, it does not replace process.env.FOO but rather import.meta.env.FOO. Not only that, but it only replaces variables prefixed with VITE_ by default. So, import.meta.env.VITE_FOO would actually be replaced, but not my original FOO. This prefix can be configured, but not set to empty string.

For a legacy project, you could grep and replace all your environment variables to use import.meta.env, then add a VITE_ prefix, update your .env files, and update the environment variables in whatever CI/CD system you use. Or you can configure the more classic behavior of replacing process.env.ANYTHING with values from a .env file in development, or the real process.env value in production.

Here’s how. Vite’s define feature is basically what we need. This registers global variables during development, and does raw text replacement for production. We need to set things up so that we manually read our .env file in development mode, and the process.env object in production mode, and then add the appropriate define entries.

Let’s build that all into a Vite plugin. First, run npm i dotenv.

Now let’s look at the code for the plugin:

import dotenv from "dotenv";  const isProd = process.env.NODE_ENV === "production"; const envVarSource = isProd ? process.env : dotenv.config().parsed;  export const dotEnvReplacement = () => {   const replacements = Object.entries(envVarSource).reduce((obj, [key, val]) => {     obj[`process.env.$ {key}`] = `"$ {val}"`;     return obj;   }, {});    return {     name: "dotenv-replacement",     config(obj) {       obj.define = obj.define || {};       Object.assign(obj.define, replacements);     }   }; };

Vite sets process.env.NODE_ENV for us, so all we need to do is check that to see which mode we’re in.

Now we get the actual environment variables. If we’re in production, we grab process.env itself. If we’re in dev, we ask dotenv to grab our .env file, parse it, and get back an object with all the values.

Our plugin is a function that returns a Vite plugin object. We inject our environment values into a new object that has process.env. in front of the value, and then we return our actual plugin object. There is a number of hooks available to use. Here, though, we only need the config hook, which allows us to modify the current config object. We add a define entry if none exists, then add all our values.

But before moving forward, I want to note that the Vite’s environment variables limitations we are working around exist for a reason. The code above is how bundlers are frequently configured, but that still means any random value in process.env is stuck into your source code if that key exists. There are potential security concerns there, so please keep that in mind.

Server proxy

What does your deployed web application look like? If all it’s doing is serving JavaScript/CSS/HTML—with literally everything happening via separate services located elsewhere—then good! You’re effectively done. What I’ve shown you should be all you need. Vite’s development server will serve your assets as needed, which pings all your services just like they did before.

But what if your web app is small enough that you have some services running right on your web server? For the project I’m converting, I have a GraphQL endpoint running on my web server. For development, I start my Express server, which previously knew how to serve the assets that webpack generated. I also start a webpack watch task to generate those assets.

But with Vite shipping its own dev server, we need to start that Express server (on a separate port than what Vite uses) and then proxy calls to /graphql over to there:

server: {   proxy: {     "/graphql": "http://localhost:3001"   } } 

This tells Vite that any requests for /graphql should be sent to http://localhost:3001/graphql.

Note that we do not set the proxy to http://localhost:3001/graphql in the config. Instead, we set it to http://localhost:3001 and rely on Vite to add the /graphql part (as well any any query arguments) to the path.

Building libs

As a quick bonus section, let’s briefly discuss building libraries. For example, what if all you want to build is a JavaScript file, e.g. a library like Redux. There’s no associated HTML file, so you’ll first need to tell Vite what to make:

build: {   outDir: "./public",   lib: {     entry: "./src/index.ts",     formats: ["cjs"],     fileName: "my-bundle.js"   } }

Tell Vite where to put the generated bundle, what to call it, and what formats to build. Note that I’m using CommonJS here instead of ES modules since the ES modules do not minify (as of this writing) due to concerns that it could break tree-shaking.

You’d run this build with vite build. To start a watch and have the library rebuild on change, you’d run

vite build --watch.

Wrapping up

Vite is an incredibly exciting tool. Not only does it take the pain, and tears out of bundling web apps, but it greatly improves the performance of doing so in the process. It ships with a blazingly fast development server that ships with hot module reloading and supports all major JavaScript frameworks. If you do web development—whether it’s for fun, it’s your job, or both!—I can’t recommend it strongly enough.


Adding Vite to Your Existing Web App originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, ,
[Top]

Animation Techniques for Adding and Removing Items From a Stack

Animating elements with CSS can either be quite easy or quite difficult depending on what you are trying to do. Changing the background color of a button when you hover over it? Easy. Animating the position and size of an element in a performant way that also affects the position of other elements? Tricky! That’s exactly what we’ll get into here in this article.

A common example is removing an item from a stack of items. The items stacked on top need to fall downwards to account for the space of an item removed from the bottom of the stack. That is how things behave in real life, and users may expect this kind of life-like motion on a website. When it doesn’t happen, it’s possible the user is confused or momentarily disorientated. You expect something to behave one way based on life experience and get something completely different, and users may need extra time to process the unrealistic movement.

Here is a demonstration of a UI for adding items (click the button) or removing items (click the item).

You could paper over the poor UI slightly by adding a “fade out” animation or something, but the result won’t be that great, as the list will will abruptly collapse and cause those same cognitive issues.

Applying CSS-only animations to a dynamic DOM event (adding brand new elements and fully removing elements) is extremely tricky work. We’re going to face this problem head-on and go over three very different types of animations that handle this, all accomplishing the same goal of helping users understand changes to a list of items. By the time we’re done, you’ll be armed to use these animations, or build your own based on the concepts.

We will also touch upon accessibility and how elaborate HTML layouts can still retain some compatibility with accessibility devices with the help of ARIA attributes.

The Slide-Down Opacity Animation

A very modern approach (and my personal favorite) is when newly-added elements fade-and-float into position vertically depending on where they are going to end up. This also means the list needs to “open up” a spot (also animated) to make room for it. If an element is leaving the list, the spot it took up needs to contract.

Because we have so many different things going on at the same time, we need to change our DOM structure to wrap each .list-item in a container class appropriately titled .list-container. This is absolutely essential in order to get our animation to work.

<ul class="list">   <li class="list-container">     <div class="list-item">List Item</div>   </li>   <li class="list-container">     <div class="list-item">List Item</div>   </li>   <li class="list-container">     <div class="list-item">List Item</div>   </li>   <li class="list-container">     <div class="list-item">List Item</div>   </li> </ul>  <button class="add-btn">Add New Item</button>

Now, the styling for this is unorthodox because, in order to get our animation effect to work later on, we need to style our list in a very specific way that gets the job done at the expense of sacrificing some customary CSS practices.

.list {   list-style: none; } .list-container {   cursor: pointer;   font-size: 3.5rem;   height: 0;   list-style: none;   position: relative;   text-align: center;   width: 300px; } .list-container:not(:first-child) {   margin-top: 10px; } .list-container .list-item {   background-color: #D3D3D3;   left: 0;   padding: 2rem 0;   position: absolute;   top: 0;   transition: all 0.6s ease-out;   width: 100%; } .add-btn {   background-color: transparent;   border: 1px solid black;   cursor: pointer;   font-size: 2.5rem;   margin-top: 10px;   padding: 2rem 0;   text-align: center;   width: 300px; }

How to handle spacing

First, we’re using margin-top to create vertical space between the elements in the stack. There’s no margin on the bottom so that the other list items can fill the gap created by removing a list item. That way, it still has margin on the bottom even though we have set the container height to zero. That extra space is created between the list item that used to be directly below the deleted list item. And that same list item should move up in reaction to the deleted list item’s container having zero height. And because this extra space expands the vertical gap between the list items further then we want it to. So that’s why we use margin-top — to prevent that from happening.

But we only do this if the item container in question isn’t the first one in the list. That’s we used :not(:first-child) — it targets all of the containers except the very first one (an enabling selector). We do this because we don’t want the very first list item to be pushed down from the top edge of the list. We only want this to happen to every subsequent item thereafter instead because they are positioned directly below another list item whereas the first one isn’t.

Now, this is unlikely to make complete sense because we are not setting any elements to zero height at the moment. But we will later on, and in order to get the vertical spacing between the list elements correct, we need to set the margin like we do.

A note about positioning

Something else that is worth pointing out is the fact that the .list-item elements nested inside of the parent .list-container elements are set to have a position of absolute, meaning that they are positioned outside of the DOM and in relation to their relatively-positioned .list-container elements. We do this so that we can get the .list-item element to float upwards when removed, and at the same time, get the other .list-item elements to move and fill the gap that removing this .list-item element has left. When this happens, the .list-container element, which isn’t positioned absolute and is therefore affected by the DOM, collapses its height allowing the other .list-container elements to fill its place, and the .list-item element — which is positioned with absolute — floats upwards, but doesn’t affect the structure of the list as it isn’t affected by the DOM.

Handling height

Unfortunately, we haven’t yet done enough to get a proper list where the individual list-items are stacked one by one on top of each other. Instead, all we will be able to see at the moment is just a single .list-item that represents all of the list items piled on top of each other in the exact same place. That’s because, although the .list-item elements may have some height via their padding property, their parent elements do not, but have a height of zero instead. This means that we don’t have anything in the DOM that is actually separating these elements out from each other because in order to do that, we would need our .list-item containers to have some height because, unlike their child element, they are affected by the DOM.

To get the height of our list containers to perfectly match the height of their child elements, we need to use JavaScript. So, we store all of our list items within a variable. Then, we create a function that is called immediately as soon as the script is loaded.

This becomes the function that handles the height of the list container elements:

const listItems = document.querySelectorAll('.list-item');  function calculateHeightOfListContainer(){ };  calculateHeightOfListContainer();

The first thing that we do is extract the very first .list-item element from the list. We can do this because they are all the same size, so it doesn’t matter which one we use. Once we have access to it, we store its height, in pixels, via the element’s clientHeight property. After this, we create a new <style> element that is prepended to the document’s body immediately after so that we can directly create a CSS class that incorporates the height value we just extracted. And with this <style> element safely in the DOM, we write a new .list-container class with styles that automatically have priority over the styles declared in the external stylesheet since these styles come from an actual <style> tag. That gives the .list-container classes the same height as their .list-item children.

const listItems = document.querySelectorAll('.list-item');  function calculateHeightOfListContainer() {   const firstListItem = listItems[0];   let heightOfListItem = firstListItem.clientHeight;   const styleTag = document.createElement('style');   document.body.prepend(styleTag);   styleTag.innerHTML = `.list-container{     height: $ {heightOfListItem}px;   }`; };  calculateHeightOfListContainer();

Showing and Hiding

Right now, our list looks a little drab — the same as the what we saw in the first example, just without any of the addition or removal logic, and styled in a completely different way to the list constructed from <ul> and <li> tags list that were used in that opening example.

Four light gray rectangular boxes with the words list item. The boxes are stacked vertically, one on top of the other. Below the bottom box is another box with a white background and thin black border that is a button with a label that says add new item.

We’re going to do something now that may seem inexplicable at the moment and modify our .list-container and .list-item classes. We’re also creating extra styling for both of these classes that will only be added to them if a new class, .show, is used in conjunction with both of these classes separately.

The purpose we’re doing this is to create two states for both the .list-container and the .list-item elements. One state is without the .show classes on both of these elements, and this state represents the elements as they are animated out from the list. The other state contains the .show class added to both of these elements. It represents the specified .list-item as firmly instantiated and visible in the list.

In just a bit, we will switch between these two states by adding/removing the .show class from both the parent and the container of a specific .list-item. We’ll combined that with a CSS transition between these two states.

Notice that combining the .list-item class with the .show class introduces some extra styles to things. Specifically, we’re introducing the animation that we are creating where the list item fades downwards and into visibility when it is added to the list — the opposite happens when it is removed. Since the most performant way to animate elements positions is with the transform property, that is what we will use here, applying opacity along the way to handle the visibility part. Because we already applied a transition property on both the .list-item and the .list-container elements, a transition automatically takes place whenever we add or remove the .show class to both of these elements due to the extra properties that the .show class brings, causing a transition whenever we either add or remove these new properties.

.list-container {   cursor: pointer;   font-size: 3.5rem;   height: 0;   list-style: none;   position: relative;   text-align: center;   width: 300px; } .list-container.show:not(:first-child) {   margin-top: 10px; } .list-container .list-item {   background-color: #D3D3D3;   left: 0;   opacity: 0;   padding: 2rem 0;   position: absolute;   top: 0;   transform: translateY(-300px);   transition: all 0.6s ease-out;   width: 100%; } .list-container .list-item.show {   opacity: 1;   transform: translateY(0); }

In response to the .show class, we are going back to our JavaScript file and changing our only function so that the .list-container element are only given a height property if the element in question also has a .show class on it as well, Plus, we are applying a transition property to our standard .list-container elements, and we will do it in a setTimeout function. If we didn’t, then our containers would animate on the initial page load when the script is loaded, and the heights are applied the first time, which isn’t something we want to happen.

const listItems = document.querySelectorAll('.list-item'); function calculateHeightOfListContainer(){   const firstListItem = listItems[0];   let heightOfListItem = firstListItem.clientHeight;   const styleTag = document.createElement('style');   document.body.prepend(styleTag);   styleTag.innerHTML = `.list-container.show {     height: $ {heightOfListItem}px;   }`;   setTimeout(function() {     styleTag.innerHTML += `.list-container {       transition: all 0.6s ease-out;     }`;   }, 0); }; calculateHeightOfListContainer();

Now, if we go back and view the markup in DevTools, then we should be able to see that the list has disappeared and all that is left is the button. The list hasn’t disappeared because these elements have been removed from the DOM; it has disappeared because of the .show class which is now a required class that must be added to both the .list-item and the .list-container elements in order for us to be able to view them.

The way to get the list back is very simple. We add the .show class to all of our .list-container elements as well as the .list-item elements contained inside. And once this is done we should be able to see our pre-created list items back in their usual place.

<ul class="list">   <li class="list-container show">     <div class="list-item show">List Item</div>   </li>   <li class="list-container show">     <div class="list-item show">List Item</div>   </li>   <li class="list-container show">     <div class="list-item show">List Item</div>   </li>   <li class="list-container show">     <div class="list-item show">List Item</div>   </li> </ul>  <button class="add-btn">Add New Item</button>

We won’t be able to interact with anything yet though because to do that — we need to add more to our JavaScript file.

The first thing that we will do after our initial function is declare references to both the button that we click to add a new list item, and the .list element itself, which is the element that wraps around every single .list-item and its container. Then we select every single .list-container element nested inside of the parent .list element and loop through them all with the forEach method. We assign a method in this callback, removeListItem, to the onclick event handler of each .list-container. By the end of the loop, every single .list-container instantiated to the DOM on a new page load calls this same method whenever they are clicked.

Once this is done, we assign a method to the onclick event handler for addBtn so that we can activate code when we click on it. But obviously, we won’t create that code just yet. For now, we are merely logging something to the console for testing.

const addBtn = document.querySelector('.add-btn'); const list = document.querySelector('.list'); function removeListItem(e){   console.log('Deleted!'); } // DOCUMENT LOAD document.querySelectorAll('.list .list-container').forEach(function(container) {   container.onclick = removeListItem; });  addBtn.onclick = function(e){   console.log('Add Btn'); } 

Starting work on the onclick event handler for addBtn, the first thing that we want to do is create two new elements: container and listItem. Both elements represent the .list-item element and their respective .list-container element, which is why we assign those exact classes to them as soon as we create the them.

Once these two elements are prepared, we use the append method on the container to insert the listItem inside of it as a child, the same as how these elements that are already in the list are formatted. With the listItem successfully appended as a child to the container, we can move the container element along with its child listItem element to the DOM with the insertBefore method. We do this because we want new items to appear at the bottom of the list but before the addBtn, which needs to stay at the very bottom of the list. So, by using the parentNode attribute of addBtn to target its parent, list, we are saying that we want to insert the element as a child of list, and the child that we are inserting (container) will be inserted before the child that is already on the DOM and that we have targeted with the second argument of the insertBefore method, addBtn.

Finally, with the .list-item and its container successfully added to the DOM, we can set the container’s onclick event handler to match the same method as every other .list-item already on the DOM by default.

addBtn.onclick = function(e){   const container = document.createElement('li');    container.classList.add('list-container');   const listItem = document.createElement('div');    listItem.classList.add('list-item');    listItem.innerHTML = 'List Item';   container.append(listItem);   addBtn.parentNode.insertBefore(container, addBtn);   container.onclick = removeListItem; }

If we try this out, then we won’t be able to see any changes to our list no matter how many times we click the addBtn. This isn’t an error with the click event handler. Things are working exactly how they should be. The .list-item elements (and their containers) are added to the list in the correct place, it is just that they are getting added without the .show class. As a result, they don’t have any height to them, which is why we can’t see them and is why it looks like nothing is happening to the list.

To get each newly added .list-item to animate into the list whenever we click on the addBtn, we need to apply the .show class to both the .list-item and its container, just as we had to do to view the list items already hard-coded into the DOM.

The problem is that we cannot just add the .show class to these elements instantly. If we did, the new .list-item statically pops into existence at the bottom of the list without any animation. We need to register a few styles before the animation additional styles that override those initial styles for an element to know what transition to make. Meaning, that if we just apply the .show class to are already in place — so no transition.

The solution is to apply the .show classes in a setTimeout callback, delaying the activation of the callback by 15 milliseconds, or 1.5/100th of a second. This imperceptible delay is long enough to create a transition from the proviso state to the new state that is created by adding the .show class. But that delay is also short enough that we will never know that there was a delay in the first place.

addBtn.onclick = function(e){   const container = document.createElement('li');    container.classList.add('list-container');   const listItem = document.createElement('div');    listItem.classList.add('list-item');    listItem.innerHTML = 'List Item';   container.append(listItem);   addBtn.parentNode.insertBefore(container, addBtn);   container.onclick = removeListItem;   setTimeout(function(){     container.classList.add('show');      listItem.classList.add('show');   }, 15); }

Success! It is now time to handle how we remove list items when they are clicked.

Removing list items shouldn’t be too hard now because we have already gone through the difficult task of adding them. First, we need to make sure that the element we are dealing with is the .list-container element instead of the .list-item element. Due to event propagation, it is likely that the target that triggered this click event was the .list-item element.

Since we want to deal with the associated .list-container element instead of the actual .list-item element that triggered the event, we’re using a while-loop to loop one ancestor upwards until the element held in container is the .list-container element. We know it works when container gets the .list-container class, which is something that we can discover by using the contains method on the classList property of the container element.

Once we have access to the container, we promptly remove the .show class from both the container and its .list-item once we have access to that as well.

function removeListItem(e) {   let container = e.target;   while (!container.classList.contains('list-container')) {     container = container.parentElement;   }   container.classList.remove('show');   const listItem = container.querySelector('.list-item');   listItem.classList.remove('show'); }

And here is the finished result:

Accessibility & Performance

Now you may be tempted to just leave the project here because both list additions and removals should now be working. But it is important to keep in mind that this functionality is only surface level and there are definitely some touch ups that need to be made in order to make this a complete package.

First of all, just because the removed elements have faded upwards and out of existence and the list has contracted to fill the gap that it has left behind does not mean that the removed element has been removed from the DOM. In fact, it hasn’t. Which is a performance liability because it means that we have elements in the DOM that serve no purpose other than to just accumulate in the background and slow down our application.

To solve this, we use the ontransitionend method on the container element to remove it from the DOM but only when the transition caused by us removing the .show class has finished so that its removal couldn’t possibly interrupt our transition.

function removeListItem(e) {   let container = e.target;   while (!container.classList.contains('list-container')) {     container = container.parentElement;   }   container.classList.remove('show');   const listItem = container.querySelector('.list-item');   listItem.classList.remove('show');   container.ontransitionend = function(){     container.remove();   } }

We shouldn’t be able to see any difference at this point because allwe did was improve the performance — no styling updates.

The other difference is also unnoticeable, but super important: compatibility. Because we have used the correct <ul> and <li> tags, devices should have no problem with correctly interpreting what we have created as an unordered list.

Other considerations for this technique

A problem that we do have however, is that devices may have a problem with the dynamic nature of our list, like how the list can change its size and the number of items that it holds. A new list item will be completely ignored and removed list items will be read as if they still exist.

So, in order to get devices to re-interpret our list whenever the size of it changes, we need to use ARIA attributes. They help get our nonstandard HTML list to be recognized as such by compatibility devices. That said, they are not a guaranteed solution here because they are never as good for compatibility as a native tag. Take the <ul> tag as an example — no need to worry about that because we were able to use the native unordered list element.

We can use the aria-live attribute to the .list element. Everything nested inside of a section of the DOM marked with aria-live becomes responsive. In other words, changes made to an element with aria-live is recognized, allowing them to issue an updated response. In our case, we want things highly reactive and we do that be setting the aria live attribute to assertive. That way, whenever a change is detected, it will do so, interrupting whatever task it was currently doing at the time to immediately comment on the change that was made.

<ul class="list" role="list" aria-live="assertive">

The Collapse Animation

This is a more subtle animation where, instead of list items floating either up or down while changing opacity, elements instead just collapse or expand outwards as they gradually fade in or out; meanwhile, the rest of the list repositions itself to the transition taking place.

The cool thing about the list (and perhaps some remission for the verbose DOM structure we created), would be the fact that we can change the animation very easily without interfering with the main effect.

So, to achieve this effect, we start of by hiding overflow on our .list-container. We do this so that when the .list-container collapses in on itself, it does so without the child .list-item flowing beyond the list container’s boundaries as it shrinks. Apart from that, the only other thing that we need to do is remove the transform property from the .list-item with the .show class since we don’t want the .list-item to float upwards anymore.

.list-container {   cursor: pointer;   font-size: 3.5rem;   height: 0;   overflow: hidden;   list-style: none;   position: relative;   text-align: center;   width: 300px; } .list-container.show:not(:first-child) {   margin-top: 10px; } .list-container .list-item {   background-color: #D3D3D3;   left: 0;   opacity: 0;   padding: 2rem 0;   position: absolute;   top: 0;   transition: all 0.6s ease-out;   width: 100%; } .list-container .list-item.show {   opacity: 1; }

The Side-Slide Animation

This last animation technique is strikingly different fromithe others in that the container animation and the .list-item animation are actually out of sync. The .list-item is sliding to the right when it is removed from the list, and sliding in from the right when it is added to the list. There needs to be enough vertical room in the list to make way for a new .list-item before it even begins animating into the list, and vice versa for the removal.

As for the styling, it’s very much like the Slide Down Opacity animation, only thing that the transition for the .list-item should be on the x-axis now instead of the y-axis.

.list-container {   cursor: pointer;   font-size: 3.5rem;   height: 0;   list-style: none;   position: relative;   text-align: center;   width: 300px; } .list-container.show:not(:first-child) {   margin-top: 10px; } .list-container .list-item {   background-color: #D3D3D3;   left: 0;   opacity: 0;   padding: 2rem 0;   position: absolute;   top: 0;   transform: translateX(300px);   transition: all 0.6s ease-out;   width: 100%; } .list-container .list-item.show {   opacity: 1;   transform: translateX(0); }

As for the onclick event handler of the addBtn in our JavaScript, we’re using a nested setTimeout method to delay the beginning of the listItem animation by 350 milliseconds after its container element has already started transitioning.

setTimeout(function(){   container.classList.add('show');    setTimeout(function(){     listItem.classList.add('show');   }, 350); }, 10);

In the removeListItem function, we remove the list item’s .show class first so it can begin transitioning immediately. The parent container element then loses its .show class, but only 350 milliseconds after the initial listItem transition has already started. Then, 600 milliseconds after the container element starts to transition (or 950 milliseconds after the listItem transition), we remove the container element from the DOM because, by this point, both the listItem and the container transitions should have come to an end.

function removeListItem(e){   let container = e.target;   while(!container.classList.contains('list-container')){     container = container.parentElement;   }   const listItem = container.querySelector('.list-item');   listItem.classList.remove('show');   setTimeout(function(){     container.classList.remove('show');     container.ontransitionend = function(){       container.remove();     }   }, 350); }

Here is the end result:

That’s a wrap!

There you have it, three different methods for animating items that are added and removed from a stack. I hope that with these examples you are now confident to work in a situation where the DOM structure settles into a new position in reaction to an element that has either been added or removed from the DOM.

As you can see, there’s a lot of moving parts and things to consider. We started with that we expect from this type of movement in the real world and considered what happens to a group of elements when one of them is updated. It took a little balancing to transition between the showing and hiding states and which elements get them at specific times, but we got there. We even went so far as to make sure our list is both performant and accessible, things that we’d definitely need to handle on a real project.

Anyway, I wish you all the best in your future projects. And that’s all from me. Over and out.


The post Animation Techniques for Adding and Removing Items From a Stack appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,
[Top]

Adding Shadows to SVG Icons With CSS and SVG Filters

Why would we need to apply shadows to SVG?

  1. Shadows are a common design feature that can help elements, like icons, stand out. They could be persistent, or applied in different states (e.g. :hover, :focus, or :active) to indicate interaction to users.
  2. Shadows happen in real life, so they can be used on screens to breathe some life into your elements and add a touch of realism to a design.

Since we’re making lists, there are two primary ways we can apply shadows to an SVG:

  1. Using the CSS filter() property
  2. Using an SVG <filter>

Yes, both involve filters! And, yes, both CSS and SVG have their own types of filters. But there is some crossover between these as well. For example, a CSS filter can refer to an SVG <filter>; that is, if we’re working with an inline SVG instead of, say, an SVG used as a background image in CSS.

What you can’t use: the CSS box-shadow property. This is commonly used for shadows, but it follows the rectangular outside edge of elements, not the edges of the SVG elements like we want. Here’s Michelle Barker with a clear explanation:

Two flat kitten faces in bright pink showing ears eyes and whiskers. The first kitten has a drop shadow around its box and the second kitten has a drop shadow around its path edges.

If you’re using an SVG icon font, though, there is always text-shadow. That will indeed work. But let’s focus on those first two as they’re in line with a majority of use cases.

Shadows with CSS filters

The trick to applying a shadow directly to SVG via CSS filters is the drop-shadow() function :

svg {   filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4)); }

That will apply a shadow that starts at 3px horizontally, 5px down, with 2px of blur, and is 40% black. Here are some examples of that:

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

Desktop

Chrome Firefox IE Edge Safari
18* 35 No 79 6*

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
91 89 4.4* 6.0-6.1*

Call an SVG filter inside a CSS filter

Say we have an SVG filter in the HTML:

<svg height="0" width="0">      <filter id='shadow' color-interpolation-filters="sRGB">     <feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.5"/>   </filter>    </svg>

We can use a CSS filter to call that SVG filter by ID instead of values we saw earlier:

svg {   filter: url(#shadow); }

Now that filter is taken from the HTML and referenced in the CSS, which applies it.

Using SVG filter primitives

You might be wondering how we got that SVG <filter> to work. To make a drop shadow with an SVG filter, we make use of a filter primitive. A filter primitive in SVG is an element that takes some sort of image or graphic as an input, then outputs that image or graphic it when it’s called. They sort of work like filters in a graphic editing application, but in code and can only be used inside an SVG <filter> element.

There are lots of different filter primitives in SVG. The one we’re reaching for is <feDropShadow>. I’ll let you guess what to does just by looking at the name.

So, similar to how we had something like this did this with a CSS filter:

svg {   filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4)); }

…we can accomplish the same with the <feDropShadow> SVG filter primitive. There are three key attributes worth calling out as they help define the appearance of the drop shadow:

  • dx — This shifts the position of the shadow along the x-axis.
  • dy — This shifts the position of the shadow along the y-axis.
  • stdDeviation — This defines the standard deviation for the drop shadow’s blur operation. There are other attributes we can use, such as the flood-color for setting the drop shadow color, and flood-opacity for setting the drop shadow’s opacity.

That example includes three <filter> elements, each with their own <feDropShadow> filter primitives.

Using SVG filters

SVG filters are very powerful. We just looked at <feDropShadow>, which is very useful of course, but there is so much more they can do (including Photoshop-like effects) and the subset of stuff we get just for shadows is extensive. Let’s look at some, like colored shadows and inset shadows.

Let’s take the SVG markup for the Twitter logo as an example :

<svg class="svg-icon" viewBox="0 0 20 20">   <path fill="#4691f6" d="M18.258,3.266c-0.693,0.405-1.46,0.698-2.277,0.857c-0.653-0.686-1.586-1.115-2.618-1.115c-1.98,0-3.586,1.581-3.586,3.53c0,0.276,0.031,0.545,0.092,0.805C6.888,7.195,4.245,5.79,2.476,3.654C2.167,4.176,1.99,4.781,1.99,5.429c0,1.224,0.633,2.305,1.596,2.938C2.999,8.349,2.445,8.19,1.961,7.925C1.96,7.94,1.96,7.954,1.96,7.97c0,1.71,1.237,3.138,2.877,3.462c-0.301,0.08-0.617,0.123-0.945,0.123c-0.23,0-0.456-0.021-0.674-0.062c0.456,1.402,1.781,2.422,3.35,2.451c-1.228,0.947-2.773,1.512-4.454,1.512c-0.291,0-0.575-0.016-0.855-0.049c1.588,1,3.473,1.586,5.498,1.586c6.598,0,10.205-5.379,10.205-10.045c0-0.153-0.003-0.305-0.01-0.456c0.7-0.499,1.308-1.12,1.789-1.827c-0.644,0.28-1.334,0.469-2.06,0.555C17.422,4.782,17.99,4.091,18.258,3.266" ></path> </svg>

We’re going to need a <filter> element to do these effects. This needs to be within an <svg> element in the HTML. A <filter> element is never rendered directly in the browser — it is only used as something that can be referenced via the filter attribute in SVG, or the url() function in CSS.

Here is the syntax showing an SVG filter and applying it to a source image :

<svg width="300" height="300" viewBox="0 0 300 300">    <filter id="myfilters">     <!-- All filter effects/primitives go in here -->   </filter>    <g filter="url(#myfilters)">     <!-- Filter applies to everything in this group -->     <path fill="..." d="..." ></path>   </g>  </svg>

The filter element is meant to hold filter primitives as children. It is a container to a series of filter operations that are combined to create a filter effects.

These filter primitive perform a single fundamental graphical operation (e.g. blurring, moving, filling, combining, or distorting) on one or more inputs. They are like building blocks where each SVG filter can be used to in conjunction with others to create an effect. <feGaussianBlur> is a popular filter primitive used to add a blur effect.

Let’s say we define the following SVG filter with <feGaussianBlur>:

<svg version="1.1" width="0" height="0">   <filter id="gaussian-blur">     <feGaussianBlur stdDeviation="1 0" />   </filter> </svg>

When applied on an element, this filter creates a Gaussian blur that blurs the element on a 1px radius on the x-axis, but no blurring on the y-axis. Here’s the result, with and without the effect:

It is possible to use multiple primitives inside a single filter. This will create interesting effects, however, you need to make the different primitives aware of each other. Bence Szabó has a crazy cool set of patterns he created this way.

When combining multiple filter primitives, the first primitive uses the original graphic (SourceGraphic) as its graphic input. Any subsequent primitive uses the result of the filter effect before it as its input. And so on. But we can get some flexibility on that with using the in, in2 and result attributes on primitive elements. Steven Bradley has an excellent write-up on filter primitives that dates back to 2016, but still hold true today.

There are 17 primitives we can use today:

  • <feGaussianBlur>
  • <feDropShadow>
  • <feMorphology>
  • <feDisplacementMap>
  • <feBlend>
  • <feColorMatrix>
  • <feConvolveMatrix>
  • <feComponentTransfer>
  • <feSpecularLighting>
  • <feDiffuseLighting>
  • <feFlood>
  • <feTurbulence>
  • <feImage>
  • <feTile>
  • <feOffset>
  • <feComposite>
  • <feMerge>

Notice the fe prefix on all of them. That stands for filter effect. Understanding SVG filters is challenging. An effect like an inset shadow requires a verbose syntax that is difficult to grasp without a thorough understanding of math and color theory. (Rob O’Leary’s “Getting Deep Into Shadows” is a good place to start.)

Rather than running down the rabbit hole of all that, we’re going to work with some pre-made filters. Fortunately, there are a lot of ready-to-use SVG filters around.

Inset shadows

To use filter effect on the Twitter logo, we need to declare it in our “SVG source document” with a unique ID for referencing in our <filter> tag.

<filter id='inset-shadow'>   <!-- Shadow offset -->   <feOffset     dx='0'     dy='0'   />    <!-- Shadow blur -->   <feGaussianBlur     stdDeviation='1'     result='offset-blur'   />    <!-- Invert drop shadow to make an inset shadow -->   <feComposite     operator='out'     in='SourceGraphic'     in2='offset-blur'     result='inverse'   />      <!-- Cut color inside shadow -->   <feFlood     flood-color='black'     flood-opacity='.95'     result='color'   />   <feComposite     operator='in'     in='color'     in2='inverse'     result='shadow'   />    <!-- Placing shadow over element -->   <feComposite     operator='over'     in='shadow'     in2='SourceGraphic'   /> </filter>

There are four different primitives in there and each one performs a different function. But, taken together, they achieving an inset shadow.

Now that we’ve created this inset shadow filter, we can apply it to our SVG. We’ve already seen how to apply it via CSS. Something like:

.filtered {   filter: url(#myfilters); }  /* Or apply only in certain states, like: */ svg:hover, svg:focus {   filter: url(#myfilters); } 

We can also apply an SVG <filter> directly within the SVG syntax with the filter attribute. That’s like:

<svg>    <!-- Apply a single filter -->   <path d="..." filter="url(#myfilters)" />    <!-- Or apply to a whole group of elements -->   <g filter="url(#myfilters)">     <path d="..." />     <path d="..." />   </g> </svg>

More examples

Here are some more shadow examples from Oleg Solomka:

Note that the basic shadows here are probably a bit more complicated than they need to be. For example, a colored shadow can still be done with <feDropShadow> like:

<feDropShadow dx="-0.8" dy="-0.8" stdDeviation="0"   flood-color="pink" flood-opacity="0.5"/>

But that embossed effect is pretty great as a filter!

Also note that you might see SVG filters in SVG syntax like this:

<svg height="0" width="0" style="position: absolute; margin-left: -100%;">   <defs>     <filter id="my-filters">       <!-- ... -->     </filter>      <symbol id="my-icon">       <!-- ... -->     </symbol>   </defs> </svg>

On the first line there, that’s saying: this SVG shouldn’t render at all — it’s just stuff that we intend to use later. The <defs> tag says something similar: we’re just defining these things to use later. That way, we don’t have to repeat ourselves by writing things out over and again. We’ll reference the filter by ID, and the symbols as well, perhaps like:

<svg>   <use xlink:href="#my-icon" /> </svg>

SVG filters have wide support (even in Internet Explorer and Edge!) with very fast performance.

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

Desktop

Chrome Firefox IE Edge Safari
8 3 10 12 6

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
91 89 4.4 6.0-6.1

Wrapping things up

A final comparison:

  • CSS filters are easier to use, but are much more limited. I don’t think it’s possible to add an inset shadow with the drop-shadow() function, for example.
  • SVG filters are much more robust, but much more complicated as well, and require having the <filter> somewhere in the HTML.
  • They both have great browser support and perform well on all modern browsers, though SVG filters have (surprisingly) the deepest browser support.

In this article, we have seen why and how to apply shadow to SVG icons with examples on each. Have you done this, but did it a different way than anything we looked at? Have you tried to do a shadow effect that you found impossible to pull off? Please share!


The post Adding Shadows to SVG Icons With CSS and SVG Filters appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Adding a Custom Welcome Guide to the WordPress Block Editor

I am creating a WordPress plugin and there is a slight learning curve when it comes to using it. I’d like to give users a primer on how to use the plugin, but I want to avoid diverting users to documentation on the plugin’s website since that takes them out of the experience.

What would be great is for users to immediately start using the plugin once it’s installed but have access to helpful tips while they are actively using it. There’s no native feature for something like this in WordPress but we can make something because WordPress is super flexible like that.

So here’s the idea. We’re going to bake documentation directly into the plugin and make it easily accessible in the block editor. This way, users get to use the plugin right away while having answers to common questions directly where they’re working. 

My plugin operates through several Custom Post Types (CPT). What we’re going to build is essentially a popup modal that users get when they go to these CPTs. 

The WordPress block editor is built in React, which utilizes components that can be customized to and reused for different situations.  That is the case with what we’re making — let’s call it the <Guide> component — which behaves like a modal, but is composed of several pages that the user can paginate through.

WordPress itself has a <Guide> component that displays a welcome guide when opening the block editor for the first time:

Screenshot showing a modal on top of the WordPress block editor welcoming users to the editor for the first time.
WordPress displays a modal with instructions for using the block editor when a user loads the editor for the first time.

The guide is a container filled with content that’s broken up into individual pages. In other words, it’s pretty much what we want. That means we don’t have to re-invent the wheel with this project; we can reuse this same concept for our own plugin.

Let’s do exactly that. 

What we want to achieve

Before we get to the solution, let’s talk about the end goal.

The design satisfies the requirements of the plugin, which is a GraphQL server for WordPress. The plugin offers a variety of CPTs that are edited through custom blocks which, in turn, are defined through templates. There’s a grand total of two blocks: one called “GraphiQL client” to input the GraphQL query, and one called “Persisted query options” to customize the behavior of the execution.

Since creating a query for GraphQL is not a trivial task, I decided to add the guide component to the editor screen for that CPT. It’s available in the Document settings as a panel called “Welcome Guide.”

Screenshot showing the WordPress editor with the document settings panel open in the right column. a welcome guide tab is highlighted in the settings.

Crack that panel open and the user gets a link. That link is what will trigger the modal.

Close-up screenshot of the welcome guide tab opened, revealing a link that says "Open Guide: Creating Persisted Queries."

For the modal itself, I decided to display a tutorial video on using the CPT on the first page, and then describe in detail all the options available in the CPT on subsequent pages.

Screenshot showing the custom modal open in the block editor and containing an embedded video on how to use the plugin.

I believe this layout is an effective way to show documentation to the user. It is out of the way, but still conveniently close to the action. Sure, we can use a different design or even place the modal trigger somewhere else using a different component instead of repurposing <Guide>, but this is perfectly good.

Planning the implementation

The implementation comprises the following steps:

  1. Scaffolding a new script to register the custom sidebar panel
  2. Displaying the custom sidebar panel on the editor for our Custom Post Type only
  3. Creating the guide
  4. Adding content to the guide

Let’s start!

Step 1: Scaffolding the script

Starting in WordPress 5.4, we can use a component called <PluginDocumentSettingPanel> to add a panel on the editor’s Document settings like this:

const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost;   const PluginDocumentSettingPanelDemo = () => (   <PluginDocumentSettingPanel     name="custom-panel"     title="Custom Panel"     className="custom-panel"   >     Custom Panel Contents   </PluginDocumentSettingPanel> ); registerPlugin( 'plugin-document-setting-panel-demo', {   render: PluginDocumentSettingPanelDemo,   icon: 'palmtree', } );

If you’re experienced with the block editor and already know how to execute this code, then you can skip ahead. I’ve been coding with the block editor for less than three months, and using React/npm/webpack is a new world for me — this plugin is my first project using them! I’ve found that the docs in the Gutenberg repo are not always adequate for beginners like me, and sometimes the documentation is missing altogether, so I’ve had to dig into the source code to find answers.

When the documentation for the component indicates to use that piece of code above, I don’t know what to do next, because <PluginDocumentSettingPanel> is not a block and I am unable to scaffold a new block or add the code there. Plus, we’re working with JSX, which means we need to have a JavaScript build step to compile the code.

I did, however, find the equivalent ES5 code:

var el = wp.element.createElement; var __ = wp.i18n.__; var registerPlugin = wp.plugins.registerPlugin; var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel; 
 function MyDocumentSettingPlugin() {   return el(     PluginDocumentSettingPanel,     {       className: 'my-document-setting-plugin',       title: 'My Panel',     },     __( 'My Document Setting Panel' )   ); } 
 registerPlugin( 'my-document-setting-plugin', {   render: MyDocumentSettingPlugin } );

ES5 code does not need be compiled, so we can load it like any other script in WordPress. But I don’t want to use that. I want the full, modern experience of ESNext and JSX.

So my thinking goes like this: I can’t use the block scaffolding tools since it’s not a block, and I don’t know how to compile the script (I’m certainly not going to set-up webpack all by myself). That means I’m stuck.

But wait! The only difference between a block and a regular script is just how they are registered in WordPress. A block is registered like this:

wp_register_script($ blockScriptName, $ blockScriptURL, $ dependencies, $ version); register_block_type('my-namespace/my-block', [   'editor_script' => $ blockScriptName, ]);

And a regular script is registered like this:

wp_register_script($ scriptName, $ scriptURL, $ dependencies, $ version); wp_enqueue_script($ scriptName);

We can use any of the block scaffolding tools to modify things then register a regular script instead of a block, which gains us access to the webpack configuration to compile the ESNext code. Those available tools are:

I chose to use the @wordpress/create-block package because it is maintained by the team developing Gutenberg.

To scaffold the block, we execute this in the command line:

npm init @wordpress/block

After completing all the prompts for information — including the block’s name, title and description — the tool will generate a single-block plugin, with an entry PHP file containing code similar to this:

/**  * Registers all block assets so that they can be enqueued through the block editor  * in the corresponding context.  *  * @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/  */ function my_namespace_my_block_block_init() {   $ dir = dirname( __FILE__ ); 
   $ script_asset_path = "$ dir/build/index.asset.php";   if ( ! file_exists( $ script_asset_path ) ) {     throw new Error(       'You need to run `npm start` or `npm run build` for the "my-namespace/my-block" block first.'     );   }   $ index_js     = 'build/index.js';   $ script_asset = require( $ script_asset_path );   wp_register_script(     'my-namespace-my-block-block-editor',     plugins_url( $ index_js, __FILE__ ),     $ script_asset['dependencies'],     $ script_asset['version']   ); 
   $ editor_css = 'editor.css';   wp_register_style(     'my-namespace-my-block-block-editor',     plugins_url( $ editor_css, __FILE__ ),     array(),     filemtime( "$ dir/$ editor_css" )   ); 
   $ style_css = 'style.css';   wp_register_style(     'my-namespace-my-block-block',     plugins_url( $ style_css, __FILE__ ),     array(),     filemtime( "$ dir/$ style_css" )   ); 
   register_block_type( 'my-namespace/my-block', array(     'editor_script' => 'my-namespace-my-block-block-editor',     'editor_style'  => 'my-namespace-my-block-block-editor',     'style'         => 'my-namespace-my-block-block',   ) ); } add_action( 'init', 'my_namespace_my_block_block_init' );

We can copy this code into the plugin, and modify it appropriately, converting the block into a regular script. (Note that I’m also removing the CSS files along the way, but could keep them, if needed.)

function my_script_init() {   $ dir = dirname( __FILE__ ); 
   $ script_asset_path = "$ dir/build/index.asset.php";   if ( ! file_exists( $ script_asset_path ) ) {     throw new Error(       'You need to run `npm start` or `npm run build` for the "my-script" script first.'     );   }   $ index_js     = 'build/index.js';   $ script_asset = require( $ script_asset_path );   wp_register_script(     'my-script',     plugins_url( $ index_js, __FILE__ ),     $ script_asset['dependencies'],     $ script_asset['version']   );   wp_enqueue_script(     'my-script'   ); } add_action( 'init', 'my_script_init' );

Let’s copy the package.json file over:

{   "name": "my-block",   "version": "0.1.0",   "description": "This is my block",   "author": "The WordPress Contributors",   "license": "GPL-2.0-or-later",   "main": "build/index.js",   "scripts": {     "build": "wp-scripts build",     "format:js": "wp-scripts format-js",     "lint:css": "wp-scripts lint-style",     "lint:js": "wp-scripts lint-js",     "start": "wp-scripts start",     "packages-update": "wp-scripts packages-update"   },   "devDependencies": {     "@wordpress/scripts": "^9.1.0"   } }

Now, we can replace the contents of file src/index.js with the ESNext code from above to register the <PluginDocumentSettingPanel> component. Upon running npm start (or npm run build for production) the code will be compiled into build/index.js.

There is a last problem to solve: the <PluginDocumentSettingPanel> component is not statically imported, but instead obtained from wp.editPost, and since wp is a global variable loaded by WordPress on runtime, this dependency is not present in index.asset.php (which is auto-generated during build). We must manually add a dependency to the wp-edit-post script when registering the script to make sure it loads before ours:

$ dependencies = array_merge(   $ script_asset['dependencies'],   [     'wp-edit-post',   ] ); wp_register_script(   'my-script',   plugins_url( $ index_js, __FILE__ ),   $ dependencies,   $ script_asset['version'] );

Now the script setup is ready!

The plugin can be updated with Gutenberg’s relentless development cycles. Run npm run packages-update to update the npm dependencies (and, consequently, the webpack configuration, which is defined on package "@wordpress/scripts") to their latest supported versions.

At this point, you might be wondering how I knew to add a dependency to the "wp-edit-post" script before our script. Well, I had to dig into Gutenberg’s source code. The documentation for <PluginDocumentSettingPanel> is somewhat incomplete, which is a perfect example of how Gutenberg’s documentation is lacking in certain places.

While digging in code and browsing documentation, I discovered a few enlightening things. For example, there are two ways to code our scripts: using either the ES5 or the ESNext syntax. ES5 doesn’t require a build process, and it references instances of code from the runtime environment, most likely through the global wp variable. For instance, the code to create an icon goes like this:

var moreIcon = wp.element.createElement( 'svg' );

ESNext relies on webpack to resolve all dependencies, which enables us to import static components. For instance, the code to create an icon would be:

import { more } from '@wordpress/icons';

This applies pretty much everywhere. However, that’s not the case for the <PluginDocumentSettingPanel> component, which references the runtime environment for ESNext:

const { PluginDocumentSettingPanel } = wp.editPost;

That’s why we have to add a dependency to the “wp-edit-post” script. That’s where the wp.editPost variable is defined.

If <PluginDocumentSettingPanel> could be directly imported, then the dependency to “wp-edit-post” would be automatically handled by the block editor through the Dependency Extraction Webpack Plugin. This plugin builds the bridge from static to runtime by creating a index.asset.php file containing all the dependencies for the runtime environment scripts, which are obtained by replacing "@wordpress/" from the package name with "wp-". Hence, the "@wordpress/edit-post" package  becomes the "wp-edit-post" runtime script. That’s how I figured out which script to add the dependency.

Step 2: Blacklisting the custom sidebar panel on all other CPTs 

The panel will display documentation for a specific CPT, so it must be registered only to that CPT. That means we need to blacklist it from appearing on any other post types.

Ryan Welcher (who created the <PluginDocumentSettingPanel> component) describes this process when registering the panel:

const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost const { withSelect } = wp.data; 
 const MyCustomSideBarPanel = ( { postType } ) => { 
   if ( 'post-type-name' !== postType ) {     return null;   } 
   return(     <PluginDocumentSettingPanel       name="my-custom-panel"       title="My Custom Panel"     >       Hello, World!     </PluginDocumentSettingPanel>   ); } 
 const CustomSideBarPanelwithSelect = withSelect( select => {   return {     postType: select( 'core/editor' ).getCurrentPostType(),   }; } )( MyCustomSideBarPanel); 
 
 registerPlugin( 'my-custom-panel', { render: CustomSideBarPanelwithSelect } );

He also suggests an alternative solution, using useSelect instead of withSelect.

That said, I’m not totally convinced by this solution, because the JavaScript file must still be loaded, even if it isn’t needed, forcing the website to take a performance hit. Doesn’t it make more sense to not register the JavaScript file than it does to run JavaScript just to disable JavaScript?

I have created a PHP solution. I’ll admit that it feels a bit hacky, but it works well. First, we find out which post type is related to the object being created or edited:

function get_editing_post_type(): ?string {   if (!is_admin()) {     return null;   } 
   global $ pagenow;   $ typenow = '';   if ( 'post-new.php' === $ pagenow ) {     if ( isset( $ _REQUEST['post_type'] ) && post_type_exists( $ _REQUEST['post_type'] ) ) {       $ typenow = $ _REQUEST['post_type'];     };   } elseif ( 'post.php' === $ pagenow ) {     if ( isset( $ _GET['post'] ) && isset( $ _POST['post_ID'] ) && (int) $ _GET['post'] !== (int) $ _POST['post_ID'] ) {       // Do nothing     } elseif ( isset( $ _GET['post'] ) ) {       $ post_id = (int) $ _GET['post'];     } elseif ( isset( $ _POST['post_ID'] ) ) {       $ post_id = (int) $ _POST['post_ID'];     }     if ( $ post_id ) {       $ post = get_post( $ post_id );       $ typenow = $ post->post_type;     }   }   return $ typenow; }

Then, ,we register the script only if it matches our CPT:

add_action('init', 'maybe_register_script'); function maybe_register_script() {   // Check if this is the intended custom post type   if (get_editing_post_type() != 'my-custom-post-type') {     return;   } 
   // Only then register the block   wp_register_script(...);   wp_enqueue_script(...); }

Check out this post for a deeper dive on how this works.

Step 3: Creating the custom guide

I designed the functionality for my plugin’s guide based on the WordPress <Guide> component. I didn’t realize I’d be doing that at first, so here’s how I was able to figure that out.

  1. Search the source code to see how it was done there.
  2. Explore the catalogue of all available components in Gutenberg’s Storybook.

First, I copied content from the block editor modal and did a basic search. The results pointed me to this file. From there I discovered the component is called <Guide> and could simply copy and paste its code to my plugin as a base for my own guide.

Then I looked for the component’s documentation. I browsed the @wordpress/components package (which, as you may have guessed, is where components are implemented) and found the component’s README file. That gave me all the information I needed to implement my own custom guide component.

I also explored the catalogue of all the available components in Gutenberg’s Storybook (which actually shows that these components can be used outside the context of WordPress). Clicking on all of them, I finally discovered <Guide>. The storybook provides the source code for several examples (or stories). It’s a handy resource for understanding how to customize a component through props.

At this point, I knew <Guide> would make a solid base for my component. There is one missing element, though: how to trigger the guide on click. I had to rack my brain for this one!

This is a button with a listener that opens the modal on click:

import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import MyGuide from './guide'; 
 const MyGuideWithButton = ( props ) => {   const [ isOpen, setOpen ] = useState( false );   return (     <>       <Button onClick={ () => setOpen( true ) }>         { __('Open Guide: “Creating Persisted Queries”') }       </Button>       { isOpen && (         <MyGuide            { ...props }           onFinish={ () => setOpen( false ) }         />       ) }     </>   ); }; export default MyGuideWithButton;

Even though the block editor tries to hide it, we are operating within React. Until now, we’ve been dealing with JSX and components. But now we need the useState hook, which is specific to React.

I’d say that having a good grasp of React is required if you want to master the WordPress block editor. There is no way around it.

Step 4: Adding content to the guide

We’re almost there! Let’s create the <Guide> component, containing a <GuidePage> component for each page of content.

The content can use HTML, include other components, and whatnot. In this particular case, I have added three <GuidePage> instances for my CPT just using HTML. The first page includes a video tutorial and the next two pages contain detailed instructions.

import { Guide, GuidePage } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; 
 const MyGuide = ( props ) => {   return (     <Guide { ...props } >       <GuidePage>         <video width="640" height="400" controls>           <source src="https://d1c2lqfn9an7pb.cloudfront.net/presentations/graphql-api/videos/graphql-api-creating-persisted-query.mov" type="video/mp4" />           { __('Your browser does not support the video tag.') }         </video>         // etc.       </GuidePage>       <GuidePage>         // ...       </GuidePage>       <GuidePage>         // ...       </GuidePage>     </Guide>   ) } export default MyGuide;
imaged gif showing the mouse cursor clicking on the Open Guide link in the block editor's document settings, which opens the custom welcome guide containing a video with links to other pages in the modal.
Hey look, we have our own guide now!

Not bad! There are a few issues, though:

  • I couldn’t embed the video inside the <Guide> because clicking the play button closes the guide. I assume that’s because the <iframe> falls outside the boundaries of the guide. I wound up uploading the video file to S3 and serving with <video>.
  • The page transition in the guide is not very smooth. The block editor’s modal looks alright because all pages have a similar height, but the transition in this one is pretty abrupt.
  • The hover effect on buttons could be improved. Hopefully, the Gutenberg team needs to fix this for their own purposes, because my CSS aren’t there. It’s not that my skills are bad; they are nonexistent.

But I can live with these issues. Functionality-wise, I’ve achieved what I need the guide to do.

Bonus: Opening docs independently 

For our <Guide>, we created the content of each <GuidePage> component directly using HTML. However, if this HTML code is instead added through an autonomous component, then it can be reused for other user interactions.

For instance, the component <CacheControlDescription> displays a description concerning HTTP caching:

const CacheControlDescription = () => {   return (     <p>The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or "no-store" if the max-age is 0</p>   ) } export default CacheControlDescription;

This component can be added inside a <GuidePage> as we did before, but also within a <Modal> component:

import { useState } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import CacheControlDescription from './cache-control-desc'; 
 const CacheControlModalWithButton = ( props ) => {   const [ isOpen, setOpen ] = useState( false );   return (     <>       <Button          icon="editor-help"         onClick={ () => setOpen( true ) }       />       { isOpen && (         <Modal            { ...props }           onRequestClose={ () => setOpen( false ) }         >           <CacheControlDescription />         </Modal>       ) }     </>   ); }; export default CacheControlModalWithButton;

To provide a good user experience, we can offer to show the documentation only when the user is interacting with the block. For that, we show or hide the button depending on the value of isSelected:

import { __ } from '@wordpress/i18n'; import CacheControlModalWithButton from './modal-with-btn'; 
 const CacheControlHeader = ( props ) => {   const { isSelected } = props;   return (     <>       { __('Cache-Control max-age') }       { isSelected && (         <CacheControlModalWithButton />       ) }     </>   ); } export default CacheControlHeader;

Finally, the <CacheControlHeader> component is added to the appropriate control.

Animated gif showing the option to view a guide displaying when a block is selected in the editor.

Tadaaaaaaaa 🎉

The WordPress block editor is quite a piece of software! I was able to accomplish things with it that I would have been unable to without it. Providing documentation to the user may not be the shiniest of examples or use cases, but it’s a very practical one and something that’s relevant for many other plugins. Want to use it for your own plugin? Go for it!

The post Adding a Custom Welcome Guide to the WordPress Block Editor appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

On Adding IDs to Headers

Here’s a two-second review. If an element has an ID, you can link to it with natural browser behavior. It’s great if headings have them, because it’s often useful to link directly to a specific section of content.

<h3 id="step-2">Step 2</a>

Should I be so inclined, I could link right to this heading, be it from an URL, like https://my-website.com/#step-2, or an on-page link, like:

<a href="#step-2">Jump to Step 2</a>

So, it’s ideal if all headers have unique IDs.

I find it entirely too much work to manually add IDs to all my headers though. For years and years, I did it like this using jQuery on this very site (sue me):

// Adjust this for targetting the headers important to have IDs const $ headers = $ (".article-content > h3");  $ headers.each((i, el) => {   const $ el = $ (el);    // Probably a flexbox layout style page   if ($ el.has("a").length != 0) {     return;   }    let idToLink = "";    if ($ el.attr("id") === undefined) {     // give it ID     idToLink = "article-header-id-" + i;     $ el.attr("id", idToLink);   } else {     // already has ID     idToLink = $ el.attr("id");   }    const $ headerLink = $ ("<a />", {     html: "#",     class: "article-headline-link",     href: "#" + idToLink   });    $ el.addClass("has-header-link").prepend($ headerLink); });

That script goes one step further than just adding IDs (if it doesn’t already have one) by adding a # link right inside the heading that links to that heading. The point of that is to demonstrate that the headers have IDs, and makes it easy to do stuff like right-click copy-link. Here’s that demo, if you care to see it.

Problem! All the sudden this stopped working.

Not the script itself, that works fine. But the native browser behavior that allows the browser to jump down to the heading when the page loads is what’s busted. I imagine it’s a race condition:

  1. The HTML arrives
  2. The page starts to render
  3. The browser is looking for the ID in the URL to scroll down to
  4. It doesn’t find it…
  5. Oh wait there it is!
  6. Scroll there.

The Oh wait there it is! step is from the script executing and putting that ID on the heading. I really don’t blame browsers for not jumping to dynamically-inserted links. I’m surprised this worked for as long as it did.

It’s much better to have the IDs on the headings by the time the HTML arrives. This site is WordPress, so I knew I could do it with some kind of content filter. Turns out I didn’t even have to bother because, of course, there is a plugin for that: Karolína Vyskočilová‘s Add Anchor Links. Works great for me. It’s technique is that it adds the ID on the anchor link itself, which is also totally fine. I guess that’s another way of avoiding messing with existing IDs.

If I didn’t have WordPress, I would have found some other way to process the HTML server-side to make sure there is some kind of heading link happening somehow. There is always a way. In fact, if it was too weird or cumbersome or whatever to do during the build process or in a server-side filter, I would look at doing it in a service worker. I’ve been having fun playing with Cloudflare’s HTMLRewriter, which is totally capable of this.

The post On Adding IDs to Headers appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]