Tag: Different

Different Ways to Get CSS Gradient Shadows

It’s a question I hear asked quite often: Is it possible to create shadows from gradients instead of solid colors? There is no specific CSS property that does this (believe me, I’ve looked) and any blog post you find about it is basically a lot of CSS tricks to approximate a gradient. We’ll actually cover some of those as we go.

But first… another article about gradient shadows? Really?

Yes, this is yet another post on the topic, but it is different. Together, we’re going to push the limits to get a solution that covers something I haven’t seen anywhere else: transparency. Most of the tricks work if the element has a non-transparent background but what if we have a transparent background? We will explore this case here!

Before we start, let me introduce my gradient shadows generator. All you have to do is to adjust the configuration, and get the code. But follow along because I’m going to help you understand all the logic behind the generated code.

Non-transparent solution

Let’s start with the solution that’ll work for 80% of most cases. The most typical case: you are using an element with a background, and you need to add a gradient shadow to it. No transparency issues to consider there.

The solution is to rely on a pseudo-element where the gradient is defined. You place it behind the actual element and apply a blur filter to it.

.box {   position: relative; } .box::before {   content: "";   position: absolute;   inset: -5px; /* control the spread */   transform: translate(10px, 8px); /* control the offsets */   z-index: -1; /* place the element behind */   background: /* your gradient here */;   filter: blur(10px); /* control the blur */ }

It looks like a lot of code, and that’s because it is. Here’s how we could have done it with a box-shadow instead if we were using a solid color instead of a gradient.

box-shadow: 10px 8px 10px 5px orange;

That should give you a good idea of what the values in the first snippet are doing. We have X and Y offsets, the blur radius, and the spread distance. Note that we need a negative value for the spread distance that comes from the inset property.

Here’s a demo showing the gradient shadow next to a classic box-shadow:

If you look closely you will notice that both shadows are a little different, especially the blur part. It’s not a surprise because I am pretty sure the filter property’s algorithm works differently than the one for box-shadow. That’s not a big deal since the result is, in the end, quite similar.

This solution is good, but still has a few drawbacks related to the z-index: -1 declaration. Yes, there is “stacking context” happening there!

I applied a transform to the main element, and boom! The shadow is no longer below the element. This is not a bug but the logical result of a stacking context. Don’t worry, I will not start a boring explanation about stacking context (I already did that in a Stack Overflow thread), but I’ll still show you how to work around it.

The first solution that I recommend is to use a 3D transform:

.box {   position: relative;   transform-style: preserve-3d; } .box::before {   content: "";   position: absolute;   inset: -5px;   transform: translate3d(10px, 8px, -1px); /* (X, Y, Z) */   background: /* .. */;   filter: blur(10px); }

Instead of using z-index: -1, we will use a negative translation along the Z-axis. We will put everything inside translate3d(). Don’t forget to use transform-style: preserve-3d on the main element; otherwise, the 3D transform won’t take effect.

As far as I know, there is no side effect to this solution… but maybe you see one. If that’s the case, share it in the comment section, and let’s try to find a fix for it!

If for some reason you are unable to use a 3D transform, the other solution is to rely on two pseudo-elements — ::before and ::after. One creates the gradient shadow, and the other reproduces the main background (and other styles you might need). That way, we can easily control the stacking order of both pseudo-elements.

.box {   position: relative;   z-index: 0; /* We force a stacking context */ } /* Creates the shadow */ .box::before {   content: "";   position: absolute;   z-index: -2;   inset: -5px;   transform: translate(10px, 8px);   background: /* .. */;   filter: blur(10px); } /* Reproduces the main element styles */ .box::after {   content: """;   position: absolute;   z-index: -1;   inset: 0;   /* Inherit all the decorations defined on the main element */   background: inherit;   border: inherit;   box-shadow: inherit; }

It’s important to note that we are forcing the main element to create a stacking context by declaring z-index: 0, or any other property that do the same, on it. Also, don’t forget that pseudo-elements consider the padding box of the main element as a reference. So, if the main element has a border, you need to take that into account when defining the pseudo-element styles. You will notice that I am using inset: -2px on ::after to account for the border defined on the main element.

As I said, this solution is probably good enough in a majority of cases where you want a gradient shadow, as long as you don’t need to support transparency. But we are here for the challenge and to push the limits, so even if you don’t need what is coming next, stay with me. You will probably learn new CSS tricks that you can use elsewhere.

Transparent solution

Let’s pick up where we left off on the 3D transform and remove the background from the main element. I will start with a shadow that has both offsets and spread distance equal to 0.

The idea is to find a way to cut or hide everything inside the area of the element (inside the green border) while keeping what is outside. We are going to use clip-path for that. But you might wonder how clip-path can make a cut inside an element.

Indeed, there’s no way to do that, but we can simulate it using a particular polygon pattern:

clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -100vmax,0 0,0 100%,100% 100%,100% 0,0 0)

Tada! We have a gradient shadow that supports transparency. All we did is add a clip-path to the previous code. Here is a figure to illustrate the polygon part.

Showing the clip-path coordinates for the element.

The blue area is the visible part after applying the clip-path. I am only using the blue color to illustrate the concept, but in reality, we will only see the shadow inside that area. As you can see, we have four points defined with a big value (B). My big value is 100vmax, but it can be any big value you want. The idea is to ensure we have enough space for the shadow. We also have four points that are the corners of the pseudo-element.

The arrows illustrate the path that defines the polygon. We start from (-B, -B) until we reach (0,0). In total, we need 10 points. Not eight points because two points are repeated twice in the path ((-B,-B) and (0,0)).

There’s still one more thing left for us to do, and it’s to account for the spread distance and the offsets. The only reason the demo above works is because it is a particular case where the offsets and spread distance are equal to 0.

Let’s define the spread and see what happens. Remember that we use inset with a negative value to do this:

The pseudo-element is now bigger than the main element, so the clip-path cuts more than we need it to. Remember, we always need to cut the part inside the main element (the area inside the green border of the example). We need to adjust the position of the four points inside of clip-path.

.box {   --s: 10px; /* the spread  */   position: relative; } .box::before {   inset: calc(-1 * var(--s));   clip-path: polygon(     -100vmax -100vmax,      100vmax -100vmax,      100vmax 100vmax,     -100vmax 100vmax,     -100vmax -100vmax,     calc(0px  + var(--s)) calc(0px  + var(--s)),     calc(0px  + var(--s)) calc(100% - var(--s)),     calc(100% - var(--s)) calc(100% - var(--s)),     calc(100% - var(--s)) calc(0px  + var(--s)),     calc(0px  + var(--s)) calc(0px  + var(--s))   ); }

We’ve defined a CSS variable, --s, for the spread distance and updated the polygon points. I didn’t touch the points where I am using the big value. I only update the points that define the corners of the pseudo-element. I increase all the zero values by --s and decrease the 100% values by --s.

It’s the same logic with the offsets. When we translate the pseudo-element, the shadow is out of alignment, and we need to rectify the polygon again and move the points in the opposite direction.

.box {   --s: 10px; /* the spread */   --x: 10px; /* X offset */   --y: 8px;  /* Y offset */   position: relative; } .box::before {   inset: calc(-1 * var(--s));   transform: translate3d(var(--x), var(--y), -1px);   clip-path: polygon(     -100vmax -100vmax,      100vmax -100vmax,      100vmax 100vmax,     -100vmax 100vmax,     -100vmax -100vmax,     calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),     calc(0px  + var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),     calc(100% - var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),     calc(100% - var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),     calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y))   ); }

There are two more variables for the offsets: --x and --y. We use them inside of transform and we also update the clip-path values. We still don’t touch the polygon points with big values, but we offset all the others — we reduce --x from the X coordinates, and --y from the Y coordinates.

Now all we have to do is to update a few variables to control the gradient shadow. And while we are at it, let’s also make the blur radius a variable as well:

Do we still need the 3D transform trick?

It all depends on the border. Don’t forget that the reference for a pseudo-element is the padding box, so if you apply a border to your main element, you will have an overlap. You either keep the 3D transform trick or update the inset value to account for the border.

Here is the previous demo with an updated inset value in place of the 3D transform:

I‘d say this is a more suitable way to go because the spread distance will be more accurate, as it starts from the border-box instead of the padding-box. But you will need to adjust the inset value according to the main element’s border. Sometimes, the border of the element is unknown and you have to use the previous solution.

With the earlier non-transparent solution, it’s possible you will face a stacking context issue. And with the transparent solution, it’s possible you face a border issue instead. Now you have options and ways to work around those issues. The 3D transform trick is my favorite solution because it fixes all the issues (The online generator will consider it as well)

Adding a border radius

If you try adding border-radius to the element when using the non-transparent solution we started with, it is a fairly trivial task. All you need to do is to inherit the same value from the main element, and you are done.

Even if you don’t have a border radius, it’s a good idea to define border-radius: inherit. That accounts for any potential border-radius you might want to add later or a border radius that comes from somewhere else.

It’s a different story when dealing with the transparent solution. Unfortunately, it means finding another solution because clip-path cannot deal with curvatures. That means we won’t be able to cut the area inside the main element.

We will introduce the mask property to the mix.

This part was very tedious, and I struggled to find a general solution that doesn’t rely on magic numbers. I ended up with a very complex solution that uses only one pseudo-element, but the code was a lump of spaghetti that covers only a few particular cases. I don’t think it is worth exploring that route.

I decided to insert an extra element for the sake of simpler code. Here’s the markup:

<div class="box">   <sh></sh> </div>

I am using a custom element, <sh>, to avoid any potential conflict with external CSS. I could have used a <div>, but since it’s a common element, it can easily be targeted by another CSS rule coming from somewhere else that can break our code.

The first step is to position the <sh> element and purposely create an overflow:

.box {   --r: 50px;   position: relative;   border-radius: var(--r); } .box sh {   position: absolute;   inset: -150px;   border: 150px solid #0000;   border-radius: calc(150px + var(--r)); }

The code may look a bit strange, but we’ll get to the logic behind it as we go. Next, we create the gradient shadow using a pseudo-element of <sh>.

.box {   --r: 50px;   position: relative;   border-radius: var(--r);   transform-style: preserve-3d; } .box sh {   position: absolute;   inset: -150px;   border: 150px solid #0000;   border-radius: calc(150px + var(--r));   transform: translateZ(-1px) } .box sh::before {   content: "";   position: absolute;   inset: -5px;   border-radius: var(--r);   background: /* Your gradient */;   filter: blur(10px);   transform: translate(10px,8px); }

As you can see, the pseudo-element uses the same code as all the previous examples. The only difference is the 3D transform defined on the <sh> element instead of the pseudo-element. For the moment, we have a gradient shadow without the transparency feature:

Note that the area of the <sh> element is defined with the black outline. Why I am doing this? Because that way, I am able to apply a mask on it to hide the part inside the green area and keep the overflowing part where we need to see the shadow.

I know it’s a bit tricky, but unlike clip-path, the mask property doesn’t account for the area outside an element to show and hide things. That’s why I was obligated to introduce the extra element — to simulate the “outside” area.

Also, note that I am using a combination of border and inset to define that area. This allows me to keep the padding-box of that extra element the same as the main element so that the pseudo-element won’t need additional calculations.

Another useful thing we get from using an extra element is that the element is fixed, and only the pseudo-element is moving (using translate). This will allow me to easily define the mask, which is the last step of this trick.

mask:   linear-gradient(#000 0 0) content-box,   linear-gradient(#000 0 0); mask-composite: exclude;

It’s done! We have our gradient shadow, and it supports border-radius! You probably expected a complex mask value with oodles of gradients, but no! We only need two simple gradients and a mask-composite to complete the magic.

Let’s isolate the <sh> element to understand what is happening there:

.box sh {   position: absolute;   inset: -150px;   border: 150px solid red;   background: lightblue;   border-radius: calc(150px + var(--r)); }

Here’s what we get:

Note how the inner radius matches the main element’s border-radius. I have defined a big border (150px) and a border-radius equal to the big border plus the main element’s radius. On the outside, I have a radius equal to 150px + R. On the inside, I have 150px + R - 150px = R.

We must hide the inner (blue) part and make sure the border (red) part is still visible. To do that, I’ve defined two mask layers —One that covers only the content-box area and another that covers the border-box area (the default value). Then I excluded one from another to reveal the border.

mask:   linear-gradient(#000 0 0) content-box,   linear-gradient(#000 0 0); mask-composite: exclude;

I used the same technique to create a border that supports gradients and border-radius. Ana Tudor has also a good article about masking composite that I invite you to read.

Are there any drawbacks to this method?

Yes, this definitely not perfect. The first issue you may face is related to using a border on the main element. This may create a small misalignment in the radii if you don’t account for it. We have this issue in our example, but perhaps you can hardly notice it.

The fix is relatively easy: Add the border’s width for the <sh> element’s inset.

.box {   --r: 50px;   border-radius: var(--r);   border: 2px solid; } .box sh {   position: absolute;   inset: -152px; /* 150px + 2px */   border: 150px solid #0000;   border-radius: calc(150px + var(--r)); }

Another drawback is the big value we’re using for the border (150px in the example). This value should be big enough to contain the shadow but not too big to avoid overflow and scrollbar issues. Luckily, the online generator will calculate the optimal value considering all the parameters.

The last drawback I am aware of is when you’re working with a complex border-radius. For example, if you want a different radius applied to each corner, you must define a variable for each side. It’s not really a drawback, I suppose, but it can make your code a bit tougher to maintain.

.box {   --r-top: 10px;   --r-right: 40px;   --r-bottom: 30px;   --r-left: 20px;   border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left); } .box sh {   border-radius: calc(150px + var(--r-top)) calc(150px + var(--r-right)) calc(150px + var(--r-bottom)) calc(150px + var(--r-left)); } .box sh:before {   border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left); }

The online generator only considers a uniform radius for the sake of simplicity, but you now know how to modify the code if you want to consider a complex radius configuration.

Wrapping up

We’ve reached the end! The magic behind gradient shadows is no longer a mystery. I tried to cover all the possibilities and any possible issues you might face. If I missed something or you discover any issue, please feel free to report it in the comment section, and I’ll check it out.

Again, a lot of this is likely overkill considering that the de facto solution will cover most of your use cases. Nevertheless, it’s good to know the “why” and “how” behind the trick, and how to overcome its limitations. Plus, we got good exercise playing with CSS clipping and masking.

And, of course, you have the online generator you can reach for anytime you want to avoid the hassle.


Different Ways to Get CSS Gradient Shadows originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , ,

Different Ways to Write CSS in React

We’re all familiar with the standard way of linking up a stylesheet to the <head> of an HTML doc, right? That’s just one of several ways we’re able to write CSS. But what does it look like to style things in a single-page application (SPA), say in a React project?

Turns out there are several ways to go about styling a React application. Some overlap with traditional styling, others not so much. But let’s count all the ways we can do it.

Importing external stylesheets

As the name suggests, React can import CSS files. The process is similar to how we link up CSS file in the HTML <head>:

  1. Create a new CSS file in your project directory.
  2. Write CSS.
  3. Import it into the React file.

Like this:

import "./style.css";

That usually goes at the top of the file where other imports happen:

import { React } from "react"; import "./Components/css/App.css"; function App() {   return (     <div className="main">     </div>   ); } export default App;

In this example, a CSS file is imported into an App.js from the /Components/css folder.

Write inline styles

You may be used to hearing that inline styling isn’t all that great for maintainability and whatnot, but there are definitely situations (here’s one!) where it makes sense. And maintainability is less of an issue in React, as the CSS often already sits inside the same file anyway.

This is a super simple example of inline styling in React:

<div className="main" style={{color:"red"}}>

A better approach, though, is to use objects:

  1. First, create an object that contains styles for different elements.
  2. Then add it to an element using the style attribute and then select the property to style.

Let’s see that in context:

import { React } from "react"; function App() {   const styles = {     main: {       backgroundColor: "#f1f1f1",       width: "100%",     },     inputText: {       padding: "10px",       color: "red",     },   };   return (     <div className="main" style={styles.main}>       <input type="text" style={styles.inputText}></input>     </div>   ); } export default App;

This example contains a styles object containing two more objects, one for the .main class and the other for a text input, which contain style rules similar to what we’d expect to see in an external stylesheet. Those objects are then applied to the style attribute of elements that are in the returned markup.

Note that curly brackets are used when referencing styles rather than the quotation marks we’d normally use in plain HTML.

Use CSS Modules

CSS Modules… what the heck happened to those, right? They have the benefit of locally scoped variables and can be used right alongside React. But what are they, again, exactly?

Quoting the repo’s documentation:

CSS Modules works by compiling individual CSS files into both CSS and data. The CSS output is normal, global CSS, which can be injected directly into the browser or concatenated together and written to a file for production use. The data is used to map the human-readable names you’ve used in the files to the globally-safe output CSS.

In simpler terms, CSS Modules allows us to use the same class name in multiple files without clashes since each class name is given a unique programmatic name. This is especially useful in larger applications. Every class name is scoped locally to the specific component in which it is being imported.

A CSS Module stylesheet is similar to a regular stylesheet, only with a different extension (e.g. styles.module.css). Here’s how they’re set up:

  1. Create a file with .module.css as the extension.
  2. Import that module into the React app (like we saw earlier)
  3. Add a className to an element or component and reference the particular style from the imported styles.

Super simple example:

/* styles.module.css */ .font {   color: #f00;   font-size: 20px; }  import { React } from "react"; import styles from "./styles.module.css"; function App() {   return (     <h1 className={styles.heading}>Hello World</h1>   ); } export default App;

Use styled-components

Have you used styled-components? It’s quite popular and allows you to build custom components using actual CSS in your JavaScript. A styled-component is basically a React component with — get ready for it — styles. Some of the features include unique class names, dynamic styling and better management of the CSS as each component has its own separate styles.

Install the styled-components npm package in the command line:

npm install styled-components

Next up, import it into the React app:

import styled from 'styled-components'

Create a component and assign a styled property to it. Note the use of template literals denoted by backticks in the Wrapper object:

import { React } from "react"; import styled from "styled-components"; function App() {   const Wrapper = styled.div`     width: 100%;     height: 100px;     background-color: red;     display: block;   `;   return <Wrapper />; } export default App;

The above Wrapper component will be rendered as a div that contains those styles.

Conditional styling

One of the advantages of styled-components is that the components themselves are functional, as in you can use props within the CSS. This opens the door up to conditional statements and changing styles based on a state or prop.

Here’s a demo showing that off:

Here, we are manipulating the div’s display property on the display state. This state is controlled by a button that toggles the div’s state when clicked. This, in turn, toggles between the styles of two different states.

In inline if statements, we use a ? instead of the usual if/else syntax. The else part is after the semicolon. And remember to always call or use the state after it has been initialized. In that last demo, for example, the state should be above the Wrapper component’s styles.

Happy React styling!

That’s a wrap, folks! We looked at a handful of different ways to write styles in a React application. And it’s not like one is any better than the rest; the approach you use depends on the situation, of course. Hopefully now you’ve got a good understanding of them and know that you have a bunch of tools in your React styling arsenal.


Different Ways to Write CSS in React originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , ,
[Top]

Using Different Color Spaces for Non-Boring Gradients

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

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

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

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

Different gradient apps with different color spaces

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

To Shared LinkPermalink on CSS-Tricks


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

CSS-Tricks

, , , , ,
[Top]

Different Degrees of Custom Property Usage

One way to work with Custom Properties is to think of them as design tokens. Colors, spacings, fonts, and whatnot. You set them at the root of the page and use them throughout your CSS. Very useful, and the classic use case for not only Custom Properties but for preprocessor variables for the last one million years.

Another way to work with Custom Properties, which can be done in addition to the design tokens approach, is to go a lot harder and use them for every major unique styling choice on any given element.

Imagine you have a Card like this (simplified for demonstration sake, of course):

.card {   background: hsl(200deg 15% 73%);   border: 4px solid rgb(255 255 255 / 0.5);   padding: 2rem;   border-radius: 8px; } .card > h2 {   margin: 0 0 1rem 0;   border-bottom: 3px solid rgba(0 0 0 / 0.2); }

Fine.

But then when you inevitably make variations of the card, you’re on your own to override these rulesets. A CSS Custom Property approach can be like:

.card {   --card-background: hsl(200deg 15% 73%);   --card-border: 4px solid rgb(255 255 255 / 0.5);   --card-padding: 2rem;   --card-border-radius: 8px;   --card-title-margin: 0 0 1rem 0;   --card-title-border: 3px solid rgba(0 0 0 / 0.2);    background: var(--card-background);   border: var(--card-border);   padding: var(--card-padding);   border-radius: var(--card-border-radius); } .card > h2 {   margin: var(--card-title-margin);   border-bottom: var(--card-title-border); }

A little more verbose, for now, but look what happens when we want to do a variation:

.card-variation {   --card-background: purple;   --card-padding-block: 2.5rem;   --card-title-margin: 0 0 2rem 0; } 

Here are three clear advantages right off the bat:

  • I’m only changing values that I’ve clearly set up to be changed. My main Card prototype maintains the integrity I want it to keep.
  • I can style children of the variation without having to re-write those selectors correctly.
  • I can now pass in styling overrides from the style attribute in the HTML for quick, one-off variations.

Less verbose with fallbacks

Rather than declaring the Custom Properties at the top and then using them right below, I can do both at the same time like this:

.card {   background: var(--card-background, hsl(200deg 15% 73%));   border: var(--card-border, 4px solid rgb(255 255 255 / 0.5));   padding: var(--card-padding, 2rem);   border-radius: var(--card-border-radius, 8px); } .card > h2 {   margin: var(--card-title-margin, 0 0 1rem 0);   border-bottom: var(--card-title-border, 3px solid rgba(0 0 0 / 0.2)); }

Now if something like --card-background does happen to get set, it will override the fallback value here. I don’t completely love this, because it means elements above .card can override it. That might be what you want, but it’s not exactly the same as declaring the values at the .card level to begin with. No strong opinions here.

Breaking it up even more

An example here is that you might want to individually control padding.

.card {   --card-padding-block: 2rem;   --card-padding-inline: 2rem;   --card-padding: var(--card-padding-block) var(--card-padding-inline);    padding: var(--card-padding); }

Now a variation can control just a part of the padding if I want:

.card-variation {   --card-padding-inline: 3rem; }

You gotta be careful of the big gotcha though. Meaning if you declare all these at the root, this isn’t going to work, because those nested properties have already been resolved. But so long as it’s first declared on .card, you’ll be fine here.

Too far?

Say you wanted super ultimate control over every part of a value. For example:

html {   --color-1-h: 200deg;   --color-1-s: 15%;   --color-1-l: 73%;   --color-1-hsl: var(--color-1-h) var(--color-1-s) var(--color-1-l);   --color-1: hsl(var(--color-1-hsl)); }

That’s kinda neat, but it’s likely too far. Colors are almost certainly going to be declared at the root and left alone, so the great gotcha is going to make overriding the low-level child properties impossible. Besides, if you have a --color-1, you probably have a 2-9 (or more) as well, which is all well and good because there is far more delicate design magic to a color system than simple mathematical manipulations of color parts.

Deliverable design systems?

There is no doubt that Tailwind has enjoyed a lot of popularity. It uses an atomic approach where a slew of HTML classes control one property each. I’d argue some of that popularity is driven by the fact that if you choose from these pre-configured classes, that the design ends up fairly nice. You can’t go off the rails. You’re choosing from a limited selection of values that have been designed to look good.

I wouldn’t go as far as to say that a Custom Properties heavy-based approach to styling is exactly the same. For example, you’ll still need to think of a class name abstraction rather than apply styling directly to the HTML element. But, it might enjoy some of the same constraints/limitations that make Tailwind and other atomic class approaches desirable. If you can only pick from a pre-defined set of --spacing-x values, --color-x values, and --font-x values, you might achieve a more cohesive design than you would have otherwise.

Personally, I’ve found inching toward a design system that is more heavily based on Custom Properties feels good — if nothing else to make variations and overrides more sensible to manage.

What about third-party design systems delivering what they deliver as… nothing but a big ol’ set of Custom Properties to use at your leisure?

Pollen

Third-party deliverables don’t even have to be the entire kitchen sink like this. For example, Adam Argyle’s transition.style provides a “Hackpack” that is nothing but Custom Properties of transition animation helpers.

Understandabilty cost

One pushback I’ve heard against this more all-in approach on Custom Properties is newcomer understandability. If you wrote the system, it probably makes perfect sense to you. But it’s an additional abstraction on top of CSS. CSS knowledge is shared by all, bespoke systems knowledge is only shared by the people actively working on it.

Rolling in fresh to a system heavily using Custom Properties is going to have a heck of a learning curve.

Videos


The post Different Degrees of Custom Property Usage appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

svg-loader: A Different Way to Work With External SVG

SVGs are awesome: they are small, look sharp on any scale, and can be customized without creating a separate file. However, there is something I feel is missing in web standards today: a way to include them as an external file that also retains the format’s customization powers.

For instance, let’s say you want to use your website’s logo stored as web-logo.svg. You can do:

<img src="/images/logo.svg" />

That’s fine if your logo is going to look the same everywhere. But in many cases, you have 2-3 variations of the same logo. Slack, for example, has two versions.

Even the colors in the main logo are slightly different.

If we had a way to customize fill color of our logo above, we could pass any arbitrary color to render all the variations.

Take the case of icons, too. You wouldn’t want to do something like this, would you?

<img src="/icons/heart-blue.svg" /> <img src="/icons/heart-red.svg" />

Load external SVGs as inline elements

To address this, I have created a library called svg-loader. Simply put, it fetches the SVG files via XHR and loads them as inline elements, allowing you to customize the properties like fill and stroke, just like inline SVGs.

For example, I have a logo on my side-project, SVGBox. Instead of creating a different file for every variation, I can have one file and customize the fill color:

I used data-src to set the URL of SVG file. The fill attribute overrides fill of the original SVG file.

To use the library, the only thing I have to ensure is that files being served have appropriate CORS headers for XHRs to succeed. The library also caches the files locally, making the subsequent much faster. Even for the first load, the performance is comparable to using <img> tags.

This concept isn’t new. svg-inject does something similar. However, svg-loader is easier to use as we only have to include the library somewhere in your code (either via a <script> tag, or in the JavaScript bundle). No extra code is needed.

Dynamically-added elements and change in attributes are also handled automatically, which ensures that it works with all web frameworks. Here’s an example in React:

But why?

This approach may feel unorthodox because it introduces a JavaScript dependency and there are already multiple ways to use SVGs, including inline and from external sources. But there’s a good case for using SVGs this way. Let’s examine them by answering the common questions.

Can we not just inline SVG ourselves?

Inlining is the simplest way to use SVGs. Just copy and paste the SVG code in the HTML. That’s what svg-loader is ultimately doing. So, why add the extra steps to load a SVG file from somewhere else? There are two major reasons:

  1. Inline SVGs make the code verbose: SVGs can be anywhere from a few lines to a few hundred. Inline SVGs can work well if what you need is just a couple of icons and they are all tiny. But it becomes a major pain if they are sizeable or many, because then, they become long strings of text in code that isn’t “business logic.” The code becomes hard to parse.

    It’s the same thing as preferring an external stylesheet over a <style> tag or using images instead of data URIs. It’s no wonder that in React codebases, the preferred approach is to use SVG as a separate component, rather than define it as a part of JSX.

  1. External SVGs are much more convenient: Copying and pasting often does the job, but external SVGs can be really convenient. Say you’re experimenting with which icon to use in your app. If you’re using inline SVGs, that means going back and forth to get the SVG code. But with external SVGs, you only have to know the name of the file.

    Take a look at this example. One of the most extensive icon repository on GitHub is Material Design Icons. With svg-loader and unpkg, we can start using any of 5,000+ icons right away.

Isn’t it inefficient to trigger an HTTP request for every SVG versus making a sprite?

Not really. With HTTP2, the cost of making an HTTP request has become less relevant. Yes, there are still benefits of bundling (e.g., better compression), but for non-blocking resources and XHRs, the pros are almost non-existent in real-world scenarios.

Here’s a Pen loading 50 icons in a similar fashion as above. (Open in incognito mode as the files are cached by default):

What about <use> tag (SVG symbols)?

SVG symbols separate the definition of the SVG file from its use. Instead of defining the SVG everywhere, we can have something like this:

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

The problem is that none of the browsers support using symbols files hosted on a third-party domain. Therefore, we can’t do something like this:

<svg>   <use xlink:href="https://icons.com/symbols.svg#heart-icon" /> </svg>

Safari doesn’t even support symbols files hosted on the same domain.

Can we not use a build tool that inlines the SVGs?

I couldn’t find an obvious way to fetch SVGs from a URL and inline them in common bundlers, like webpack and Grunt, although they exist for inlining SVG files stored locally. Even if a plugin that does this exists, setting up bundlers isn’t exactly straightforward. In fact, I often avoid using them until the project has reached acertain level of complexity. We must also realize that a majority of the internet is alien to things like webpack and React. Simple scripts can have a much wider appeal.

What about the <object> tag?

The <object> tag is a native way to include external SVG files that work across all the browsers.:

<object data="https://unpkg.com/mdi-svg@2.2.43/svg/access-point-network.svg" width="32" height="32"></object>

However, the drawback is we’re unable to customize the SVG’s attributes unless it’s hosted on the same domain (and the <object> tag doesn’t respect CORS headers). Even if the file is hosted on the same domain, we’d require JavaScript to manipulate the fill, like this:

<object data="https://unpkg.com/mdi-svg@2.2.43/svg/access-point-network.svg" width="32" height="32" onload="this.contentDocument.querySelector('svg').fill = 'red'"></object>

In short, using external SVG files this way makes it ultra-convenient to use icons and other SVG assets. As covered earlier, with unpkg, we can use any icon on GitHub without needing extra code. We can avoid creating a pipeline in a bundler to process SVG files or a component for every icon, and just host the icons on a CDN.

Loading SVG files this way packs a lot of benefits with very little cost.


The post svg-loader: A Different Way to Work With External SVG appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]

A Primer on the Different Types of Browser Storage

In back-end development, storage is a common part of the job. Application data is stored in databases, files in object storage, transient data in caches… there are seemingly endless possibilities for storing any sort of data(visit https://developer.couchbase.com/comparing-document-vs-relational/ and get all the details). But data storage isn’t limited only to the back end. The front end (the browser) is equipped with many options to store data as well, including a cloud. We can boost our application performance, save user preferences, keep the application state across multiple sessions, or even different computers, by utilizing this storage.

In this article, we will go through the different possibilities to store data in the browser. We will cover three use cases for each method to grasp the pros and cons. In the end, you will be able to decide what storage is the best fit for your use case. So let’s start!

The localStorage API

localStorage is one of the most popular storage options in the browser and the go-to for many developers. The data is stored across sessions, never shared with the server, and is available for all pages under the same protocol and domain. Storage is limited to ~5MB.

Surprisingly, the Google Chrome team doesn’t recommend using this option as it blocks the main thread and is not accessible to web workers and service workers. They launched an experiment, KV Storage, as a better version, but it was just a trial that doesn’t seem to have gone anywhere just yet.

The localStorage API is available as window.localStorage and can save only UTF-16 strings. We must make sure to convert data to strings before saving it into localStorage. The main three functions are:

  • setItem('key', 'value')
  • getItem('key')
  • removeItem('key')

They’re all synchronous, which makes it simple to work with, but they block the main thread.

It’s worth mentioning that localStorage has a twin called sessionStorage. The only difference is that data stored in sessionStorage will last only for the current session, but the API is the same.

Let’s see it in action. The first example demonstrates how to use localStorage for storing the user’s preferences. In our case, it’s a boolean property that turns on or off the dark theme of our site.

You can check the checkbox and refresh the page to see that the state is saved across sessions. Take a look at the save and load functions to see how I convert the value to string and how I parse it. It’s important to remember that we can store only strings.

This second example loads Pokémon names from the PokéAPI.

We send a GET request using fetch and list all the names in a ul element. Upon getting the response, we cache it in the localStorage so our next visit can be much faster or even work offline. We have to use JSON.stringify to convert the data to string and JSON.parse to read it from the cache.

In this last example, I demonstrate a use case where the user can browse through different Pokémon pages, and the current page is saved for the next visits.

The issue with localStorage, in this case, is that the state is saved locally. This behavior doesn’t allow us to share the desired page with our friends. Later, we will see how to overcome this issue.

We will use these three examples in the next storage options as well. I forked the Pens and just changed the relevant functions. The overall skeleton is the same for all methods.

The IndexedDB API

IndexedDB is a modern storage solution in the browser. It can store a significant amount of structured data — even files, and blobs. Like every database, IndexedDB indexes the data for running queries efficiently. It’s more complex to use IndexedDB. We have to create a database, tables, and use transactions.

Compared to localStorage , IndexedDB requires a lot more code. In the examples, I use the native API with a Promise wrapper, but I highly recommend using third-party libraries to help you out. My recommendation is localForage because it uses the same localStorage API but implements it in a progressive enhancement manner, meaning if your browser supports IndexedDB, it will use it; and if not, it will fall back to localStorage.

Let’s code, and head over to our user preferences example!

idb is the Promise wrapper that we use instead of working with a low-level events-based API. They’re almost identical, so don’t worry. The first thing to notice is that every access to the database is async, meaning we don’t block the main thread. Compared to localStorage, this is a major advantage.

We need to open a connection to our database so it will be available throughout the app for reading and writing. We give our database a name, my-db, a schema version, 1, and an update function to apply changes between versions. This is very similar to database migrations. Our database schema is simple: only one object store, preferences. An object store is the equivalent of an SQL table. To write or read from the database, we must use transactions. This is the tedious part of using IndexedDB. Have a look at the new save and load functions in the demo.

No doubt that IndexedDB has much more overhead and the learning curve is steeper compared to localStorage. For the key value cases, it might make more sense to use localStorage or a third-party library that will help us be more productive.

Application data, such as in our Pokémon example, is the forte of IndexedDB. You can store hundreds of megabytes and even more in this database. You can store all the Pokémon in IndexedDB and have them available offline and even indexed! This is definitely the one to choose for storing app data.

I skipped the implementation of the third example, as IndexedDB doesn’t introduce any difference in this case compared to localStorage. Even with IndexedDB, the user will still not share the selected page with others or bookmark it for future use. They’re both not the right fit for this use case.

Cookies

Using cookies is a unique storage option. It’s the only storage that is also shared with the server. Cookies are sent as part of every request. It can be when the user browses through pages in our app or when the user sends Ajax requests. This allows us to create a shared state between the client and the server, and also share state between multiple applications in different subdomains. This is not possible by other storage options that are described in this article. One caveat: cookies are sent with every request, which means that we have to keep our cookies small to maintain a decent request size.

The most common use for cookies is authentication, which is out of the scope of this article. Just like the localStorage, cookies can store only strings. The cookies are concatenated into one semicolon-separated string, and they are sent in the cookie header of the request. You can set many attributes for every cookie, such as expiration, allowed domains, allowed pages, and many more.

In the examples, I show how to manipulate the cookies through the client-side, but it’s also possible to change them in your server-side application.

Saving the user’s preferences in a cookie can be a good fit if the server can utilize it somehow. For example, in the theme use case, the server can deliver the relevant CSS file and reduce potential bundle size (in case we’re doing server-side-rendering). Another use case might be to share these preferences across multiple subdomain apps without a database.

Reading and writing cookies with JavaScript is not as straightforward as you might think. To save a new cookie, you need to set document.cookie — check out the save function in the example above. I set the dark_theme cookie and add it a max-age attribute to make sure it will not expire when the tab is closed. Also, I add the SameSite and Secure attributes. These are necessary because CodePen uses iframe to run the examples, but you will not need them in most cases. Reading a cookie requires parsing the cookie string.

A cookie string looks like this:

key1=value1;key2=value2;key3=value3

So, first, we have to split the string by semicolon. Now, we have an array of cookies in the form of key1=value1, so we need to find the right element in the array. In the end, we split by the equal sign and get the last element in the new array. A bit tedious, but once you implement the getCookie function (or copy it from my example :P) you can forget it.

Saving application data in a cookie can be a bad idea! It will drastically increase the request size and will reduce application performance. Also, the server cannot benefit from this information as it’s a stale version of the information it already has in its database. If you use cookies, make sure to keep them small.

The pagination example is also not a good fit for cookies, just like localStorage and IndexedDB. The current page is a temporary state that we would like to share with others, and any of these methods do not achieve it.

URL storage

URL is not a storage, per se, but it’s a great way to create a shareable state. In practice, it means adding query parameters to the current URL that can be used to recreate the current state. The best example would be search queries and filters. If we search the term flexbox on CSS-Tricks, the URL will be updated to https://css-tricks.com/?s=flexbox. See how easy it is to share a search query once we use the URL? Another advantage is that you can simply hit the refresh button to get newer results of your query or even bookmark it.

We can save only strings in the URL, and its maximum length is limited, so we don’t have so much space. We will have to keep our state small. No one likes long and intimidating URLs.

Again, CodePen uses iframe to run the examples, so you cannot see the URL actually changing. Worry not, because all the bits and pieces are there so you can use it wherever you want.

We can access the query string through window.location.search and, lucky us, it can be parsed using the URLSearchParams class. No need to apply any complex string parsing anymore. When we want to read the current value, we can use the get function. When we want to write, we can use set. It’s not enough to only set the value; we also need to update the URL. This can be done using history.pushState or history.replaceState, depending on the behavior we want to accomplish.

I wouldn’t recommend saving a user’s preferences in the URL as we will have to add this state to every URL the user visits, and we cannot guarantee it; for example, if the user clicks on a link from Google Search.

Just like cookies, we cannot save application data in the URL as we have minimal space. And even if we did manage to store it, the URL will be long and not inviting to click. Might look like a phishing attack of sorts.

Just like our pagination example, the temporary application state is the best fit for the URL query string. Again, you cannot see the URL changes, but the URL updates with the ?page=x query parameter every time you click on a page. When the web page loads, it looks for this query parameter and fetches the right page accordingly. Now we can share this URL with our friends so they can enjoy our favorite Pokémon.

Cache API

Cache API is a storage for the network level. It is used to cache network requests and their responses. The Cache API fits perfectly with service workers. A service worker can intercept every network request, and using the Cache API, it can easily cache both the requests. The service worker can also return an existing cache item as a network response instead of fetching it from the server. By doing so, you can reduce network load times and make your application work even when offline. Originally, it was created for service workers but in modern browsers the Cache API is available also in window, iframe, and worker contexts as-well. It’s a very powerful API that can improve drastically the application user experience.

Just like IndexedDB the Cache API storage is not limited and you can store hundreds of megabytes and even more if you need to. The API is asynchronous so it will not block your main thread. And it’s accessible through the global property caches.

To read more about the Cache API, the Google Chrome team has made a great tutorial.

Chris created an awesome Pen with a practical example of combining service workers and the Cache API.

Bonus: Browser extension

If you build a browser extension, you have another option to store your data. I discovered it while working on my extension, daily.dev. It’s available via chrome.storage or browser.storage, if you use Mozilla’s polyfill. Make sure to request a storage permission in your manifest to get access.

There are two types of storage options, local and sync. The local storage is self-explanatory; it means it isn’t shared and kept locally. The sync storage is synced as part of the Google account and anywhere you install the extension with the same account this storage will be synced. Pretty cool feature if you ask me. Both have the same API so it’s super easy to switch back-and-forth, if needed. It’s async storage so it doesn’t block the main thread like localStorage. Unfortunately, I cannot create a demo for this storage option as it requires a browser extension but it’s pretty simple to use, and almost like localStorage. For more information about the exact implementation, refer to Chrome docs.

Conclusion

The browser has many options we can utilize to store our data. Following the Chrome team’s advice, our go-to storage should be IndexedDB. It’s async storage with enough space to store anything we want. localStorage is not encouraged, but is easier to use than IndexedDB. Cookies are a great way to share the client state with the server but are mostly used for authentication.

If you want to create pages with a shareable state such as a search page, use the URL’s query string to store this information. Lastly, if you build an extension, make sure to read about chrome.storage.


The post A Primer on the Different Types of Browser Storage appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,
[Top]

WordPress-Powered Landing Pages on a Totally Different Site via Cloudflare Workers

What if you have some content on one site and want to display that content on another site? We can do this in the browser no problem. We can fetch it, and plunk it onto the page.

Ajax, right? Ugh. Now we’re in client-side rendered site territory, which isn’t great for performance, speed, or resiliency.

What if we could fetch that content and stitch it into the main page on the server side? Server side isn’t the right word for it though. What if we could do it at the global CDN level? Do it at the edge, as they say. That’s what we’ve been doing at CodePen, so we can build pages with the lovely WordPress block editor but serve them on our main site.

Direct Link to ArticlePermalink


The post WordPress-Powered Landing Pages on a Totally Different Site via Cloudflare Workers appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , , , ,
[Top]

Different Approaches to Responsive CSS Motion Path

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

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

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

Direct Link to ArticlePermalink

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

CSS-Tricks

, , , ,
[Top]

Different Favicon for Development

I bet a lot of us tend to have the production website and the development website up simultaneously a lot. It’s almost a developer cliché at this point to make some local change, refresh, refresh, refresh, refresh, and just not see the change, only to discover you were looking at the production website, not your local development website.

It’s just funny at first, but it’s frustrating if it happens a lot. It’s also totally avoidable by having an obvious visual¹ difference between the two sites. An easy way to do that? Different favicons.

There is no real trick to this. I literally just have a different favicon.ico file in development than I do in production. On this (WordPress) site, I only version control and deploy the wp-content folder, which is not the root of the site where the favicon lives. Any files at the root I have to manually SFTP in to change. I simply changed my local version, and there it sits, being all different.

Other trickery

Speaking of favicons…

This has me wondering what best practices for favicons are in 2020, at least for garden variety content websites like this.

I hate to say it, but I don’t really care about what the icon is when someone adds this site to the home screen on an iPad, ya know? Aside from one fellow who wanted a copy of the whole database to use the site offline to teach prisoners, nobody has ever asked me about “installing” CSS-Tricks.

Nor do I care about the tile color on a Windows 8 tablet or to customize the color of the browser chrome on Android. That kinda stuff tends to be part of the process when “generating” favicons.

I do care that the favicon looks good on high-resolution displays (a 32×32 graphic isn’t much of a splurge). I like the idea of SVG favicons. I like the idea of making sure dark mode is handled. I like the idea of doing this with as little code and files as possible. Anyone dig into this lately and want to enlighten me?

  1. “Visual” difference. Hm. I wonder what could be done for developers with visual impairments? Ideas?

The post Different Favicon for Development appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Same HTML, Different CSS

Ahmad Shadeed covers the idea of a card component that has a fixed set of semantic HTML with some BEMy classes on it. There is a title, author, image, and tags. Then he redesigns the card into five totally different designs without touching any of the HTML just the CSS.

If this is an ah-ha moment for you, awesome! It might be worth knowing that this exact concept essentially excited an entire generation of front-end developers, in no small part due to the concept of the CSS Zen Garden, where the entire website was a fixed set of HTML and only CSS changes birthed some incredible creativity.

Of course, we typically do get our hands into HTML when doing redesign work, but this is still a fun exercise that drives home the power of CSS. I wonder if JavaScript-powered components are what delivers this awe today, because they have a similar power of abstraction: make changes to a component and see the impact across an entire site. Only instead of the idea being rooted in constraint, there are no constraints.

Direct Link to ArticlePermalink

The post Same HTML, Different CSS appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]