Web Standards

Using Percy to add visual testing to a Jekyll site

Css Tricks - Thu, 06/27/2019 - 4:57am

Visual testing is the automated process of reviewing software from a purely visual standpoint. Instead of testing the code underneath, visual testing is all about what end users actually see and interact with.

Similar to functional testing, however, visual testing fits directly into your stack and workflow. And with Percy, it’s easy to add snapshots to and run visual reviews for anything that runs in a browser.

Let’s set up an app to test

In this tutorial, we’re going to clone an example Jekyll site, add Percy snapshots, make some visual changes, and review them in Percy.

Before we add Percy, let’s set up our example app:

git clone https://github.com/percy/example-percy-jekyll.git

Navigate to your local app, install dependencies, then build and run the site:

cd example-percy-jekyll/ bundle install bundle exec jekyll build bundle exec jekyll serve

Note: Ruby >= 2.3 is required

Open and click around the site locally to see what we’ll be taking Percy snapshots of.

Shoutout to CloudCannon for the Hydra theme.

Next, if you haven’t already, sign up for a free Percy account and create an organization and a new project:

Percy projects correspond to the web application, component library, or static site you’re testing. In this case, we’ll be connecting our Percy project to our Jekyll site.

Generating Percy snapshots

To authenticate your local environment with your new Percy project, copy the PERCY_TOKEN environment variable from the new project screen or your project settings, then run:

$ export PERCY_TOKEN=aaabbbcccdddeee $ npx percy snapshot _site/

Note: Be sure to replace the token with your project-specific token.

You’ll see output like this:

$ npx percy snapshot _site/ Downloading Chromium r662092 - 88.3 Mb [====================] 100% 0.0s [percy] created build #1: https://percy.io/jekyll-test-site/hydra-theme/builds/2048096 [percy] percy has started. [percy] serving static site at http://localhost:5339/ [percy] snapshot taken: '/404.html' [percy] snapshot taken: '/index.html' [percy] snapshot taken: '/about/index.html' [percy] snapshot taken: '/blog/index.html' [percy] snapshot taken: '/contact/index.html' [percy] snapshot taken: '/contact-success/index.html' [percy] snapshot taken: '/pricing/index.html' [percy] snapshot taken: '/category/marketing/index.html' [percy] snapshot taken: '/category/sales/index.html' [percy] snapshot taken: '/category/tips/index.html' [percy] snapshot taken: '/sales/2016/07/20/the-process-for-direct-sales/index.html' [percy] snapshot taken: '/marketing/2016/08/12/the-history-of-marketing/index.html' [percy] snapshot taken: '/sales/2016/08/06/definition-of-sales/index.html' [percy] snapshot taken: '/sales/tips/2016/07/28/effective-upselling-techniques/index.html' [percy] snapshot taken: '/sales/tips/2016/08/02/sales-effectiveness/index.html' [percy] shutting down static site at http://localhost:5339/ [percy] stopping percy... [percy] waiting for 15 snapshots to complete... [percy] done. [percy] finalized build #1: https://percy.io/jekyll-test-site/hydra-theme/builds/2048096

Click the build link or head over to your Percy project to check out your first build.

What’s going on behind the scenes?

Once npx percy snapshot _site/ was called, Percy captured the DOM snapshots for each page of your Jekyll site. Percy then recreated the snapshots to compare against baseline snapshots and determine which pixels have changed.

Since this is the first build, there isn’t anything to compare it to. It has also been auto-approved because the commit was on master and we assume that master builds are production-ready.

Making and reviewing visual changes

Now that you’ve pushed your first build and established a baseline to compare your next snapshots to, let’s make a visual change to review.

Let’s make a wide sweeping CSS change. Head over to the cloudcannon.scss file and change the brand colors:

$brand-color: #ff8a00; $secondary-brand-color: #da1b60;

Since we’re pushing these changes directly to master, be sure to disable auto-approve on master branches from your Percy project settings.

Once those changes are saved, build your site and run Percy again:

$ bundle exec jekyll build $ npx percy snapshot _site/

Head back to Percy or click the Percy build link to see the visual changes!

If you haven’t been following along, you can check out the build.

What’s going on in the Percy UI?

The red areas in the right panel represent pixels that have changed—the visual diffs. Clicking that area (or pressing "d") will toggle between the underlying snapshot and the overlaid diff so you can easily see what exactly has changed. You’ll also notice that the first several diffs have been matched and grouped to make it easier and faster to review.

Each snapshot has been rendered across both Chrome and Firefox, and at mobile and desktop widths. Rendering your site across these variations helps you detect subtle differences caused by browser rendering or responsive bugs.

Now that we’re happy with our fresh new look, hit "Approve All." ✅

You’ve done your first visual review! Visual testing is great not only for catching visual bugs, but also for knowing the exact impact of any given code change. Seeing your UI visualized during code reviews is invaluable, helping you fix regressions before they make their way to production, or to deploy with complete confidence.

Configuring your snapshots

You can configure how and where Percy runs for each build by creating a global .percy.yml file in the root of your project.

You can customize the responsive widths at which your snapshots are rendered. For example, if you want to add a super wide snapshot in addition to our mobile and desktop widths:

version: 1 snapshot: widths: [375, 1280, 1920]

You can also ignore certain pages. For example, if you’d like to ignore multiple pages with the same layout like blog categories posts:

version: 1 static-snapshots: ignore-files: "/blog/category/*"

Learn more about Percy SDK configuration in our docs).

Adding Percy to your workflow

What we’ve done so far demonstrates how Percy generates snapshots and detects visual changes locally, but to get the most value out of automated visual testing, we recommend integrating Percy with your CI service.

For instructions and to see all of our supported CI services, check out our CI setup documentation. Here are a few of our most popular supported services:

Visual testing is best when done alongside code reviews. We support integrations with GitHub, GitLab, and Bitbucket (coming soon)! With an integration enabled, Percy will show up in your checks commit and pull request checks, notifying you if visual changes are detected:

Clicking "Details" will take you right to the Percy build where you can review visual changes.
After snapshots are approved, your commit status will change to green and the pull request can be merged.

With continuous visual reviews on every feature branch, it’s easy to have 100% confidence the visual changes you’ll be deploying.

We hope this tutorial has helped you get acquainted with Percy’s visual review platform and workflow. To learn more about how Percy works, feel free to check out these docs:

Happy testing! 💜

Using Percy to add visual testing to a Jekyll site

Css Tricks - Thu, 06/27/2019 - 4:57am

Visual testing is the automated process of reviewing software from a purely visual standpoint. Instead of testing the code underneath, visual testing is all about what end users actually see and interact with.

Similar to functional testing, however, visual testing fits directly into your stack and workflow. And with Percy, it’s easy to add snapshots to and run visual reviews for anything that runs in a browser.

Let’s set up an app to test

In this tutorial, we’re going to clone an example Jekyll site, add Percy snapshots, make some visual changes, and review them in Percy.

Before we add Percy, let’s set up our example app:

git clone https://github.com/percy/example-percy-jekyll.git

Navigate to your local app, install dependencies, then build and run the site:

cd example-percy-jekyll/ bundle install bundle exec jekyll build bundle exec jekyll serve

Note: Ruby >= 2.3 is required

Open and click around the site locally to see what we’ll be taking Percy snapshots of.

Shoutout to CloudCannon for the Hydra theme.

Next, if you haven’t already, sign up for a free Percy account and create an organization and a new project:

Percy projects correspond to the web application, component library, or static site you’re testing. In this case, we’ll be connecting our Percy project to our Jekyll site.

Generating Percy snapshots

To authenticate your local environment with your new Percy project, copy the PERCY_TOKEN environment variable from the new project screen or your project settings, then run:

$ export PERCY_TOKEN=aaabbbcccdddeee $ npx percy snapshot _site/

Note: Be sure to replace the token with your project-specific token.

You’ll see output like this:

$ npx percy snapshot _site/ Downloading Chromium r662092 - 88.3 Mb [====================] 100% 0.0s [percy] created build #1: https://percy.io/jekyll-test-site/hydra-theme/builds/2048096 [percy] percy has started. [percy] serving static site at http://localhost:5339/ [percy] snapshot taken: '/404.html' [percy] snapshot taken: '/index.html' [percy] snapshot taken: '/about/index.html' [percy] snapshot taken: '/blog/index.html' [percy] snapshot taken: '/contact/index.html' [percy] snapshot taken: '/contact-success/index.html' [percy] snapshot taken: '/pricing/index.html' [percy] snapshot taken: '/category/marketing/index.html' [percy] snapshot taken: '/category/sales/index.html' [percy] snapshot taken: '/category/tips/index.html' [percy] snapshot taken: '/sales/2016/07/20/the-process-for-direct-sales/index.html' [percy] snapshot taken: '/marketing/2016/08/12/the-history-of-marketing/index.html' [percy] snapshot taken: '/sales/2016/08/06/definition-of-sales/index.html' [percy] snapshot taken: '/sales/tips/2016/07/28/effective-upselling-techniques/index.html' [percy] snapshot taken: '/sales/tips/2016/08/02/sales-effectiveness/index.html' [percy] shutting down static site at http://localhost:5339/ [percy] stopping percy... [percy] waiting for 15 snapshots to complete... [percy] done. [percy] finalized build #1: https://percy.io/jekyll-test-site/hydra-theme/builds/2048096

Click the build link or head over to your Percy project to check out your first build.

What’s going on behind the scenes?

Once npx percy snapshot _site/ was called, Percy captured the DOM snapshots for each page of your Jekyll site. Percy then recreated the snapshots to compare against baseline snapshots and determine which pixels have changed.

Since this is the first build, there isn’t anything to compare it to. It has also been auto-approved because the commit was on master and we assume that master builds are production-ready.

Making and reviewing visual changes

Now that you’ve pushed your first build and established a baseline to compare your next snapshots to, let’s make a visual change to review.

Let’s make a wide sweeping CSS change. Head over to the cloudcannon.scss file and change the brand colors:

$brand-color: #ff8a00; $secondary-brand-color: #da1b60;

Since we’re pushing these changes directly to master, be sure to disable auto-approve on master branches from your Percy project settings.

Once those changes are saved, build your site and run Percy again:

$ bundle exec jekyll build $ npx percy snapshot _site/

Head back to Percy or click the Percy build link to see the visual changes!

If you haven’t been following along, you can check out the build.

What’s going on in the Percy UI?

The red areas in the right panel represent pixels that have changed—the visual diffs. Clicking that area (or pressing "d") will toggle between the underlying snapshot and the overlaid diff so you can easily see what exactly has changed. You’ll also notice that the first several diffs have been matched and grouped to make it easier and faster to review.

Each snapshot has been rendered across both Chrome and Firefox, and at mobile and desktop widths. Rendering your site across these variations helps you detect subtle differences caused by browser rendering or responsive bugs.

Now that we’re happy with our fresh new look, hit "Approve All." ✅

You’ve done your first visual review! Visual testing is great not only for catching visual bugs, but also for knowing the exact impact of any given code change. Seeing your UI visualized during code reviews is invaluable, helping you fix regressions before they make their way to production, or to deploy with complete confidence.

Configuring your snapshots

You can configure how and where Percy runs for each build by creating a global .percy.yml file in the root of your project.

You can customize the responsive widths at which your snapshots are rendered. For example, if you want to add a super wide snapshot in addition to our mobile and desktop widths:

version: 1 snapshot: widths: [375, 1280, 1920]

You can also ignore certain pages. For example, if you’d like to ignore multiple pages with the same layout like blog categories posts:

version: 1 static-snapshots: ignore-files: "/blog/category/*"

Learn more about Percy SDK configuration in our docs).

Adding Percy to your workflow

What we’ve done so far demonstrates how Percy generates snapshots and detects visual changes locally, but to get the most value out of automated visual testing, we recommend integrating Percy with your CI service.

For instructions and to see all of our supported CI services, check out our CI setup documentation. Here are a few of our most popular supported services:

Visual testing is best when done alongside code reviews. We support integrations with GitHub, GitLab, and Bitbucket (coming soon)! With an integration enabled, Percy will show up in your checks commit and pull request checks, notifying you if visual changes are detected:

Clicking "Details" will take you right to the Percy build where you can review visual changes.
After snapshots are approved, your commit status will change to green and the pull request can be merged.

With continuous visual reviews on every feature branch, it’s easy to have 100% confidence the visual changes you’ll be deploying.

We hope this tutorial has helped you get acquainted with Percy’s visual review platform and workflow. To learn more about how Percy works, feel free to check out these docs:

Happy testing! 💜

The post Using Percy to add visual testing to a Jekyll site appeared first on CSS-Tricks.

Three Predictions From the State of CSS 2019 Survey

Css Tricks - Wed, 06/26/2019 - 2:31pm

Running a developer survey like the State of CSS is a multi-stage process. First, you need to collect the data. Then, you process it into a usable shape. Finally, you come up with nifty ways to visualize it and release it to the world.

But then, once the dust settles and the traffic dies down comes my favorite part: actually thinking about the data. By taking a deeper look at our data, as well as observing how the community discussed our findings, three unexpected trends ended up coming into focus.

But first, some background for those not already familiar with the project.

I first started the State of JavaScript survey three years ago in 2016 as a way to answer my own uncertainties about the future of web development. At the time, JavaScript fatigue was running wild and I thought a comprehensive developer survey could prove itself the antidote.

The original State of JavaScript 2016 edition

Turns out I hit a nerve: that first survey turned out to be very popular, and our audience has grown each year since, along with the scope of the survey. (I was also joined by Raphael Benitte, creator of the Nivo.js dataviz library, to help me with data processing and visualization.) This year marks the first time we're pivoting out into a new dimension, namely the not-so-simple world of CSS.

Taking on CSS Prediction 1: CSS still has a lot of unexplored territory

One of the things we wanted to quantify with the survey was how much of CSS was still left "unexplored." In other words, what CSS features are developers either unfamiliar with, or else hadn't yet used. For that reason we decided early-on to focus our Features section on new CSS properties, like shapes, masking, or scroll-snap rather than “boring” floats or tables.

The resulting data paints an interesting picture: it turns out that when you look at it this way, CSS morphs from a familiar landscape to a wild, unexplored jungle.

A look at comparing Flexbox vs. CSS Grid provides a good illustration of this trend. While nearly everybody who's heard of Flexbox has also used it, only 55% of developers who are aware of CSS Grid have actually tried it. That's a big gap, especially for a technology as important as CSS Grid!

Layout Features

Or take CSS Shapes: 68% of developers are aware of them, only 31% of that group has actually used the feature.

CSS Shapes

This all points at a big gap between what we collectively want to learn and what we actually know. It's that potential for growth that is exactly what makes CSS so exciting in 2019.

Prediction 2: Functional CSS will keep rising

If you're old enough to remember the CSS Zen Garden — or to have actually learned CSS through it (in which case I know how you feel, my back hurts when I get up in the morning as well) — then this next trend might seem weird, or even downright wrong.

CSS Zen Garden: one page, many themes.

Functional CSS rejects the platonic ideal of pure, untainted markup free from any styling concerns and embraces "functional" (aka "atomic" or "utility") classes. Think <div class="text-red text-medium border-1">...</div>.

Adopting this approach means you can't magically update your stylesheet and change your entire design without modifying a single line of markup. But be honest, how often does this happen anyway? Compared to the often theoretical elegance of the Zen Garden philosophy, libraries like Tailwind and Tachyons provide tangible, real-world benefits, which explains why they're so highly regarded. In fact, those take the #1 and #4 spots, respectively, in terms of satisfaction ratio in the CSS Framework category.

Awareness, interest, and satisfaction ratio rankings for CSS frameworks.

Tailwind especially seems to be picking up speed, at least judging by the Twitter engagement from its community in response to the survey results. Having just hit version 1.0, it's definitely a project to keep an eye on!

Prediction 3: The battle for CSS has just begun

Looking at our data, I can't help but wonder if "JavaScript fatigue" will soon be replaced by "CSS fatigue."

When evaluating technologies, it's important to look not just at raw usage numbers, but also at user satisfaction. After all, you don't want to jump on the latest bandwagon just to find out its current occupants can't wait to hop off it.

This scatterplot chart that's divided into quadrants is perfect for this. It plots usage against satisfaction, making it easy to isolate popular, high-satisfaction tools into their own quadrant.

Usage vs. Satisfaction

What's apparent in this chart is that the most densely populated area is the "Assess" quadrant. In other words, the low-usage, high-satisfaction technologies that are still battling it out for supremacy. This is exactly the state that the JavaScript ecosystem finds itself in as well. Many contenders, but few decisive winners as of today.

This is not necessarily a bad thing: yes, it does make the average developer's life harder when it comes to picking the right tool, but hey, this is why we do what we do! Additionally, competition can only be good for the ecosystem as a whole. Once the dust settles, we'll hopefully end up with the best possible options having survived!

CSS in 2019

Overall, the State of CSS survey shows that this is not your grandpa's CSS anymore. For years, we developers have loved to complain about the inadequacies of CSS and its lack of powerful features. But in 2019, CSS is challenging us to put our money where our mouthes are: here's all the features you've always wanted. Now what are you going to do about it?

I, for one, am very excited to dive even deeper into this new world of styling. And, of course, to tune back in 2020 to see what new trends we find then!

Three Predictions From the State of CSS 2019 Survey

Css Tricks - Wed, 06/26/2019 - 2:31pm

Running a developer survey like the State of CSS is a multi-stage process. First, you need to collect the data. Then, you process it into a usable shape. Finally, you come up with nifty ways to visualize it and release it to the world.

But then, once the dust settles and the traffic dies down comes my favorite part: actually thinking about the data. By taking a deeper look at our data, as well as observing how the community discussed our findings, three unexpected trends ended up coming into focus.

But first, some background for those not already familiar with the project.

I first started the State of JavaScript survey three years ago in 2016 as a way to answer my own uncertainties about the future of web development. At the time, JavaScript fatigue was running wild and I thought a comprehensive developer survey could prove itself the antidote.

The original State of JavaScript 2016 edition

Turns out I hit a nerve: that first survey turned out to be very popular, and our audience has grown each year since, along with the scope of the survey. (I was also joined by Raphael Benitte, creator of the Nivo.js dataviz library, to help me with data processing and visualization.) This year marks the first time we're pivoting out into a new dimension, namely the not-so-simple world of CSS.

Taking on CSS Prediction 1: CSS still has a lot of unexplored territory

One of the things we wanted to quantify with the survey was how much of CSS was still left "unexplored." In other words, what CSS features are developers either unfamiliar with, or else hadn't yet used. For that reason we decided early-on to focus our Features section on new CSS properties, like shapes, masking, or scroll-snap rather than “boring” floats or tables.

The resulting data paints an interesting picture: it turns out that when you look at it this way, CSS morphs from a familiar landscape to a wild, unexplored jungle.

A look at comparing Flexbox vs. CSS Grid provides a good illustration of this trend. While nearly everybody who's heard of Flexbox has also used it, only 55% of developers who are aware of CSS Grid have actually tried it. That's a big gap, especially for a technology as important as CSS Grid!

Layout Features

Or take CSS Shapes: 68% of developers are aware of them, only 31% of that group has actually used the feature.

CSS Shapes

This all points at a big gap between what we collectively want to learn and what we actually know. It's that potential for growth that is exactly what makes CSS so exciting in 2019.

Prediction 2: Functional CSS will keep rising

If you're old enough to remember the CSS Zen Garden — or to have actually learned CSS through it (in which case I know how you feel, my back hurts when I get up in the morning as well) — then this next trend might seem weird, or even downright wrong.

CSS Zen Garden: one page, many themes.

Functional CSS rejects the platonic ideal of pure, untainted markup free from any styling concerns and embraces "functional" (aka "atomic" or "utility") classes. Think <div class="text-red text-medium border-1">...</div>.

Adopting this approach means you can't magically update your stylesheet and change your entire design without modifying a single line of markup. But be honest, how often does this happen anyway? Compared to the often theoretical elegance of the Zen Garden philosophy, libraries like Tailwind and Tachyons provide tangible, real-world benefits, which explains why they're so highly regarded. In fact, those take the #1 and #4 spots, respectively, in terms of satisfaction ratio in the CSS Framework category.

Awareness, interest, and satisfaction ratio rankings for CSS frameworks.

Tailwind especially seems to be picking up speed, at least judging by the Twitter engagement from its community in response to the survey results. Having just hit version 1.0, it's definitely a project to keep an eye on!

Prediction 3: The battle for CSS has just begun

Looking at our data, I can't help but wonder if "JavaScript fatigue" will soon be replaced by "CSS fatigue."

When evaluating technologies, it's important to look not just at raw usage numbers, but also at user satisfaction. After all, you don't want to jump on the latest bandwagon just to find out its current occupants can't wait to hop off it.

This scatterplot chart that's divided into quadrants is perfect for this. It plots usage against satisfaction, making it easy to isolate popular, high-satisfaction tools into their own quadrant.

Usage vs. Satisfaction

What's apparent in this chart is that the most densely populated area is the "Assess" quadrant. In other words, the low-usage, high-satisfaction technologies that are still battling it out for supremacy. This is exactly the state that the JavaScript ecosystem finds itself in as well. Many contenders, but few decisive winners as of today.

This is not necessarily a bad thing: yes, it does make the average developer's life harder when it comes to picking the right tool, but hey, this is why we do what we do! Additionally, competition can only be good for the ecosystem as a whole. Once the dust settles, we'll hopefully end up with the best possible options having survived!

CSS in 2019

Overall, the State of CSS survey shows that this is not your grandpa's CSS anymore. For years, we developers have loved to complain about the inadequacies of CSS and its lack of powerful features. But in 2019, CSS is challenging us to put our money where our mouthes are: here's all the features you've always wanted. Now what are you going to do about it?

I, for one, am very excited to dive even deeper into this new world of styling. And, of course, to tune back in 2020 to see what new trends we find then!

The post Three Predictions From the State of CSS 2019 Survey appeared first on CSS-Tricks.

Getting to Know the useReducer React Hook

Css Tricks - Wed, 06/26/2019 - 3:59am

useReducer is one of a handful of React hooks that shipped in React 16.7.0. It accepts a reducer function with the application initial state, returns the current application state, then dispatches a function.

Here is an example of how it is used;

const [state, dispatch] = useReducer(reducer, initialState);

What’s the good for? Well, think about any situation where having the first loaded state of the application might be nice. Let’s say the starting point on an interactive map. Maybe it’s an app that lets the user build a custom car with custom options from a default model. Here’s a pretty neat demo of a calculator app that puts useRedcuer to use in order to reset the calculator to a default state of zero when clearing it out.

See the Pen
Basic React Hook Calculator
by Gianpierangelo De Palma (@dpgian)
on CodePen.

We’re going to dig into a couple more examples in this post, but let’s first look at the hook itself to get a better idea of what it is and what exactly it does when it’s used.

The almighty reducer

It’s tough to talk about useState without also mentioning JavaScript’s reduce method. We linked it up at the very top, but Sarah’s post is an excellent overview of reducers and helps set the state for where we’re going here.

The first and most important thing to understand about a reducer is that it will always only return one value. The job of a reducer is to reduce. That one value can be a number, a string, an array or an object, but it will always only be one. Reducers are really great for a lot of things, but they're especially useful for applying a bit of logic to a group of values and ending up with another single result.

So, if we have an array of numbers, reduce will distill it down to a single number that adds up for as many times as there are values. Say we have this simple array:

const numbers = [1, 2, 3]

...and we have a function that logs each time our reducer makes a calculation into the console. This will help us see how reduce distills the array into a single number.

const reducer = function (tally, number) { console.log(`Tally: ${tally}, Next number: ${number}, New Total: ${tally + number}`) return tally + number }

Now let’s run a reducer on it. As we saw earlier, reduce takes dispatches a function that runs against a default state. Let’s plug our reducer function and an initial value of zero in there.

const total = numbers.reduce(reducer, 0)

Here’s what gets logged to the console:

"Tally: 0, Next number: 1, New Total: 1" "Tally: 1, Next number: 2, New Total: 3" "Tally: 3, Next number: 3, New Total: 6"

See how reduce takes an initial value and builds on it as each number in the array is added to it until we get a final value? In this case, that final value is 6.

I also really like this (modified) example from Dave Ceddia that shows how reduce can be used on an array of letters to spell a word:

var letters = ['r', 'e', 'd', 'u', 'c', 'e']; // `reduce` takes 2 arguments: // - a function to do the reducing (you might say, a "reducer") // - an initial value for accumulatedResult var word = letters.reduce( function(accumulatedResult, arrayItem) { return accumulatedResult + arrayItem; }, ''); // <-- notice this empty string argument: it's the initial value console.log(word) // => "reduce" useReducer works with states and actions

OK, that was a lot of refresher to get what we’re really talking about: userReducer. It’s important to get all this, though, because you may have noticed where we’re going now after having seen the way reduce fires a function against an initial value. It’s the same sort of concept, but returns two elements as an array, the current state and a dispatch function.

In other words:

const [state, dispatch] = useReducer(reducer, initialArg, init);

What’s up with that third init argument? It’s an optional value that will lazily create the initial state. That means we can calculate the initial state/value with an init function outside of the reducer instead of providing an explicit value. That’s handy if the initial value could be different, say based on a last saved state instead of a consistent value.

To get it working, we need to do a few things:

  • Define an initial state.
  • Provide a function that contains actions that update the state.
  • Trigger userReducer to dispatch an updated state that’s calculated relative to the initial state.

The classic example of this a counter application. In fact, that’s what React’s docs use to drive the concept home. Here’s that put into practice:

See the Pen
React useReducer 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

It’s a good example because it demonstrates how an initial state (a zero value) is used to calculate a new value each time an action is fired by clicking either the increase or decrease button. We could even throw in a “Reset" button in there to clear the total back to the initial state of zero.

Example: A Car Customizer

See the Pen
React useReducer - car example
by Geoff Graham (@geoffgraham)
on CodePen.

In this example, we are making the assumption that the user has selected a car to purchase. However, we want the app to allow the user to add extra options to the car. Each option has a price that adds to the base total.

First, we need to create the initial state which will consist of the car, an empty array to keep track of features, and an additional price that starts at $26,395 and a list of items in the store, so the user can pick what they want.

const initialState = { additionalPrice: 0, car: { price: 26395, name: "2019 Ford Mustang", image: "https://cdn.motor1.com/images/mgl/0AN2V/s1/2019-ford-mustang-bullitt.jpg", features: [] }, store: [ { id: 1, name: "V-6 engine", price: 1500 }, { id: 2, name: "Racing detail package", price: 1500 }, { id: 3, name: "Premium sound system", price: 500 }, { id: 4, name: "Rear spoiler", price: 250 } ] };

Our reducer function will handle two things: the addition and removal of new items.

const reducer = (state, action) => { switch (action.type) { case "REMOVE_ITEM": return { ...state, additionalPrice: state.additionalPrice - action.item.price, car: { ...state.car, features: state.car.features.filter((x) => x.id !== action.item.id)}, store: [...state.store, action.item] }; case "BUY_ITEM": return { ...state, additionalPrice: state.additionalPrice + action.item.price, car: { ...state.car, features: [...state.car.features, action.item] }, store: state.store.filter((x) => x.id !== action.item.id) } default: return state; } }

When the user selects the item she wants, we update the features for the car, increase the additionalPrice and also remove the item from the store. We ensure that the other parts of the state remain as they are.
We do something similar when a user removes an item from the features list - reduce the additional price, return the item to the store.
Here is how the App component looks like.

const App = () => { const inputRef = useRef(); const [state, dispatch] = useReducer(reducer, initialState); const removeFeature = (item) => { dispatch({ type: 'REMOVE_ITEM', item }); } const buyItem = (item) => { dispatch({ type: 'BUY_ITEM', item }) } return ( <div> <div className="box"> <figure className="image is-128x128"> <img src={state.car.image} /> </figure> <h2>{state.car.name}</h2> <p>Amount: ${state.car.price}</p> <div className="content"> <h6>Extra items you bought:</h6> {state.car.features.length ? ( <ol type="1"> {state.car.features.map((item) => ( <li key={item.id}> <button onClick={() => removeFeature(item)} className="button">X </button> {item.name} </li> ))} </ol> ) : <p>You can purchase items from the store.</p> } </div> </div> <div className="box"> <div className="content"> <h4>Store:</h4> {state.store.length ? ( <ol type="1"> {state.store.map((item) => ( <li key={item.id}>\ <button onClick={() => buyItem(item)} className="button">Buy </button> {item.name} </li> ))} </ol> ) : <p>No features</p> } </div> <div className="content"> <h4> Total Amount: ${state.car.price + state.additionalPrice} </h4> </div> </div> </div> ); }

The actions that get dispatched contains the details of the selected item. We make use of the action type to determine how the reducer function will handle the updating of the state. You can see that the rendered view changes based on what you do - buying an item from the store removes the item from the store and adds it to the list of features. Also, the total amount gets updated. No doubt, there are some improvements that can be done to the application, this is only for learning purpose.

What about useState? Can’t we use that instead?

An astute reader may have been asking this all along. I mean, setState is generally the same thing, right? Return a stateful value and a function to re-render a component with that new value.

const [state, setState] = useState(initialState);

We could have even used the useState() hook in the counter example provided by the React docs. However, useReducer is preferred in cases where state has to go through complicated transitions. Kent C. Dodds wrote up a explanation of the differences between the two and (while he often reaches for setState) he provides a good use case for using userReducer instead:

If your one element of your state relies on the value of another element of your state, then it's almost always best to use useReducer

For example, imagine you have a tic-tac-toe game you're writing. You have one element of state called squares which is just an array of all the squares and their value[.]

My rule of thumb is to reach for useReducer to handle complex states, particularly where the initial state is based on the state of other elements.

Oh wait, we already have Redux for this!

Those of you who have worked with Redux already know everything we’ve covered here and that’s because it was designed to use the Context API to pass stored states between components — without having to pass props through other components to get there.

So, does useReducer replace Redux? Nope. I mean, you can basically make your own Redux by using it with the useContext hook, but that’s doesn’t mean Redux is useless; Redux still has plenty of other features and benefits worth considering.

Where have you used userReducer? Have you found clear-cut cases where it’s better than setState? Maybe you can experiment with the things we covered here to build something. Here are a few ideas:

  • A calendar that focus at today’s date but allows a user to select other dates. Maybe even add a “Today" button that returns the user to today’s date.
  • You can try improving on the car example - have a list of cars that users can purchase. You might have to define this in the initial state, then the user can add extra features they want with a charge. These features can be predefined, or defined by the user.

Getting to Know the useReducer React Hook

Css Tricks - Wed, 06/26/2019 - 3:59am

useReducer is one of a handful of React hooks that shipped in React 16.7.0. It accepts a reducer function with the application initial state, returns the current application state, then dispatches a function.

Here is an example of how it is used;

const [state, dispatch] = useReducer(reducer, initialState);

What’s the good for? Well, think about any situation where having the first loaded state of the application might be nice. Let’s say the starting point on an interactive map. Maybe it’s an app that lets the user build a custom car with custom options from a default model. Here’s a pretty neat demo of a calculator app that puts useRedcuer to use in order to reset the calculator to a default state of zero when clearing it out.

See the Pen
Basic React Hook Calculator
by Gianpierangelo De Palma (@dpgian)
on CodePen.

We’re going to dig into a couple more examples in this post, but let’s first look at the hook itself to get a better idea of what it is and what exactly it does when it’s used.

The almighty reducer

It’s tough to talk about useState without also mentioning JavaScript’s reduce method. We linked it up at the very top, but Sarah’s post is an excellent overview of reducers and helps set the state for where we’re going here.

The first and most important thing to understand about a reducer is that it will always only return one value. The job of a reducer is to reduce. That one value can be a number, a string, an array or an object, but it will always only be one. Reducers are really great for a lot of things, but they're especially useful for applying a bit of logic to a group of values and ending up with another single result.

So, if we have an array of numbers, reduce will distill it down to a single number that adds up for as many times as there are values. Say we have this simple array:

const numbers = [1, 2, 3]

...and we have a function that logs each time our reducer makes a calculation into the console. This will help us see how reduce distills the array into a single number.

const reducer = function (tally, number) { console.log(`Tally: ${tally}, Next number: ${number}, New Total: ${tally + number}`) return tally + number }

Now let’s run a reducer on it. As we saw earlier, reduce takes dispatches a function that runs against a default state. Let’s plug our reducer function and an initial value of zero in there.

const total = numbers.reduce(reducer, 0)

Here’s what gets logged to the console:

"Tally: 0, Next number: 1, New Total: 1" "Tally: 1, Next number: 2, New Total: 3" "Tally: 3, Next number: 3, New Total: 6"

See how reduce takes an initial value and builds on it as each number in the array is added to it until we get a final value? In this case, that final value is 6.

I also really like this (modified) example from Dave Ceddia that shows how reduce can be used on an array of letters to spell a word:

var letters = ['r', 'e', 'd', 'u', 'c', 'e']; // `reduce` takes 2 arguments: // - a function to do the reducing (you might say, a "reducer") // - an initial value for accumulatedResult var word = letters.reduce( function(accumulatedResult, arrayItem) { return accumulatedResult + arrayItem; }, ''); // <-- notice this empty string argument: it's the initial value console.log(word) // => "reduce" useReducer works with states and actions

OK, that was a lot of refresher to get what we’re really talking about: userReducer. It’s important to get all this, though, because you may have noticed where we’re going now after having seen the way reduce fires a function against an initial value. It’s the same sort of concept, but returns two elements as an array, the current state and a dispatch function.

In other words:

const [state, dispatch] = useReducer(reducer, initialArg, init);

What’s up with that third init argument? It’s an optional value that will lazily create the initial state. That means we can calculate the initial state/value with an init function outside of the reducer instead of providing an explicit value. That’s handy if the initial value could be different, say based on a last saved state instead of a consistent value.

To get it working, we need to do a few things:

  • Define an initial state.
  • Provide a function that contains actions that update the state.
  • Trigger userReducer to dispatch an updated state that’s calculated relative to the initial state.

The classic example of this a counter application. In fact, that’s what React’s docs use to drive the concept home. Here’s that put into practice:

See the Pen
React useReducer 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

It’s a good example because it demonstrates how an initial state (a zero value) is used to calculate a new value each time an action is fired by clicking either the increase or decrease button. We could even throw in a “Reset" button in there to clear the total back to the initial state of zero.

Example: A Car Customizer

See the Pen
React useReducer - car example
by Geoff Graham (@geoffgraham)
on CodePen.

In this example, we are making the assumption that the user has selected a car to purchase. However, we want the app to allow the user to add extra options to the car. Each option has a price that adds to the base total.

First, we need to create the initial state which will consist of the car, an empty array to keep track of features, and an additional price that starts at $26,395 and a list of items in the store, so the user can pick what they want.

const initialState = { additionalPrice: 0, car: { price: 26395, name: "2019 Ford Mustang", image: "https://cdn.motor1.com/images/mgl/0AN2V/s1/2019-ford-mustang-bullitt.jpg", features: [] }, store: [ { id: 1, name: "V-6 engine", price: 1500 }, { id: 2, name: "Racing detail package", price: 1500 }, { id: 3, name: "Premium sound system", price: 500 }, { id: 4, name: "Rear spoiler", price: 250 } ] };

Our reducer function will handle two things: the addition and removal of new items.

const reducer = (state, action) => { switch (action.type) { case "REMOVE_ITEM": return { ...state, additionalPrice: state.additionalPrice - action.item.price, car: { ...state.car, features: state.car.features.filter((x) => x.id !== action.item.id)}, store: [...state.store, action.item] }; case "BUY_ITEM": return { ...state, additionalPrice: state.additionalPrice + action.item.price, car: { ...state.car, features: [...state.car.features, action.item] }, store: state.store.filter((x) => x.id !== action.item.id) } default: return state; } }

When the user selects the item she wants, we update the features for the car, increase the additionalPrice and also remove the item from the store. We ensure that the other parts of the state remain as they are.
We do something similar when a user removes an item from the features list - reduce the additional price, return the item to the store.
Here is how the App component looks like.

const App = () => { const inputRef = useRef(); const [state, dispatch] = useReducer(reducer, initialState); const removeFeature = (item) => { dispatch({ type: 'REMOVE_ITEM', item }); } const buyItem = (item) => { dispatch({ type: 'BUY_ITEM', item }) } return ( <div> <div className="box"> <figure className="image is-128x128"> <img src={state.car.image} /> </figure> <h2>{state.car.name}</h2> <p>Amount: ${state.car.price}</p> <div className="content"> <h6>Extra items you bought:</h6> {state.car.features.length ? ( <ol type="1"> {state.car.features.map((item) => ( <li key={item.id}> <button onClick={() => removeFeature(item)} className="button">X </button> {item.name} </li> ))} </ol> ) : <p>You can purchase items from the store.</p> } </div> </div> <div className="box"> <div className="content"> <h4>Store:</h4> {state.store.length ? ( <ol type="1"> {state.store.map((item) => ( <li key={item.id}>\ <button onClick={() => buyItem(item)} className="button">Buy </button> {item.name} </li> ))} </ol> ) : <p>No features</p> } </div> <div className="content"> <h4> Total Amount: ${state.car.price + state.additionalPrice} </h4> </div> </div> </div> ); }

The actions that get dispatched contains the details of the selected item. We make use of the action type to determine how the reducer function will handle the updating of the state. You can see that the rendered view changes based on what you do - buying an item from the store removes the item from the store and adds it to the list of features. Also, the total amount gets updated. No doubt, there are some improvements that can be done to the application, this is only for learning purpose.

What about useState? Can’t we use that instead?

An astute reader may have been asking this all along. I mean, setState is generally the same thing, right? Return a stateful value and a function to re-render a component with that new value.

const [state, setState] = useState(initialState);

We could have even used the useState() hook in the counter example provided by the React docs. However, useReducer is preferred in cases where state has to go through complicated transitions. Kent C. Dodds wrote up a explanation of the differences between the two and (while he often reaches for setState) he provides a good use case for using userReducer instead:

If your one element of your state relies on the value of another element of your state, then it's almost always best to use useReducer

For example, imagine you have a tic-tac-toe game you're writing. You have one element of state called squares which is just an array of all the squares and their value[.]

My rule of thumb is to reach for useReducer to handle complex states, particularly where the initial state is based on the state of other elements.

Oh wait, we already have Redux for this!

Those of you who have worked with Redux already know everything we’ve covered here and that’s because it was designed to use the Context API to pass stored states between components — without having to pass props through other components to get there.

So, does useReducer replace Redux? Nope. I mean, you can basically make your own Redux by using it with the useContext hook, but that’s doesn’t mean Redux is useless; Redux still has plenty of other features and benefits worth considering.

Where have you used userReducer? Have you found clear-cut cases where it’s better than setState? Maybe you can experiment with the things we covered here to build something. Here are a few ideas:

  • A calendar that focus at today’s date but allows a user to select other dates. Maybe even add a “Today" button that returns the user to today’s date.
  • You can try improving on the car example - have a list of cars that users can purchase. You might have to define this in the initial state, then the user can add extra features they want with a charge. These features can be predefined, or defined by the user.

The post Getting to Know the useReducer React Hook appeared first on CSS-Tricks.

Spam Detection APIs

Css Tricks - Tue, 06/25/2019 - 4:06am

I was trying to research the landscape of these the other day — And by research, I mean light Googling and asking on Twitter. Weirdly, very little comes to mind when thinking about spam detection APIs. I mean some kind of URL endpoint, paid or not, where you can hit it with a block of text and whatever metadata it wants and it'll tell you if it's spam or not. Seems like something an absolute buttload of the internet could use and something companies of any size could monetize or offer free to show off their smart computer machines.

Akismet is the big kid on the block.

You might think of Akismet as a WordPress thing, and it is. It's an Automattic product and is perhaps primarily used as a WordPress plugin. I run that here on CSS-Tricks and it's blocked 1,989,326 so far.

It also has a generic API. There are libraries for Dart, JavaScript, PHP, Python, Ruby, Go, etc, as well as plugins for other CMSs. So if you use a different CMS or have your custom app, you can still use Akismet for spam detection.

After you get an API key, you can POST to a URL endpoint with all the data it needs and it'll respond true if it's spam or false if it's not.

To get better results over time, you can also submit content telling it if it's spam or ham (ham is the opposite of spam... good content).

Plino

Several folks mentioned Plino to me.

There is a lot to like here, like the fact that it's free and returns a JSON response like you might be used to in development. There is the fancy buzzword "Machine Learning" being used here, too. It makes me think that with lots of people using this, it'll get smarter and smarter as it goes. But there is no way to submit ham/spam, so I'm not sure that's really the case.

There is other stuff that makes me nervous. It's clearly on Heroku which is kinda expensive at scale, and so with no pricing model it seems like it could go away anytime. Sorta feels like a fun-but-abandoned side project. Last commit was two years ago, as I write.

OOPSpam

OOPSpam looks super similar to Plino, but has a pricing model, which is nice. They publish their latency, which is over two seconds. I haven't compared that to the others so I have no idea if they are all that slow. Two seconds seems like a lot for an API call to me, but maybe it's not that big of a deal since it's an async submit?

CleanTalk

CleanTalk has a clear pricing structure and appears to have plenty of customers, which is a plus to me. The website looks a little janky though, which makes me worry a little.

(Sorry if that's a little rude, but it's just mental math to me. Good design is one of the least expensive investments a company can make to increase trust, so companies that overlook it make me wonder.)

It looks like they have a variety of anti-spam solutions though, which is interesting. For example, you can ask an API to see if an IP, email, or domain is on a blacklist, which is a pretty raw way of blocking bad stuff, but useful for stuff like protecting against spam registrations (rather than just checking blocks of text). They also have a firewall solution, which is interesting for folks trying to block spam before it even touches their servers.

Email options...

There are a couple out there that seem rather specific to testing emails. As in, testing your own emails before you send them to make sure they aren't considered spam by email services. Here are a couple I cam across while looking around:

The post Spam Detection APIs appeared first on CSS-Tricks.

Spam Detection APIs

Css Tricks - Tue, 06/25/2019 - 4:06am

I was trying to research the landscape of these the other day — And by research, I mean light Googling and asking on Twitter. Weirdly, very little comes to mind when thinking about spam detection APIs. I mean some kind of URL endpoint, paid or not, where you can hit it with a block of text and whatever metadata it wants and it'll tell you if it's spam or not. Seems like something an absolute buttload of the internet could use and something companies of any size could monetize or offer free to show off their smart computer machines.

Akismet is the big kid on the block.

You might think of Akismet as a WordPress thing, and it is. It's an Automattic product and is perhaps primarily used as a WordPress plugin. I run that here on CSS-Tricks and it's blocked 1,989,326 so far.

It also has a generic API. There are libraries for Dart, JavaScript, PHP, Python, Ruby, Go, etc, as well as plugins for other CMSs. So if you use a different CMS or have your custom app, you can still use Akismet for spam detection.

After you get an API key, you can POST to a URL endpoint with all the data it needs and it'll respond true if it's spam or false if it's not.

To get better results over time, you can also submit content telling it if it's spam or ham (ham is the opposite of spam... good content).

Plino

Several folks mentioned Plino to me.

There is a lot to like here, like the fact that it's free and returns a JSON response like you might be used to in development. There is the fancy buzzword "Machine Learning" being used here, too. It makes me think that with lots of people using this, it'll get smarter and smarter as it goes. But there is no way to submit ham/spam, so I'm not sure that's really the case.

There is other stuff that makes me nervous. It's clearly on Heroku which is kinda expensive at scale, and so with no pricing model it seems like it could go away anytime. Sorta feels like a fun-but-abandoned side project. Last commit was two years ago, as I write.

OOPSpam

OOPSpam looks super similar to Plino, but has a pricing model, which is nice. They publish their latency, which is over two seconds. I haven't compared that to the others so I have no idea if they are all that slow. Two seconds seems like a lot for an API call to me, but maybe it's not that big of a deal since it's an async submit?

CleanTalk

CleanTalk has a clear pricing structure and appears to have plenty of customers, which is a plus to me. The website looks a little janky though, which makes me worry a little.

(Sorry if that's a little rude, but it's just mental math to me. Good design is one of the least expensive investments a company can make to increase trust, so companies that overlook it make me wonder.)

It looks like they have a variety of anti-spam solutions though, which is interesting. For example, you can ask an API to see if an IP, email, or domain is on a blacklist, which is a pretty raw way of blocking bad stuff, but useful for stuff like protecting against spam registrations (rather than just checking blocks of text). They also have a firewall solution, which is interesting for folks trying to block spam before it even touches their servers.

Email options...

There are a couple out there that seem rather specific to testing emails. As in, testing your own emails before you send them to make sure they aren't considered spam by email services. Here are a couple I cam across while looking around:

The post Spam Detection APIs appeared first on CSS-Tricks.

Why I don’t use web components

Css Tricks - Tue, 06/25/2019 - 4:06am

Here’s an interesting post by Rich Harris where he’s made a list of some of the problems he’s experienced in the past with web components and why he doesn’t use them today:

Given finite resources, time spent on one task means time not spent on another task. Considerable energy has been expended on web components despite a largely indifferent developer population. What could the web have achieved if that energy had been spent elsewhere?

The most convincing part of Rich’s argument for me is where he writes about progressive enhancement and the dependence on polyfills for using web components today. And I’m sure that a lot of folks disagree with many of Rich’s points here, and there’s an awful amount of snark in the comments beneath his post, but it’s certainly an interesting conversation worth digging into. For an opposing perspective, go read the very last paragraph in the last installment of our Web Components Guide, where author Caleb Williams suggests that there's no need to wait to use web components in projects:

These standards are ready to adopt into our projects today with the appropriate polyfills for legacy browsers and Edge. And while they may not replace your framework of choice, they can be used alongside them to augment you and your organization’s workflows.

But all of this is a good reminder that hey: web components are a thing that we should be able to freely criticize and talk about without being jerks. And I think Rich does that pretty well.

Direct Link to ArticlePermalink

The post Why I don’t use web components appeared first on CSS-Tricks.

Why I don’t use web components

Css Tricks - Tue, 06/25/2019 - 4:06am

Here’s an interesting post by Rich Harris where he’s made a list of some of the problems he’s experienced in the past with web components and why he doesn’t use them today:

Given finite resources, time spent on one task means time not spent on another task. Considerable energy has been expended on web components despite a largely indifferent developer population. What could the web have achieved if that energy had been spent elsewhere?

The most convincing part of Rich’s argument for me is where he writes about progressive enhancement and the dependence on polyfills for using web components today. And I’m sure that a lot of folks disagree with many of Rich’s points here, and there’s an awful amount of snark in the comments beneath his post, but it’s certainly an interesting conversation worth digging into. For an opposing perspective, go read the very last paragraph in the last installment of our Web Components Guide, where author Caleb Williams suggests that there's no need to wait to use web components in projects:

These standards are ready to adopt into our projects today with the appropriate polyfills for legacy browsers and Edge. And while they may not replace your framework of choice, they can be used alongside them to augment you and your organization’s workflows.

But all of this is a good reminder that hey: web components are a thing that we should be able to freely criticize and talk about without being jerks. And I think Rich does that pretty well.

Direct Link to ArticlePermalink

The post Why I don’t use web components appeared first on CSS-Tricks.

Linkbait 43

QuirksBlog - Tue, 06/25/2019 - 3:39am

Back from hiatus. I’m starting up serious planning and writing of “CSS for JavaScripters” so this is a CSS-heavy linkbait, mostly filled with reminders to myself.

  • Every-layout.dev is easily the most important CSS resource to be unveiled in recent months. (In fact, it was unveiled at CSS Day, which was a nice touch.) Serious, in-depth, algorithmic discussion of several popular CSS layouts and how to construct them with care.
  • The 2019 State of CSS survey results. Contains several interesting gems. The figure that really surprised me is that 85% of the respondents is male. I thought CSS had a slightly higher ratio of women. Then again, maybe it’s the marketing of the survey that caused the disparity. (I never heard of it until I saw the results.) Or my gender guesstimate is just wrong.
  • The CSS mindset:

    [...] the declarative nature of CSS makes it particularly difficult to grasp, especially if you think about it in terms of a “traditional” programming language.

    Other programming languages often work in controlled environments, like servers. They expect certain conditions to be true at all times [...]

    CSS on the other hand works in a place that can never be fully controlled, so it has to be flexible by default. It’s less about “programming the appearance” and more about translating a design into a set of rules that communicate the intent behind it. Leave enough room, and the browser will do the heavy lifting for you.

    Interesting turn of phrase that echoes my own thoughts on the subject. Expect to find this in The Book.
  • Excellent overview of render blocking in CSS and how to avoid it. It’s simple, really, but there will be countless CSS programmers who need this sort of tutorials. Will also go in The Book.
  • An older article, but Harry Roberts’s Cyclomatic Complexity: Logic in CSS remains one cornerstone of CSS understanding, and teaching. This one is mostly meant to remind myself of its existence; you probably already read it.
  • Facebook lost 20% of its usage (likes, shares, and such) since the Cambridge Analytica scandal broke.
    This sounds great in theory, but what if the people who are harder to dupe are the ones who stop using Facebook, while the more gullible people remain? The average Facebook user would become more stupid, and Facebook would become even better at influencing its users.
  • Also, Facebook should be regulated. Not as a media company, but as a drug.
  • We all know third-party scripts are among the worst offenders when it comes to website performance. But how bad is it actually, and who are the worst culprits? Third-party Web provides useful answers and treeviews.
  • Excellent overview of WebViews, their purpose, their tricky bits, and their diversity. Required reading for all five people who care about the browser market.
  • [In Dutch] List of websites closed on Sundays. In an ultimate meta-move this list is only available on Sundays.
    (And why are these websites closed? Because their proprietors subscribe to the strict Dutch Reformed view that Sundays are not for media enjoyment. See this article about the SGP political party for more background information.)
  • You shouldn’t do thing with tool, you should do other thing
  • Have a tip for the next Linkbait? Or a comment on this one? Let me know (or here or here).

Render Snarky Comments in Comic Sans

Css Tricks - Mon, 06/24/2019 - 12:29pm

Hilarious idea by Zach Leatherman. To test if a comment is "snarky" or not, there is an npm package up to the task.

On this site, we generally just delete snarky comments, but I still run a WordPress plugin that allows me to "feature" or "bury" comments. It's old but it still works fine in the latest WordPress. We use the bury functionality for comments that aren't rude, snarky, or spam, but also don't add much to the post. We try to downplay those so they aren't wasting people's time scrolling through the threads.

I dig the Comic Sans idea, so I'm gonna give it a shot. &#x1f609;

Direct Link to ArticlePermalink

The post Render Snarky Comments in Comic Sans appeared first on CSS-Tricks.

Building a Conference Schedule with CSS Grid

Css Tricks - Mon, 06/24/2019 - 4:21am

It’s hard to beat the feeling of finding a perfect use for a new technology. You can read every handy primer under the sun and ooh-and-ahh at flashy demos, but the first time you use it on your own project… that’s when things really click.

I gained a new appreciation for CSS Grid when building a flexible layout for a conference schedule. The needs of the project aligned perfectly with grid’s strengths: a two-dimensional (vertical and horizontal) layout with complex placement of child elements. In the process of building a proof of concept, I found a few techniques that made the code highly readable and outright fun to work with.

The resulting demo included some interesting uses of CSS Grid features and forced me to grapple with some details of grid you don’t run into in every day.

Before we get started, it might be a good idea to keep another tab open with the CSS-Tricks guide to CSS Grid to reference the concepts we cover throughout the post.

Defining our layout requirements

I set out to create the following layout thanks to WordCamp, the hundreds of WordPress-focused conferences that happen around the world each year. These varied events range in size and format, yet all use the same schedule layout tool.

The final layout we’ll build.

I helped schedule a couple WordCamps and styled a WordCamp website, so I knew the shortcomings of the existing HTML table layout. If your schedule didn’t fit in a uniform grid, well…¯\_(?)_/¯

Setting out to find a better way, I started by listing the layout requirements:

  • Sessions of variable length (limited to set time increments)
    Imagine back-to-back one-hour talks in three rooms alongside a two-hour workshop in another.
  • Sessions spanning one or more "Tracks"
    Tracks are usually associated with a specific room in the venue. In the case of my local WordCamp in Seattle, the venue can literally remove a wall to combine two rooms!
  • Schedule can include empty space
    A last-minute cancellation or extra-short session creates gaps in a schedule.
  • Design is easy to customize with CSS
    WordCamp websites allow theming only via CSS.
  • Layout can be automatically generated from CMS content
    Since we’re building a layout from structured session data on thousands of websites, we can’t rely on any HTML or CSS that’s too clever or bespoke.
Getting started: Solid HTML

Before I write any CSS, I always start with rock-solid HTML.

The top-level <div> will have a class of .schedule and serve as the grid parent. Each unique start time gets its own heading followed by all sessions starting at that time. The markup for each session isn’t very important, but make sure that seeing the layout isn’t required to understand when and where a session happens. (You’ll see why in a moment.)

<h2>Conference Schedule</h2> <div class="schedule"> <h3 class="time-slot">8:00am</h3> <div class="session session-1 track-1"> <h4 class="session-title"><a href="#">Session Title</a></h4> <span class="session-time">8:00am - 9:00am</span> <span class="session-track">Track 1</span> <span class="session-presenter">Presenter Name</span> </div> <!-- Sessions 2, 3, 4 --> <h3 class="time-slot">9:00am</h3> <div class="session session-5 track-1"> <h4 class="session-title"><a href="#">Session Title</a></h4> <span class="session-time">9:00am - 10:00am</span> <span class="session-track">Track 1</span> <span class="session-presenter">Presenter Name</span> </div> <!-- Sessions 6, 7, 8 --> <!-- etc... --> </div> <!-- end .schedule --> Mobile layout and grid fallback complete!

Adding in a bit of up-to-you CSS to make things pretty, our mobile layout and fallback for browsers that don’t support CSS Grid is already complete!

Here’s how mine looks with the colors I used:

People on phones, zooming in their browsers, or even using Internet Explorer will have no problem finding their favorite sessions at this conference! Adding the grid layout

Now for the actual CSS Grid part!

My ah-ha moment when building this came from reading Robin’s article here on CSS-Tricks, "Making a Bar Chart with CSS Grid." TL;DR: One grid row represents 1% of the chart's height, so a bar spans the same number of rows as the percentage it represents.

.chart { display: grid; grid-template-rows: repeat(100, 1fr); /* 1 row = 1%! */ } .fifty-percent-bar { grid-row: 51 / 101; /* 101 - 51 = 50 => 50% */ }

That helped me realize that grid is perfect for any layout tied to some regular incremental unit. In the case of a schedule, that unit is time! For this demo, we’ll use increments of 30 minutes, but do whatever makes sense for you. (Just watch out for the Chrome bug that limits Grid layouts to 1000 rows.)

The first version I tried used similar syntax to the bar chart in Robin’s and some basic math to place the sessions. We’re using eight rows because there are eight 30-minute increments between 8 a.m. and 12 p.m. Remember that implicit grid line numbers start at one (not zero), so the grid rows are numbered one through nine.

A rudimentary version of a single column schedule with numbered grid lines. (View Demo: Numbered Gridlines vs. Named Gridlines) .schedule { display: grid; grid-template-rows: repeat(8, 1fr); } .session-1 { grid-row: 1 / 3; /* 8am - 9am, 3 - 1 = 2 30-minute increment */ } .session-2 { grid-row: 3 / 6; /* 9am - 10:30am, 6-3 = 3 30-minute increments */ }

The problem with this technique is that placing items on a grid with a lot of rows is very abstract and confusing. (This issue added a ton of complexity to Robin’s bar chart demo, too.)

This is where named grid lines come to the rescue! Instead of relying on grid line numbers, we can give each line a predictable name based on the corresponding time of day.

By using named grid lines for the schedule, it becomes much easier to place a session. (View Demo: Numbered Gridlines vs. Named Gridlines) .schedule { display: grid; grid-template-rows: [time-0800] 1fr [time-0830] 1fr [time-0900] 1fr [time-0930] 1fr; /* etc... Note: Use 24-hour time for line names */ } .session-1 { grid-row: time-0800 / time-0900; } .session-2 { grid-row: time-0900 / time-1030; }

That is gloriously easy to understand. There is no complicated math to figure out how many rows there are before and after a session starts or ends. Even better, we can generate grid line names and session layout styles with information stored in WordPress. Throw a start and end time at the grid, and you’re good to go!

Since the schedule has multiple tracks, we’ll need a column for each one. The tracks work in a similar way to the times, using named track lines for each grid column line. There’s also an extra first column for the start time headings.

.schedule { /* continued */ grid-template-columns: [times] 4em [track-1-start] 1fr [track-1-end track-2-start] 1fr [track-2-end track-3-start] 1fr [track-3-end track-4-start] 1fr [track-4-end]; }

Here though, we take named grid lines one step further. Each line gets two names: one for the track it starts and one for the track it ends. This isn’t strictly necessary, but it makes the code much clearer, especially when a session spans more than one column.

With the time- and track-based grid lines defined, we can now place any session we want just from knowing it’s time and track!

.session-8 { grid-row: time-1030 / time-1100; grid-column: track-2-start / track-3-end; /* spanning two tracks! */ }

Putting that all together, we get some lengthy but extremely readable code that is a real joy to work with.

@media screen and (min-width: 700px) { .schedule { display: grid; grid-gap: 1em; grid-template-rows: [tracks] auto /* Foreshadowing! */ [time-0800] 1fr [time-0830] 1fr [time-0900] 1fr [time-0930] 1fr [time-1000] 1fr [time-1030] 1fr [time-1100] 1fr [time-1130] 1fr [time-1200] 1fr; grid-template-columns: [times] 4em [track-1-start] 1fr [track-1-end track-2-start] 1fr [track-2-end track-3-start] 1fr [track-3-end track-4-start] 1fr [track-4-end]; } .time-slot { grid-column: times; } } <div class="session session-1 track-1" style="grid-column: track-1; grid-row: time-0800 / time-0900;"> <!-- details --> </div> <div class="session session-2 track-2" style="grid-column: track-2; grid-row: time-0800 / time-0900"> <!-- details --> </div> <!-- etc... -->

The final code uses inline styles for session placement which feels right to me. If you don’t like this and are working with more modern browsers, you could pass the line names to CSS via CSS variables.

Quick note: using fr units versus the auto value for row heights

One detail worth noting is the use of the fr unit for defining row heights.

When using 1fr to determine row heights, all rows have the same height. That height is determined by the content of the tallest row in the schedule. (I had to read the W3C spec for fr to figure this out!) That produces a beautiful schedule where height is proportional to time, but can also lead to a very tall layout.

Using 1fr produces equal-height rows determined by the tallest row in the grid. (View Demo: Grid row heights 1fr vs auto)

For example, if your schedule grid has 15-minute increments from 7 a.m. to 6 p.m., that’s a total of 48 grid rows. In that case, you probably want to use auto for your row height because the schedule is much more compact with each grid row's height determined by its content.

Using auto makes each row the height of its content. (View Demo: Grid row heights 1fr vs auto) A word about accessibility

There are real concerns about the accessibility of certain CSS Grid techniques. Specifically, the ability to change the order of information visually in ways that don’t match the source order causes problems for people using keyboard navigation.

This layout uses that ability to arbitrarily place items on a grid, so some caution is warranted. However, because the heading and source order align with the visualization of start times, this seems like a safe use to me.

If you’re inspired to do something similar, carefully consider accessibility. It makes sense to order information by time in this case, but I can imagine a legitimate case for TAB order to go down columns rather than across rows. (Modifying this demo to do that shouldn’t be too hard!)

Whatever you do, always consider accessibility.

Adding sticky track names

Finally, it’s time to add in the track names that look like table headers at the top of each column. Since a session’s track is already visible, I chose to hide the "headers" from assistive technology with aria-hidden="true".

The track names go in the first grid row, conveniently named "tracks." As long as you don’t have any weird overflow issues, position: sticky keeps those in view while you scroll.

<span class="track-slot" aria-hidden="true" style="grid-column: track-1;">Track 1</span> <span class="track-slot" aria-hidden="true" style="grid-column: track-2;">Track 2</span> <span class="track-slot" aria-hidden="true" style="grid-column: track-3;">Track 3</span> <span class="track-slot" aria-hidden="true" style="grid-column: track-4;">Track 4</span> .track-slot { display: none; /* only visible with Grid layout */ } @supports( display:grid ) { @media screen and (min-width:700px) { .track-slot { grid-row: tracks; display: block; position: sticky; top: 0; z-index: 1000; background-color: rgba(255,255,255,.9); } } }

It’s a slick little finishing touch to the final demo. ✨

The result

Here’s how things look with everything we’ve covered put together!

See the Pen
Conference Schedule with CSS Grid
by Mark Root-Wiley (@mrwweb)
on CodePen.

We’re just getting started

This schedule is definitely the most satisfying use of CSS Grid I’ve ever made. I love how "data-driven" and semantic the line naming is, and the accessibility and CMS requirements fit in perfectly without any inconvenience.

The only question left for me is what other types of "data-driven" grids are out there to build? I’ve seen some great calendar layouts and here’s a Monopoly board layout. What about a football field, a timeline, restaurant tables, or theater seats? What else?

The post Building a Conference Schedule with CSS Grid appeared first on CSS-Tricks.

Reduced Motion Picture Technique, Take Two

Css Tricks - Fri, 06/21/2019 - 5:11am

Did you see that neat technique for using the <picture> element with <source media=""> to serve an animated image (or not) based on a prefers-reduced-motion media query?

After we shared that in our newsletter, we got an interesting reply from Michael Gale:

What about folks who love their animated GIFs, but just didn’t want the UI to be zooming all over the place? Are they now forced to make a choice between content and UI?

I thought that was a pretty interesting question.

Also, whenever I see <img src="gif.gif"> these days, my brain is triggered into WELL WHAT ABOUT MP4?! territory, as I've been properly convinced that videos are better-in-all-ways on the web than GIFs. Turns out, some browsers support videos right within the <img> element and, believe it or not, you can write fallbacks for that, with — drumroll, please — for the <picture> element as well!

Let's take a crack at combining all this stuff.

Adding an MP4 source

The easy one is adding an additional <source> with the video. That means we'll need three source media files:

  1. A fallback non-animated graphic when prefers-reduced-motion is reduce.
  2. An animated GIF as the default.
  3. An MP4 video to replace the GIF, if the fallback is supported.

For example:

<picture> <source srcset="static.png" media="(prefers-reduced-motion: reduce)"></source> <source srcset="animated.mp4" type="video/mp4"> <img srcset="animated.gif" alt="animated image" /> </picture>

Under default conditions in Chrome, only the GIF is downloaded and shown:

Under default conditions in Safari, only the MP4 is downloaded and shown:

If you've activated prefers-reduced-motion: reduce in either Chrome or Safari (on my Mac, I go to System Preferences → Accessibility → Display → Reduce Motion), both browsers only download the static PNG file.

I tested Firefox and it doesn't seem to work, instead continuing to download the GIF version. Firefox seems to support prefers-reduced-motion, but perhaps it's just not supported on <source> elements yet? I'm not sure what's up there.

Wouldn't it be kinda cool to provide a single animated source and have a tool generate the others from it? I bet you could wire that up with something like Cloudinary.

Adding a toggle to show the animated version

Like Michael Gale mentioned, it seems a pity that you're entirely locked out from seeing the animated version just because you've flipped on a reduced motion toggle.

It should be easy enough to have a <button> that would use JavaScript to yank out the media query and force the browser to show the animated version.

I'm fairly sure there is no practical way to do this declaratively in HTML. We also can't put this button within the <picture> tag. Even though <picture> isn't a replaced element, the browser still gets confused and doesn't like it. Instead, it doesn't render it at all. No big deal, we can use a wrapper.

<div class="picture-wrap"> <picture> <!-- sources --> </picture> <button class="animate-button">Animate</button> </div>

We can position the button on top of the image somewhere. This is just an arbitrary choice — you could put it wherever you want, or even have the entire image be tappable as long as you think you could explain that to users. Remember to only show the button when the same media query matches:

.picture-wrap .animate-button { display: none; } @media (prefers-reduced-motion: reduce) { .picture-wrap .animate-button { display: block; } }

When the button is clicked (or tapped), we need to remove the media query to start the animation by downloading an animated source.

let button = document.querySelector(".animate-button"); button.addEventListener("click", () => { const parent = button.closest(".picture-wrap"); const picture = parent.querySelector("picture"); picture.querySelector("source[media]").remove(); });

Here's that in action:

See the Pen
Prefers Reduction Motion Technique PLUS!
by Chris Coyier (@chriscoyier)
on CodePen.

Maybe this is a good component?

We could automatically include the button, the button styling, and the button functionality with a web component. Hey, why not?

See the Pen
Prefers Reduction Motion Technique as Web Component
by Chris Coyier (@chriscoyier)
on CodePen.

The post Reduced Motion Picture Technique, Take Two appeared first on CSS-Tricks.

Weekly Platform News: Mozilla’s AV1 Encoder, Samsung One UI CSS, DOM Matches Method

Css Tricks - Fri, 06/21/2019 - 5:10am

Šime posts regular content for web developers on webplatform.news.

In this week's weekly roundup, Vimeo and Mozilla partner up on a video encoding format, how to bind instructions to to form fields using aria labels, the DOM has a matching function, and Samsung is working on its own CSS library.

Vimeo partners with Mozilla to use their rav1e encoder

Vittorio Giovara: AV1 is a royalty-free video codec designed by the Alliance for Open Media and the the most anticipated successor of H.264. Vimeo is contributing to the development of Mozilla’s AV1 encoder.

In order for AV1 to succeed, there is a need of an encoder like x264, a free and open source encoder, written by the community, for the community, and available to everyone: rav1e. Vimeo believes in what Mozilla is doing.

Use aria-describedby to bind instructions to form fields

Raghavendra Satish Peri: If you provide additional instructions for a form field, use the aria-describedby attribute to bind the instruction to the field. Otherwise, assistive technology users who use the Tab key might miss this information.

<label for="dob">Date of Birth</label> <input type="text" aria-describedby="dob1" id="dob" /> <span id="dob1">Use DD/MM/YY</span> Samsung Internet announces One UI CSS

Diego González: Samsung is experimentally developing a CSS library based on its new One UI design language. The library is called One UI CSS and includes styles for common form controls such as buttons, menus, and sliders, as well as other assets (web fonts, SVG icons, polyfills).

Some of the controls present in One UI CSS. DOM elements have a matches method

Sam Thorogood: You can use the matches method to test if a DOM element has a specific CSS class, attribute or ID value. This method accepts a CSS selector and returns true if the element matches the given selector.

el.classList.has('foo') /* becomes */ el.matches('.foo'); el.hasAttribute('hello') /* becomes */ el.matches('[hello]'); el.id === 'bar' /* becomes */ el.matches('#bar');

The post Weekly Platform News: Mozilla’s AV1 Encoder, Samsung One UI CSS, DOM Matches Method appeared first on CSS-Tricks.

Hello Subgrid!

Css Tricks - Thu, 06/20/2019 - 10:48am

Rachel Andrew’s talk at CSSconf is wonderful because it digs into one of the most exciting changes that’s coming soon to a browser near you: subgrid! That’s a change to the CSS Grid spec that allows for much greater flexibility for our visual designs. Subgrid allows us to set one grid on an entire page and let child elements use that very same grid tracks.

The reason why I’m very excited is because this solves one of the most annoying visual layout issues that I’ve come across since becoming a web developer, and if that sounds bonkers and/or wonderful to you, then make sure to check out Rachel’s talk because she does a much better job of describing this than I possibly could:

Direct Link to ArticlePermalink

The post Hello Subgrid! appeared first on CSS-Tricks.

Managing WordPress Metadata in Gutenberg Using a Sidebar Plugin

Css Tricks - Thu, 06/20/2019 - 5:18am

WordPress released their anticipated over to the post editor, nicknamed Gutenberg, which is also referred to as the block editor. It transforms a WordPress post into a collection of blocks that you can add, edit, remove and re-order in the layout. Before the official release, Gutenberg was available as a plugin and, during that time, I was interested in learning how to create custom blocks for the editor. I was able to learn a lot about Gutenberg that I decided to put together a course that discusses almost everything you need to know to develop blocks for Gutenberg.

In this article, we will discuss metaboxes and metafields in WordPress. Specifically, we’ll cover how to replace the old PHP metaboxes in Gutenberg and extend Gutenberg’s sidebar to add a React component that will be used to manipulate the metadata using the global JavaScript Redux-like stores. Note that metadata in Gutenberg can also be manipulated using blocks. And both ways are discussed in my course, however, in this article I am going to focus on managing metadata in the sidebar since I believe this method will be used more often.

This article assumes some familiarity with ReactJS and Redux. Gutenberg relies heavily on these technologies to render UI and manage state. You can also check out the CSS-Tricks guide to learning Gutenberg for an intro to some of the concepts we’ll cover here.

The block editor interface Gutenberg is a React application

At its core, Gutenberg is a ReactJS application. Everything you see in the editor is rendered using a React component. The post title, the content area that contains the blocks, the toolbar at the top and the right sidebar are all React components. Data or application states in this React application are stored in centralized JavaScript objects, or "stores." These stores are managed by WordPress’ data module. This module shares a lot of core principles with Redux. So, concepts like stores, reducers, actions, action creators, etc., also exist in this module. I will sometimes refer to these stores as "Redux-like" stores.

These stores do not only store any data about the current post, like the post content (the blocks), the post title, and the selected categories, but it also stores global information about a WordPress website, like all the categories, tags, posts, attachments and so on. In addition to that, UI state information like,"is the sidebar opened or closed?" are also stored in these global stores. One of the jobs of the "data module" is to retrieve data from these stores and also change data in the stores. Since these stores are global and can be used by multiple React components changing data in any store will be reflected in any Gutenberg UI part (including blocks) that uses this piece of data.

Once a post is saved, the WordPress REST API will be used to update the post using the data stored in these global stores. So the post title, the content, categories etc., that are stored in these global stores will be sent as payload in the WP REST API endpoint that updates the post. And thus if we are able to manipulate data in these stores, once the user clicks save, the data that we manipulated will be stored in the database by the API without us having to do anything.

One of the things that is not managed by these global stores in Gutenberg is metadata. If you have some metafields that you used to manage using a metabox in the pre-Gutenberg "classic" editor, those will not be stored and manipulated using the global Redux-like stores by default. However, we can opt-in to manage metadata using JavaScript and the Redux-like stores. Although those old PHP metaboxes will still appear in Gutenberg, WordPress recommends porting these PHP metaboxes to another approach that uses the global stores and React components. And this will ensure a more unified and consistent experience. You can read more about problems that could occur by using PHP metaboxes in Gutenberg.

So before we start, let’s take a look at the Redux-like stores in Gutenberg and how to use them.

Retrieving and changing data in Gutenberg’s Redux-like stores

We now know that the Gutenberg page is managed using these Redux-like stores. We have some default "core" stores that are defined by WordPress. Additionally, we can also define our own stores if we have some data that we would like to share between multiple blocks or even between blocks and other UI elements in the Gutenberg page, like the sidebar. Creating your own stores is also discussed in my course and you can read about it in the official docs. However, in this article we will focus on how to use the existing stores. Using the existing stores lets us manipulate metadata; therefore we will not need to create any custom stores.

In order to access these stores, make sure you have the latest WordPress version with Gutenberg active and edit any post or page. Then, open your browser console an type the following statement:

wp.data.select('core/editor').getBlocks()

You should get something like this:

Let’s break this down. First, we access the wp.data module which (as we discussed) is responsible for managing the Redux-like stores. This module will be available inside the global wp variable if you have Gutenberg in your WordPress installation. Then, inside this module, we call a function called select. This function receives a store name as an argument and returns all the selectors for this store. A selector is a term used by the data module and it simply means a function that gets some data from the store. So, in our example, we accessed the core/editor store, and this will return a bunch of functions that can be used to get data from this store. One of these functions is getBlocks() which we called above. This function will return an array of objects where each object represents a block in your current post. So depending on how many blocks you have in your post, this array will change.

As we’ve seen, we accessed a store called core/editor. This store contains information about the current post that you are editing. We’ve also seen how to get the blocks in the current post but we can also get a lot of other stuff. We can get the title of the current post, the current post ID, the current post post type and pretty much everything else we might need.

But in the example above, we were only able to retrieve data. What if we want to change data? Let’s take a look at another selector in the ‘core/editor’ store. Let’s run this selector in our browser console:

wp.data.select('core/editor').getEditedPostAttribute('title')

This should return the title of the post currently being edited:

Great! Now what if we want to change the title using the data module? Instead of calling select(), we can call dispatch() which will also receive a store name and return some actions that you can dispatch. If you are familiar with Redux, terms like "actions" and "dispatch" will sound familiar to you. If this sounds new to you, all you need to know is that dispatching a certain action simply means changing some data in a store. In our case, we want to change the post title in the store, so we can call this function:

wp.data.dispatch('core/editor').editPost({title: 'My new title'})

Now take a look at the post title in the editor — it will be changed accordingly!

That’s how we can manipulate any piece of data in the Gutenberg interface. Wan retrieve the data using selectors and change that data using actions. Any change will be reflected in any part of the UI that uses this data.

There are, of course, other stores in Gutenberg that you can checkout on this page. So, let’s take a quick look at a couple of more stores before we move on.

The stores that you will use the most are the core/editor which we just looked at, and the core store. Unlike core/editor, the core store contains information, not only about the currently edited post, but also about the whole WordPress website in general. So, for instance, we can get all the authors on the website using:

wp.data.select('core').getAuthors()

We can also get some posts from the website like so:

wp.data.select('core').getEntityRecords('postType','post',{per_page: 5})

Make sure to run this twice if the first result was null. Some selectors like this one will send an API call first to get your post. That means the the returned value will initially be null until the API request is fulfilled:

Let’s look at one more store: edit-post. This store is responsible for the UI information in the actual editor. For example, we can have selectors that check if the sidebar is currently open:

wp.data.select('core/edit-post').isEditorSidebarOpened()

This will return true if the sidebar is opened. But try closing the sidebar, run this function again, and it should return false.

We can also open and close the sidebar by dispatching actions in this store. Having the sidebar open and running this action in the browser console, the sidebar should be closed:

wp.data.dispatch('core/edit-post').closeGeneralSidebar()

You will unlikely need to use this store, but it’s good to know that this is what Gutenberg does when you click on the sidebar icon to close it.

There are some more stores that you might need to take a look at. The core/notices store, for instance, could be useful. This can help you display error, warning and success messages in the Gutenberg page. You can also check all the other stores here.

Try to play around with these stores in your browser until you feel comfortable using them. After that, we can see how to use them in real code outside the browser.

Let’s setup a WordPress plugin to add a Gutenberg sidebar

Now that we know how to use the Redux-like stores in Gutenberg, the next step is to add a React sidebar component in the editor. This React component will be connected to the core/editor store and it will have some input that, when changed, will dispatch some action that will manipulate metadata — like the way we manipulated the post title earlier. But to do that, we need to create a WordPress plugin that holds our code.

You can follow along by cloning or downloading the repository for this example on GitHub.

Let’s create a new folder inside wp-content/plugins directory of the WordPress installation. I am going to call it gutenberg-sidebar. Inside this folder, let’s create the entry point for our plugin. The entry point is the PHP file that will be run when activating your plugin. It can be called index.php or plugin.php. We’re going to use plugin.php for this example and put some information about the plugin at the top as well as add some code that avoids direct access:

<?php /** * Plugin Name: gutenberg-sidebar * Plugin URI: https://alialaa.com/ * Description: Sidebar for the block editor. * Author: Ali Alaa * Author URI: https://alialaa.com/ */ if( ! defined( 'ABSPATH') ) { exit; }

You should find your plugin on the Plugins screen in the WordPress admin. Click on "Activate" in order for the code to run.

As you might imagine, we will write a lot of JavaScript and React from this point, forward. And in order to code React components easily we will need to use JSX. And JSX is not valid JavaScript that can run in your browser, it needs to be converted into plain JavaScript. We might also need to use ESNext features and import statements for importing and exporting modules.

And these features will not work on all browsers, so it’s better to transform our code into old ES5 JavaScript. Thankfully, there are a lot of tools that can help us achieve that. A famous one is webpack. webpack, however, is a big topic in itself and it won’t fit the scope of this article. Therefore, we are going to use another tool that WordPress provides which is @wordpress/scripts. By installing this package, we will get a recommended webpack configuration without having to do anything in webpack ourselves. Personally, I recommend that you learn webpack and try to do the configuration yourself. This will help you understand what’s going on and give you more control. You can find a lot of resources online and it’s also discussed in my course. But for now, let’s install the WordPress webpack configuration tool.

Change to your plugin folder in Terminal:

cd path/to/your/theme/folder

Next, we need to initialize npm in that folder in order to install @wordpress/scripts. This can be done by running this command:

npm init

This command will ask you some questions like the package name, version, license, etc. You can keep hitting Enter and leave the default values. You should have a package.json file in your folder and we can start installing npm packages. Let’s install @wordpress/scripts by running the following command:

npm install @wordpress/scripts --save-dev

This package will expose a CLI called wp-scripts which you can use in your npm scripts. There are different commands that you can run. We will focus on the build and start commands for now. The <code>build script will transform your files so that they are minified and ready for production. Your source code’s entry point is configured in src/index.js and the transformed output will be at build/index.js. Similarly, the start script will transform your code in src/index.js to build/index.js, however, this time, the code will not be minified to save time and memory — the command will also watch for changes in your files and re-build your files every time something is changed. The start command is suitable to be used for development while the build command is for production. To use these commands, we will replace the scripts key in the package.json file which will look something like this if you used the default options when we initialized npm.

Change this:

"scripts": { "test": "echo "Error: no test specified" && exit 1" },

...to this:

"scripts": { "start": "wp-scripts start", "build": "wp-scripts build" },

Now we can run npm start and npm run build to start development or build files, respectively.

Let’s create a new folder in the plugin’s root called src and add an index.js file in it. We can see it things are working by sprinkling in a little JavaScript. We’ll try an alert.

Now run npm start in Terminal. You should find the build folder created with the compiled index.js and also sourcemap files. In addition to that, you will notice that the build/index.js file is not minified and webpack will be watching for changes. Try changing the src/index.js file and save again. The build/index.js file will re-generated:

If you stop the watch (Ctrl + C ) in Terminal and run npm run build, the build/index.js file should now be minified.

Now that we have our JavaScript bundle, we need to enqueue this file in the Gutenberg editor. To do that we can use the hoo enqueue_block_editor_assets which will insure that the files are enqueued only in the Gutenberg page and not in other wp-admin pages where it isn’t needed.

We can enqueue our file like so in plugin.php:

// Note that it’s a best practice to prefix function names (e.g. myprefix) function myprefix_enqueue_assets() { wp_enqueue_script( 'myprefix-gutenberg-sidebar', plugins_url( 'build/index.js', __FILE__ ) ); } add_action( 'enqueue_block_editor_assets', 'myprefix_enqueue_assets' );

Visit the Gutenberg page. If all is well, you should get an alert, thanks to what we added to src/index.js earlier.

Fantastic! We’re ready to write some JavaScript code, so let’s get started!

Importing WordPress JavaScript packages

In order to add some content to the existing Gutenberg sidebar or create a new blank sidebar, we need to register a Gutenberg JavaScript plugin — and in order to do that, we need to use some functions and components from packages provided by WordPress: wp-plugins, wp-edit-post and wp-i18n. These packages will be available in the wp global variable in the browser as wp.plugins, wp.editPost and wp.i18n.

We can import the functions that we need into src/index.js. Specifically, those functions are: registerPlugin and PluginSidebar.

const { registerPlugin } = wp.plugins; const { PluginSidebar } = wp.editPost; const { __ } = wp.i18n;

It’s worth noting that we need to make sure that we have these files as dependencies when we enqueue our JavaScript file in order to make sure that our index.js file will be loaded after the wp-plugins, wp-edit-posts and wp-i18n packages. Let’s add those to plugin.php:

function myprefix_enqueue_assets() { wp_enqueue_script( 'myprefix-gutenberg-sidebar', plugins_url( 'build/index.js', __FILE__ ), array( 'wp-plugins', 'wp-edit-post', 'wp-i18n', 'wp-element' ) ); } add_action( 'enqueue_block_editor_assets', 'myprefix_enqueue_assets' );

Notice that I added wp-element in there as a dependency. I did that because we will write some React components using JSX. Typically, we’d import the entire React library when making React components. However, wp-element is an abstraction layer atop React so we never have to install or import React directly. Instead, we use wp-element as a global variable.

These packages are also available as npm packages. Instead of importing functions from the global wp variable (which will only be available in the browser that your code editor knows nothing about), we can simply install these packages using npm and import them in our file. These WordPress packages are usually prefixed with @wordpress.

Let’s install the two packages that we need:

npm install @wordpress/edit-post @wordpress/plugins @wordpress/i18n --save

Now we can import our packages in index.js:

import { registerPlugin } from "@wordpress/plugins"; import { PluginSidebar } from "@wordpress/edit-post"; import { __ } from "@wordpress/i18n";

The advantage of importing the packages this way is that your text editor knows what @wordpress/edit-post and @wordpress/plugins are and it can autocomplete functions and components for you — unlike importing from wp.plugins and wp.editPost which will only be available in the browser while the text editor has no clue what wp is.

Your text editor can autocomplete component names for you.

You might also think that importing these packages in your bundle will increase your bundle size, but there’s no worries there. The webpack config file that comes with @wordpress/scripts is instructed to skip bundling these @wordpress packages and depend of the wp global variable instead. As a result, the final bundle will not actually contain the various packages, but reference them via the wp variable.

Great! so I am going to stick to importing packages using npm in this article, but you are totally welcome to import from the global wp variable if you prefer. Let’s now use the functions that we imported!

Registering a Gutenberg Plugin

In order to add a new custom sidebar in Gutenberg, we first need to register a plugin — and that’s what the registerPlugin function that we imported will do. As a first argument, registerPlugin will receive a unique slug for this plugin. We can have an array of options as a second argument. Among these options, we can have an icon name (from the dashicons library) and a render function. This render function can return some components from the wp-edit-post package. In our case. we imported the PluginSidebar component from wp-edit-post and created a sidebar in the Gutenberg editor by returning this component in the render function. I also added PluginSidebar inside a React fragment since we can add other components in the render function as well. Also, the __ function imported from wp-i18n will be used so we can translate any string that we output:

registerPlugin( 'myprefix-sidebar', { icon: 'smiley', render: () => { return ( <> <PluginSidebar title={__('Meta Options', 'textdomain')} > Some Content </PluginSidebar> </> ) } })

You should now have a new icon beside the cog icon in the Gutenberg editor screen. This smiley icon will toggle our new sidebar which will have whatever content we have inside the PluginSidebar component:

If you were to click on that star icon beside the sidebar title, the sidebar smiley icon will be removed from the top toolbar. Therefore, we need to add another way to access our sidebar in case the user un-stars it from the top toolbar, and to do that, we can import a new component from wp-edit-post called PluginSidebarMoreMenuItem. So, let’s modify out import statement:

import { PluginSidebar, PluginSidebarMoreMenuItem } from "@wordpress/edit-post";

The PluginSidebarMoreMenuItem will allow us to add an item in the Gutenberg menu that you can toggle using the three dots icon at the top-right of the page. We want to modify our plugin to include this component. We need to give PluginSidebar a name prop and give PluginSidebarMoreMenuItem a target prop with the same value:

registerPlugin( 'myprefix-sidebar', { icon: 'smiley', render: () => { return ( <> <PluginSidebarMoreMenuItem target="myprefix-sidebar" > {__('Meta Options', 'textdomain')} </PluginSidebarMoreMenuItem> <PluginSidebar name="myprefix-sidebar" title={__('Meta Options', 'textdomain')} > Some Content </PluginSidebar> </> ) } })

In the menu now, we will have a "Meta Options" item with our smiley icon. This new item should toggle our custom sidebar since they are linked using the name and the target props:

Great! Now we have a new space in our Gutenberg page. We can replace the "some content" text in PluginSidebar and add some React components of our own!

Also, let’s make sure to check the edit-post package documentation. This package contains a lot of other components that you can add in your plugin. These components can allow you to extend the existing default sidebar and add your own components in it. Also, we can find components that allow us to add items in the Gutenberg top-right menu and also for the blocks menu.

Handling metadata in the classic editor

Let’s take a quick look at how we used to manage metadata in the classic editor using metaboxes. First, install and activate the classic editor plugin in order to switch back to the classic editor. Then, add some code that will add a metabox in the editor page. This metabox will manage a custom field that we’ll call _myprefix_text_metafield. This metafield will just be a text field that accepts HTML markup. You can add this code in plugin.php or put it in a separate file and include it plugin.php:

<?php function myprefix_add_meta_box() { add_meta_box( 'myprefix_post_options_metabox', 'Post Options', 'myprefix_post_options_metabox_html', 'post', 'normal', 'default' ); } add_action( 'add_meta_boxes', 'myprefix_add_meta_box' ); function myprefix_post_options_metabox_html($post) { $field_value = get_post_meta($post->ID, '_myprefix_text_metafield', true); wp_nonce_field( 'myprefix_update_post_metabox', 'myprefix_update_post_nonce' ); ?> <p> <label for="myprefix_text_metafield"><?php esc_html_e( 'Text Custom Field', 'textdomain' ); ?></label> <br /> <input class="widefat" type="text" name="myprefix_text_metafield" id="myprefix_text_metafield" value="<?php echo esc_attr( $field_value ); ?>" /> </p> <?php } function myprefix_save_post_metabox($post_id, $post) { $edit_cap = get_post_type_object( $post->post_type )->cap->edit_post; if( !current_user_can( $edit_cap, $post_id )) { return; } if( !isset( $_POST['myprefix_update_post_nonce']) || !wp_verify_nonce( $_POST['myprefix_update_post_nonce'], 'myprefix_update_post_metabox' )) { return; } if(array_key_exists('myprefix_text_metafield', $_POST)) { update_post_meta( $post_id, '_myprefix_text_metafield', sanitize_text_field($_POST['myprefix_text_metafield']) ); } } add_action( 'save_post', 'myprefix_save_post_metabox', 10, 2 );

I am not going to go into details in this code since this is out of the scope of the article, but what it’s essentially doing is:

  • Making a metabox using the add_meta_box function
  • Rendering an HTML input using the myprefix_post_options_metabox_html function
  • Controlling the metafield, called _myprefix_text_metafield
  • Using the save_post action hook to get the HTML input value and update the field using update_post_meta.

If you have the classic editor plugin installed, then you should see the metafield in the post editor:

Note that the field is prefixed with an underscore (_myprefix_text_metafield) which prevents it from being edited using the custom fields metabox that comes standard in WordPress. We add this underscore because we intend to manage the field ourselves and because it allows us to hide it from the standard Custom Fields section of the editor.

Now that we have a way to manage the field in the classic editor, let’s go ahead and deactivate the classic editor plugin and switch back to Gutenberg. The metabox will still appear in Gutenberg. However, as we discussed earlier, WordPress recommends porting this PHP-based metabox using a JavaScript approach.

That’s what we will do in the rest of the article. Now that we know how to use the Redux-like stores to manipulate data and how to add some React content in the sidebar, we can finally create a React component that will manipulate our metafield and add it in the sidebar of the Gutenberg editor.

We don’t want to completely get rid of the PHP-based field because it’s still helpful in the event that we need to use the classic editor for some reason. So, we’re going to hide the field when Gutenberg is active and show it when the classic editor is active. We can do that by updating the myprefix_add_meta_box function to use the __back_compat_meta_box option:

function myprefix_add_meta_box() { add_meta_box( 'myprefix_post_options_metabox', 'Post Options', 'myprefix_post_options_metabox_html', 'post', 'normal', 'default', array('__back_compat_meta_box' => true) ); }

Let’s move on to creating the React component that manages the metadata.

Getting and setting metadata using JavaScript

We have seen how to get the post title and how to change it using the wp-data module. Let’s take a look at how to do the same for custom fields. To get metafields, we can call the save selector getEditedPostAttribute. But this time we will pass it a value of meta instead of title.

Once that’s done, test it out in the browser console:

wp.data.select('core/editor').getEditedPostAttribute('meta')

As you will see, this function will return an empty array, although we are sure that we have a custom field called _myprefix_text_metafield that we are managing using the classic editor. To make custom fields manageable using the data module, we first have to register the field in the plugin.php.

function myprefix_register_meta() { register_meta('post', '_myprefix_text_metafield', array( 'show_in_rest' => true, 'type' => 'string', 'single' => true, )); } add_action('init', 'myprefix_register_meta');

Make sure to set the show_in_rest option to true. WordPress will fetch the fields using the WP REST API. That means, we need to enable the show_in_rest option to expose it.

Run the console test again and we will have an object with all of our custom fields returned.

Amazing! We are able to get our custom field value, so now let’s take a look at how can we change the value in the store. We can dispatch the editPost action in the core/editor store and pass it an object with a meta key, which will be another object with the fields that we need to update:

wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: 'new value'}})

Now try running the getEditedPostAttribute selector again and the value should be updated to new value.

If you try saving a post after updating the field using Redux, you will get an error. And if you take a look at the Network tab in DevTools, you will find that the error is returned from the wp-json/wp/v2/posts/{id} REST endpoint that says that we are not allowed to update _myprefix_text_metafield.

This because WordPress treats any field that is prefixed with an underscore as a private value that cannot be updated using the REST API. We can, however, specify an auth_callback option that will allow updating this field using the REST API when it returns true as long as the editor is capable of editing posts. We can also add the sanitize_text_field function to sanitize the value before saving to the database:

function myprefix_register_meta() { register_meta('post', '_myprefix_text_metafield', array( 'show_in_rest' => true, 'type' => 'string', 'single' => true, 'sanitize_callback' => 'sanitize_text_field', 'auth_callback' => function() { return current_user_can('edit_posts'); } )); } add_action('init', 'myprefix_register_meta');

Now try the following:

  • Open a new post in WordPress.
  • Run this in the DevTools console see the current value of the field:
wp.data.select('core/editor').getEditedPostAttribute('meta')
  • Run this in DevTools to update the value:
wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: 'new value'}})
  • There will be errors, so save the post to clear them.
  • Refresh the page and run this in the DevTools console:
wp.data.select('core/editor').getEditedPostAttribute('meta')

Does the new value show up in the console? If so, great! Now we know how to get and set the meta field value using Redux and we are ready to create a react component in the sidebar to do that.

Creating a React component to manage the custom fields

What we need to do next is create a React component that contains a text field that is controlled by the value of the metafield in the Redux store. It should have the value of the meta field...and hey, we already know how to get that! We can create the component in a separate file and then import it index.js. However I am simply going to create directly in index.js since we’re dealing with a very small example.

Again, we’re only working with a single text field, so let’s import a component provided by a WordPress package called @wordpress/components. This package contains a lot of reusable components that are Gutenberg-ready without us having to write them from scratch. It’s a good idea to use components from this package in order to be consistent with the rest of the Gutenberg UI.

First, let’s install this package:

npm install --save @wordpress/components

We’ll import TextControl and PanelBody at the top of index.js to fetch the two components we need from the package:

import { PanelBody, TextControl } from "@wordpress/components";

Now let’s create our component. I am going to create a React functional component and call it PluginMetaFields, but you can use a class component if you’d prefer that.

let PluginMetaFields = (props) => { return ( <> <PanelBody title={__("Meta Fields Panel", "textdomain")} icon="admin-post" intialOpen={ true } > <TextControl value={wp.data.select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield']} label={__("Text Meta", "textdomain")} /> </PanelBody> </> ) }

PanelBody takes title, icon and initialOpen props. Title and icon are pretty self-explanatory. initialOpen puts the panel in an open/expanded state by default. Inside the panel, we have TextControl. which receives a label and a value for the input. As you can see in the snippet above, we get the value from the global store by accessing the _myprefix_text_metafield field from the object returned by wp.data.select('core/editor').getEditedPostAttribute('meta').

Notice that we are now depending on @wordpress/components and use wp.data. We must add these packages as dependencies when we enqueue our file in plugin.php:

function myprefix_enqueue_assets() { wp_enqueue_script( 'myprefix-gutenberg-sidebar', plugins_url( 'build/index.js', __FILE__ ), array( 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data' ) ); } add_action( 'enqueue_block_editor_assets', 'myprefix_enqueue_assets' );

Let’s officially add the component to the sidebar instead of the dummy text we put in earlier as a quick example:

registerPlugin( 'myprefix-sidebar', { icon: 'smiley', render: () => { return ( <> <PluginSidebarMoreMenuItem target="myprefix-sidebar" > {__('Meta Options', 'textdomain')} </PluginSidebarMoreMenuItem> <PluginSidebar name="myprefix-sidebar" title={__('Meta Options', 'textdomain')} > <PluginMetaFields /> </PluginSidebar> </> ) } })

This should should give you a "Meta Options" panel that contains a "Meta Fields" title, a pin icon, and a text input with a "Test Meta" label and default value of "new value."

Nothing will happen when you type into the text input because we are not yet handling updating the field. We’ll do that next, however, we first need to take care of another problem. Try to run editPost in the DevTools console again, but with a new value:

wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: 'a newer value'}})

You will notice that the value in the text field will not update to the new value. That’s the problem. We need the field to be controlled by the value in the Redux store, but we don’t see that reflected in the component. What’s up with that?

If you have used Redux with React before, then you probably know that we need to use a higher order component called connect in order to use Redux store values in a React component. The same goes for React components in Gutenberg — we have to use some higher order component to connect our component with the Redux-like store. Unfortunately, we are unable to simply call wp.data.select directly as we did before. This higher order component lives in the wp.data global variable which is also available as an npm package called @wordpress.data. So let’s install it to help us solve the issue.

npm install --save @wordpress/data

The higher order component we need is called withSelect, so let’s import it in index.js.

import { withSelect } from "@wordpress/data";

Remember that we already added wp-data as a dependency in wp_enqueue_script, so we can just use it by wrapping our component with it, like so:

PluginMetaFields = withSelect( (select) => { return { text_metafield: select('core/editor').getEditedPostAttribute('meta')['_myprefix_text_metafield'] } } )(PluginMetaFields);

Here, we’re overriding our PluginMetaFields component and assigning it the same component, now wrapped with the withSelect higher order component. withSelect will receive a function as an argument. This function will receive the select function (which we used to access wp.data.select) and it should return an object. Each key in this object will be injected as a prop in the component (similar to connect in Redux). withSelect will return a function that we can pass it the component (PluginMetaFields) again as seen above. So, by having this higher order component, we now get text_metafield as a prop in the component, and whenever the meta value in the redux store is updated, the prop will also get updated — thus, the component will update since components update whenever a prop is changed.

let PluginMetaFields = (props) => { return ( <> <PanelBody title={__("Meta Fields Panel", "textdomain")} icon="admin-post" intialOpen={ true } > <TextControl value={props.text_metafield} label={__("Text Meta", "textdomain")} /> </PanelBody> </> ) }

If you now try and run editPost with a new meta value in your browser, the value of the text field in the sidebar should also be updated accordingly!

So far, so good. Now we know how to connect our React components with our Redux-like stores. We are now left with updating the meta value in the store whenever we type in the text field.

Dispatching actions in React components

We now need to dispatch the editPost action whenever we type into the text field. Similar to wp.data.select, we also should not call wp.data.dispatch directly in our component like so:

// Do not do this <TextControl value={props.text_metafield} label={__("Text Meta", "textdomain")} onChange={(value) => wp.data.dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: value}}) } />

We will instead wrap our component with another higher order component from the @wordpress.data package called withDispatch. We’ve gotta import that, again, in plugin.js:

import { withSelect, withDispatch } from "@wordpress/data";

In order to use it, we can wrap our component — which is already wrapped with withSelect and again with withDispatch — like so:

PluginMetaFields = withDispatch( (dispatch) => { return { onMetaFieldChange: (value) => { dispatch('core/editor').editPost({meta: {_myprefix_text_metafield: value}}) } } } )(PluginMetaFields);

You can check out yet another WordPress package called @wordpress/compose. It makes using multiple high order components a bit cleaner for use in a single component. But I will leave that to you to try out for the sake of keeping our example simple.

withDispatch is similar to withSelect in that it will receive a function that has the dispatch function as an argument. That allows us to return an object from this function that contains functions that will be available inside the component’s props. I went about this by creating a function with an arbitrary name (onMetaFieldChange) that will receive a value, dispatch the editPost action, and set the meta value in the Redux store to the value received in the function’s argument. We can call this function in the component and pass it the value of the text field inside the onChange callback:

<TextControl value={props.text_metafield} label={__("Text Meta", "textdomain")} onChange={(value) => props.onMetaFieldChange(value)} />

Confirm everything is working fine by opening the custom sidebar in the WordPress post editor, updating the field, saving the post and then refreshing the page to make sure the value is saved in the database!

Let’s add a color picker

It should be clear now that can we update a meta field using JavaScript, but we’ve only looked at simple text field so far. The @wordpress/components library provides a lot of very useful components, including dropdowns, checkboxes, radio buttons, and so on. Let’s level up and conclude this tutorial by taking a look at how we can use the color picker component that’s included in the library.

You probably know what to do. First, we, import this component in index.js:

import { PanelBody, TextControl, ColorPicker } from "@wordpress/components";

Now, instead of registering a new custom field, let’s aim for simplicity and assume that this color picker will be controlled by the same _myprefix_text_metafield field we worked with earlier. We can use the ColorPicker component inside our PanelBody and it will be very similar to what we saw with TextControl, but the prop names will be slightly different. We have a color prop instead of value and onChangeComplete instead on onChange. Also, onChangeComplete will receive a color object that contains some information about the chosen color. This object will have a hex property we can use to store the color value in the _myprefix_text_metafield field.

Catch all that? It boils down to this:

<ColorPicker color={props.text_metafield} label={__("Colour Meta", "textdomain")} onChangeComplete={(color) => props.onMetaFieldChange(color.hex)} />

We should now have a color picker in our sidebar, and since it’s controlling the same meta field as the TextControl component, our old text field should update whenever we pick a new color.

That’s a wrap!

If you have reached this far in the article, then congratulations! I hope you enjoyed it. Make sure to check out my course if you want to learn more about Gutenberg and custom blocks. You can also find the final code for this article over at GitHub.

The post Managing WordPress Metadata in Gutenberg Using a Sidebar Plugin appeared first on CSS-Tricks.

So, you think you’ve got project management nailed down

Css Tricks - Thu, 06/20/2019 - 5:04am

(This is a sponsored post.)

Who needs a project manager? You're an organized person who can keep track of your own work, right?

Wrong.

Well, wrong if you're part of a team. The thing about being self-organized is that it's related to project management but not synonymous with it. Case in point: what happens if your project relies on someone else's involvement? Sure you're organized, but can you always say the same about your co-workers? Chances are you need something to keep everyone in sync so that a project stays on course.

That's where you should consider trying monday.com.

monday.com is project management, but with a human touch. Sure, there's task lists, assignments, milestones, due dates, and such like you would expect from any project management tool. That's a given. That said, monday.com takes things up a notch by stripping away the barriers that prevent team members from collaborating with one another. For example, monday.com includes real-time messaging, file sharing, reporting, and a slew of other features that bridge the gaps between people and tasks so that everyone has purview into the progress of a project. Plus, it's so pretty to look at.

There's so much more than meets the eye because monday.com goes beyond project management. There's resource management that ensures you have the right tools for a project, forecasting to affirm the prospect of a business opportunity, and even client management services. Seriously, your team and perhaps company can lean into monday.com and get a ton of use out of it.

You know what to do from here. Give monday.com a try. There's a free trial and we're sure you'll find it to be so useful that you'll want to stick with it well beyond.

Get Started

Direct Link to ArticlePermalink

The post So, you think you’ve got project management nailed down appeared first on CSS-Tricks.

How to Increase Your Page Size by 1,500% with webpack and Vue

Css Tricks - Wed, 06/19/2019 - 4:25am

Disclaimer: This article is mostly satire. I do not think that I am better than you because I once wrote some TypeScript nor do I think that it’s a good thing for us to make web pages bigger. Feel free to misrepresent these views to maximize clicks.

You know, there are a lot of articles out there telling you how to make your page smaller: optimize your images, remove extraneous CSS rules, re-write the whole thing in Dreamweaver using framesets. Look, ?Walmart just reduced their page size by some numbers, give or take.

JavaScript housekeeping:

&#x1f5d1;️ Remove old & duplicate deps
&#x1f425; Replace large deps w/small ones
&#x1f9d0; Check you aren't over-polyfilling
&#x1f9fd; Clean up A/B test configs
✂️ Code-splitting!

Walmart Grocery did this for their site. 69% smaller JS bundles. 28% faster Time-to-Interactive. pic.twitter.com/3kSp7Ssi35

— Addy Osmani (@addyosmani) May 31, 2019

What we don’t have are enough articles showing you how to increase your page size. In fact, the only article I could find was this one from the Geek Squad which ended up being about making the font size bigger. This is a good start, but I think we can do better.

Put on some weight

Now, why would you want to increase your page size? Isn’t that a not-very-nice thing for people on low bandwidth connections? Well, there are several excellent and in no-way-contrived reasons and here are three of them since things that come in threes are more satisfying.

  1. You have a gigabit connection and you live in Tennessee so surely everyone else is in better shape than you are.
  2. Browsers do caching, silly. That means that you only have to download the site once. Stop complaining. First world problems.
  3. You don’t care whether or not people ever visit your site because you, "work to live, not live to work."

If any of those completely relatable reasons resonates with you, I’d like to show you how I increased the size of my CSS by 1,500% — and you can too, with one simple webpack trick.

One weird trick

It all started when I decided to refactor my retirement plan project called The Urlist over to the Bulma CSS framework.

The original incarnation of the site was all hand-rolled and my Sass looked like an episode of Hoarders.

"Burke, you don’t need 13 different .button styles. Why don’t you pick one and we can get rid of these other 12 so you have somewhere to sleep?"

Bulma also includes things like modals that I used third-party Vue components to make.

It also has a hamburger menu because it’s a well-known scientific fact that you cannot have a successful site without a hamburger.

Look,?I don’t make the rules. This is just how business works.

I was quite happy with the result. Bulma styles are sharp, and the layout system is easy to learn. It’s almost as if someone somewhere understands CSS and also doesn’t hate me. That’s just a hard combination to find these days.

After a few weeks of refactoring (during which I would ask myself, "WHAT ARE YOU EVEN DOING MAN?!? THE SITE ALREADY WORKS!"), I finally finished. As a side note, the next time you think about refactoring something, don’t. Just leave it alone. If you don’t leave any technical debt for the next generation, they’re going to be extremely bored and that’s going to be on you.

When I built the project, I noticed something odd: the size of my CSS had gone up pretty significantly. My hand-crafted abomination was only 30KB gzipped and I was up to 260KB after the refactor.

And, to make matters worse, the Vue CLI was lecturing me about it...

Which, of course, I ignored. I don’t take instructions from robots.

What I did instead was deploy it. To production. On the internet. Because I did not spend all of this time refactoring to not deploy it. Yeah, sunk costs and all that, but excuse me if I’m more pragmatic than your poster of logical fallacies. All I’m saying is I came to party and I was not going home without a buzz.

Then I took to Twitter to announce my accomplishment to the ambivalent masses. As one does.

I refactored https://t.co/hgGmemoQeX to Bulma. The whole thing looks cleaner and the styles are drastically simplified. Great work, @jgthms.

Only downside is that my CSS is now pretty big. ~260kb gzipped. Before it was ~30kb. Worth the trade-off? pic.twitter.com/te2DTgknS1

— Burke Holland (@burkeholland) May 28, 2019

Shortly thereafter, Jeremy Thomas, who created Bulma (and clearly loves Dragon Ball) responded. It was quick, too. It’s like there is a bat signal that goes out whenever a moron tweets.

Nice! It seems your CSS got big because of lots of duplicate styles.
For example, ".section.is-medium[data-v-" is in the CSS 13 times, but should only be there once. Same if your search for ".hero[data-v-".
It seems you've namespaced the whole of Bulma in each of your component.

— Jeremy Thomas (@jgthms) May 28, 2019

Duplicate styles? 13 times? What the heck is a namespace? Is that a ? symbol or a custom Jeremy Thomas logo?

It’s at this moment that I realized that I have no idea what I’m doing.

Put the Sass down and back away slowly

I’ll be the first to admit that I don’t know a lot about CSS, and even Less about Sass. Get it? Less about Sass? Forget it. I don’t want your pity laugh.

When I setup my Vue CLI project to use Bulma, I created a src/styles folder and dropped in a bulma-but-not-all-of-bulma-only-some-of-it.scss file. They say naming things is hard, but I don’t see why.

That file imports the pieces of Bulma that I want to use. It’s Bulma, but not all of it. Only some of it.

@import "bulma/sass/utilities/_all.sass"; @import "bulma/sass/base/_all.sass"; @import "bulma/sass/form/shared.sass"; @import "bulma/sass/form/input-textarea.sass"; // etc...

Then I imported that file into a custom Sass file which I called... site.scss. I like to keep things simple.

@import "./bulma-but-not-all-of-bulma-only-some-of-it.scss"; html, body { background-color: #f9fafc; } // etc...

I wanted to import these files into Vue globally so that I could use them in every component. And I wanted to do it the right way; the canonical way. I think it’s clear from my willingness to deploy 2+ MB of CSS into production that I like to do things the "right way".

I read this excellent blog post from Sarah Drasner called, "How to import a Sass file into every component in your Vue app." She shows how to do it by modifying the webpack build process via the vue.config.js file.

module.exports = { css: { loaderOptions: { sass: { data: `@import "@/styles/site.scss";` } } } }

What I did not understand is that this imports Sass into every component in a Vue app. You know, like the title of the blog post literally says. This is also how I ended up with a bunch of duplicate styles that had a data-v- attribute selector on them. I have scoped styles to thank for that.

How Vue handles `scoped`

Vue allows you to "scope" styles to a component. This means that a style only affects the component that it’s in, and not the rest of the page. There is no magic browser API that does this. Vue pulls it off by dynamically inserting a data- attribute in both the element and the selector. For example, this:

<template> <button class="submit">Submit</button> <template> <style lang="scss" scoped> .submit { background-color: #20ae96; } </style>

...becomes this:

<button class="submit" data-v-2929>Submit</button> <style> .submit[data-v-2929] { background-color: #20ae96; } </style>

That dynamic data tag gets added to every child element in the component as well. So every element and every style for this component will have a data-v-2929 on them at runtime.

If you import a Sass file into your component that has actual styles in it, Vue (via webpack) will pull in those styles and "namespace" them with that dynamic data- attribute. The result is that is you include Bulma in your app 13 damn times with a bunch of data-v weirdness in front of it.

But this begs the question: if webpack renders the CSS in every single component, why would you ever want to use the vue.config.js approach? In a word: variables.

The variable sharing problem

You can’t define a Sass variable in one component and reference it from another. That would also be kind of hard to manage since you would be defining and using variables all over the place. Only I would write code like that.

You, on the other hand, would probably put all your variables in a variables.scss file. Each component would then reference that central store of variables. Importing a variables file into every single component is redundant. It’s also excessive. And unnecessary. And long-winded.

This is precisely the problem that Sarah’s article is solving: importing a Sass file into every component in your project.

It’s OK to import something like variables into every component because variables aren’t rendered. If you import 200 variables and only reference one of them, who cares? Those variables don’t exist in the rendered CSS anyway.

For example, this:

<style lang="scss" scoped> $primary: #20ae96; $secondary: #336699; .submit { background-color: $primary } </style>

...becomes this:

<style> .submit[data-v-2929] { background-color: #20ae96; } </style>

So, there are really two problems here:

  1. Bulma needs to be global.
  2. Bulma’s variables should be accessible from the components.

What we need is a clever combination of Sarah’s technique, along with a little proprietary knowledge about how Bulma is structured.

Using Bulma with the Vue

We’re going to accomplish this with the least amount of duplication by having three files in the src/styles directory:

variables.scss: This file will be where you pull in Bulma’s variables and override/define your own. Note that you have to include the following three files to get all of Bulma’s variables. And they have to be in this order...

// Your variables customizations go up here // Include Bulma's variables @import "bulma/sass/utilities/initial-variables.sass"; @import "bulma/sass/utilities/functions.sass"; @import "bulma/sass/utilities/derived-variables.sass";

bulma-custom.scss: This file is where you pull in the pieces of Bulma that you want. It should reference the variables.scss file.

@import "./variables.scss"; /* UTILTIES */ @import "bulma/sass/utilities/animations.sass"; @import "bulma/sass/utilities/controls.sass"; @import "bulma/sass/utilities/mixins.sass"; // etc...

site.scss: This pulls in the bulma-custom.scss file and is also where you define global styles that are used across the whole project.

@import url("https://use.fontawesome.com/releases/v5.6.3/css/all.css"); @import "./bulma-custom.scss"; html, body { height: 100%; background-color: #f9fafc; } // etc...

Import the site.scss file into your main.js file. Or in my case, main.ts. Does it make me better than you that I use TypeScript? Yes. Yes it does.

import Vue from "vue"; import App from "./App.vue"; import router from "./router"; // import styles import "@/styles/site.scss";

This makes all of the Bulma pieces we are using available in every component. They are global, but only included once.

Per Sarah’s article, add the variables.scss file to the vue.config.js file.

module.exports = { css: { loaderOptions: { sass: { data: `@import "@/styles/variables.scss";` } } } }

This makes it so that you can reference any of the Bulma variables or your own from any .vue component.

Now you have the best of both worlds: Bulma is available globally and you still have access to all Bulma variables in every component.

Total size of CSS now? About 1,500% smaller...

Take that, Walmart.

Redemption via PR

In an effort to redeem myself, I’ve submitted a PR to the Bulma docs that walks through how to customize Bulma in a Vue CLI project. It’s an act of contrition for taking to Twitter and making Bulma seem like the problem when really Burke is the problem.

And you would think that by now I would have this figured out: Burke is always the problem.

The post How to Increase Your Page Size by 1,500% with webpack and Vue appeared first on CSS-Tricks.

Drop caps & design systems

Css Tricks - Wed, 06/19/2019 - 4:25am

Ethan Marcotte has written up his process for how to make drop caps accessible for screen readers and browsers alike. All of that is very interesting and I’m sure I’ll use a technique like this in the near future, but the part that made me hop out of my seat is where Ethan notes his experience with design systems at Vox:

Since rolling out our new and improved drop caps, we’ve continued to iterate on them. (Including fixing a number of bugs that I, a professional web designer, introduced.) We’ve also discussed potential changes to the custom styles feature, in order to make it sustainable. But for my money, the real benefit of the work wasn’t the drop caps themselves, but the process that emerged from it.

This is something I’ve been thinking about a lot. The tricky thing to understanding design systems is that the process of creating and maintaining them is just as important than the code it uses. Sure, yes, fixing small things is very important, but the long-lasting improvements to a design system will always be around process. Why is this thing breaking? How long has it been broken for? How do we setup a way to make sure that this problem never pops up again?

Questions about process will always be the most useful questions to ask. The system will reward you for asking.

Direct Link to ArticlePermalink

The post Drop caps & design systems appeared first on CSS-Tricks.

Syndicate content
©2003 - Present Akamai Design & Development.