Front End Web Development

DRY State Switching With CSS Variables: Fallbacks and Invalid Values

Css Tricks - Thu, 12/06/2018 - 5:25am

This is the second post in a two-part series that looks into the way CSS variables can be used to make the code for complex layouts and interactions less difficult to write and a lot easier to maintain. The first installment walks through various use cases where this technique applies. This post covers the use of fallbacks and invalid values to extend the technique to non-numeric values.

The strategy of using CSS Variables to drive the switching of layouts and interactions that we covered in the first post in this series comes with one major caveat: it only works with numeric values — lengths, percentages, angles, durations, frequencies, unit-less number values and so on. As a result, it can be really frustrating to know that you're able to switch the computed values of more than ten properties with a single CSS variable, but then you need to explicitly switch the non-numeric values of properties like flex-direction or text-align from row to column or from left to right or the other way around.

One example would be the one below, where the text-align property depends on parity and the flex-direction depends on whether we are viewing the front end in the wide screen scenario or not.

Screenshot collage.

I complained about this and got a very interesting suggestion in return that makes use of CSS variable fallbacks and invalid values. It was interesting and gives us something new to work with, so let's start with a short recap of what these are and go from there!

Fallback values

The fallback value of a CSS variable is the second and optional argument of the var() function. For example, let's consider we have some .box elements whose background is set to a variable of --c:

.box { background: var(--c, #ccc) }

If we haven't explicitly specified a value for the --c variable elsewhere, then the fallback value #ccc is used.

Now let's say some of these boxes have a class of .special. Here, we can specify --c as being some kind of orange:

.special { --c: #f90 }

This way, the boxes with this .special class have an orange background, while the others use the light grey fallback.

See the Pen by thebabydino (@thebabydino) on CodePen.

There are a few things to note here.

First off, the fallback can be another CSS variable, which can have a CSS variable fallback itself and... we can fall down a really deep rabbit hole this way!

background: var(--c, var(--c0, var(--c1, var(--c2, var(--c3, var(--c4, #ccc))))))

Secondly, a comma separated list is a perfectly valid fallback value. In fact, everything specified after the first comma inside the var() function constitutes the fallback value, as seen in the example below:

background: linear-gradient(90deg, var(--stop-list, #ccc, #f90))

See the Pen by thebabydino (@thebabydino) on CodePen.

And last, but certainly not least, we can have different fallback values for the same variable used in different places, as illustrated by this example:

$highlight: #f90; a { border: solid 2px var(--c, #{rgba($highlight, 0)}) color: var(--c, #ccc); &:hover, &:focus { --c: #{$highlight} } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Invalid values

First off, I want to clarify what I mean by this. "Invalid values" is shorter and easier to remember, but what it really refers to any value that makes a declaration invalid at computed value time.

For example, consider the following piece of code:

--c: 1em; background: var(--c)

1em is a valid length value, but this is not a valid value for the background-color property, so here this property will take its initial value (which is transparent) instead.

Putting it all together

Let's say we have a bunch of paragraphs where we change the lightness of the color value to switch between black and white based on parity (as explained in the previous post in this series):

p { --i: 0; /* for --i: 0 (odd), the lightness is 0*100% = 0% (black) * for --i: 1 (even), the lightness is 1*100% = 100% (white)* / color: hsl(0, 0%, calc(var(--i)*100%)); &:nth-child(2n) { --i: 1 } }

We also want the odd paragraphs to be right-aligned, while keeping the even ones left-aligned. In order to achieve this, we introduce a --parity variable which we don't set explicitly in the general case — only for even items. What we do set in the general case is our previous variable, --i. We set it to the value of --parity with a fallback of 0:

p { --i: var(--parity, 0); color: hsl(0, 0%, calc(var(--i)*100%)); &:nth-child(2n) { --parity: 1 } }

So far, this achieves exactly the same as the previous version of our code. However, if we take advantage of the fact that, we can use different fallback values in different places for the same variable, then we can also set text-align to the value of --parity using a fallback of... right!

text-align: var(--parity, right)

In the general case, where we're not setting --parity explicitly; text-align uses the fallback right, which is a valid value, so we have right alignment. For the even items however, we're setting --parity explicitly to 1, which is not a valid value for text-align. That means text-align reverts to its initial value, which is left.

See the Pen by thebabydino (@thebabydino) on CodePen.

Now we have right alignment for the odd items and left alignment for the even items while still putting a single CSS variable to use!

Dissecting a more complex example

Let's consider we want to get the result below:

Numbered cards where even cards have symmetrical styles with respect to odd cards.

We create these cards with a paragraph element <p> for each one. We switch their box-sizing to border-box, then give them a width, a max-width, a padding and a margin. We also change the default font.

See the Pen by thebabydino (@thebabydino) on CodePen.

We've also added a dummy outline just to see the boundaries of these elements.

Next, let's add the numbering using CSS counters and a :before pseudo-element:

p { /* same code as before */ counter-increment: c; &:before { content: counter(c, decimal-leading-zero) } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now, we'll give our paragraphs a flex layout and increase the size of the numbering:

p { /* same code as before */ display: flex; align-items: center; &:before { font-size: 2em; content: counter(c, decimal-leading-zero); } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now comes the interesting part!

We set a switch --i that changes value with the parity — it's 0 for the odd items and 1 for the even ones.

p { /* same code as before */ --i: 0; &:nth-child(2n) { --i: 1 } }

Next, we want the numbering to be on the left for the odd items and on the right for the even ones. We achieve this via the order property. The initial value for this property is 0, for both the :before pseudo-element and the paragraph's text content. If we set this order property to 1 for the numbering (the :before pseudo-element) of the even elements, then this moves the numbering after the content.

p { /* same code as before */ --i: 0; &:before { /* same code as before */ /* we don't really need to set order explicitly as 0 is the initial value */ order: 0; } &:nth-child(2n) { --i: 1; &:before { order: 1 } } }

You may notice that, in this case, the order value is the same as the switch --i value, so in order to simplify things, we set the order to the switch value.

p { /* same code as before */ --i: 0; &:before { /* same code as before */ order: var(--i) } &:nth-child(2n) { --i: 1 } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now we want a bit of spacing (let's say $gap) in between the numbers and the paragraph text. This can be achieved with a lateral margin on the :before.

For the odd items, the item numbers are on the left, so we need a non-zero margin-right. For the even items, the item numbers are on the right, so we need a non-zero margin-left.

When the parity switch value is 0 for the odd items, the left margin is 0 = 0*$gap, while the right margin is $gap = 1*$gap = (1 - 0)*$gap.

Similarly for the even items, when the parity switch value is 1, the left margin is $gap = 1*$gap, while the right margin is 0 = 0*$gap = (1 - 1)*$gap.

The result in both cases is that margin-left is the parity switch value times the margin value ($gap), while margin-right is 1 minus the parity switch value, all multiplied with the margin value.

$gap: .75em; p { /* same code as before */ --i: 0; &:before { /* same code as before */ margin: 0 /* top */ calc((1 - var(--i))*#{$gap}) /* right */ 0 /* bottom */ calc(var(--i)*#{$gap}) /* left */; } &:nth-child(2n) { --i: 1 } }

If we use the complementary value (1 - var(--i)) in more than one place, then it's probably best to set it to another CSS variable --j.

$gap: .75em; p { /* same code as before */ --i: 0; --j: calc(1 - var(--i)); &:before { /* same code as before */ margin: 0 /* top */ calc(var(--j)*#{$gap}) /* right */ 0 /* bottom */ calc(var(--i)*#{$gap}) /* left */; } &:nth-child(2n) { --i: 1 } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we want to give these items a proper background. This is a grey to orange gradient, going from left to right (or along a 90deg angle) in the case of odd items (parity switch --i: 0) and from right to left (at a -90deg angle) in the case of even items (parity switch --i: 1).

This means the absolute value of the gradient angle is the same (90deg), only the sign is different — it's +1 for the odd items (--i: 0) and -1 for the even items (--i: 1).

In order to switch the sign, we use the approach we covered in the first post:

/* * for --i: 0, we have 1 - 2*0 = 1 - 0 = +1 * for --i: 1, we have 1 - 2*1 = 1 - 2 = -1 */ --s: calc(1 - 2*var(--i))

This way, our code becomes:

p { /* same code as before */ --i: 0; --s: calc(1 - 2*var(--i)); background: linear-gradient(calc(var(--s)*90deg), #ccc, #f90); &:nth-child(2n) { --i: 1 } }

We can also remove the dummy outline since we don't need it at this point:

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we do something similar for the transform property.

The odd items are translated a bit to the right (in the positive direction of the x axis) and rotated a bit in the clockwise (positive) direction, while the even items are translated a bit to the left (in the negative direction of the x axis) and rotated a bit in the other (negative) direction.

The translation and rotation amounts are the same; only the signs differ.

For the odd items, the transform chain is:

translate(10%) rotate(5deg)

While for the even items, we have:

translate(-10%) rotate(-5deg)

Using our sign --s variable, the unified code is:

p { /* same code as before */ --i: 0; --s: calc(1 - 2*var(--i)); transform: translate(calc(var(--s)*10%)) rotate(calc(var(--s)*5deg)); &:nth-child(2n) { --i: 1 } }

This is now starting to look like something!

See the Pen by thebabydino (@thebabydino) on CodePen.

The next step is to round the card corners. For the odd cards, we want the corners on the left side to be rounded to a radius of half the height. For the even items, we want the corners on the right side to be rounded to the same radius.

Given we don't know the heights of our cards, we just use a ridiculously large value, say something like 50vh, which gets scaled down to fit due to the way border-radius works. In our case, this means scaled down to whichever is smaller between half the item height (since going vertically has both a top and bottom rounded corner on the same side) and the full item width (since going horizontally has one rounded corner; either on the left or on the right, but not on both the right and the left).

This means we want the corners on the left to have this radius ($r: 50vh) for odd items (--i: 0) and the ones on the right to have the same radius for even items (--i: 1). As a result, we do something pretty similar to the numbering margin case:

$r: 50vh; p { /* same code as before */ --i: 0; --j: calc(1 - var(--i)); --r0: calc(var(--j)*#{$r}); --r1: calc(var(--i)*#{$r}); /* clockwise from the top left */ border-radius: var(--r0) /* top left */ var(--r1) /* top right */ var(--r1) /* bottom right */ var(--r0) /* bottom left */; &:nth-child(2n) { --i: 1 } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Now comes the truly interesting part — text alignment! We want the text in the odd items to be aligned right, while the text in the even items is aligned left. The only problem is that text-align doesn't take a number value so, no addition or multiplication tricks can help us here.

What can help is combining the use of fallback and invalid values for CSS variables. To do this, we introduce another parity variable --p and it's this variable that we actually set to 1 for even items. Unlike --i before, we never set --p explicitly for the general case as we want different fallback values of this variable to be used for different properties.

As for --i, we set it to --p with a fallback value of 0. This fallback value of 0 is the value that actually gets used in the general case, since we never explicitly set --p there. For the even case, where we explicitly set --p to 1, --i becomes 1 as well.

At the same time, we set the text-align property to --p with a fallback value of right in the general case. In the even case, where we have --p explicitly set to 1, the text-align value becomes invalid (because we have set text-align to the value of --p and --p is now 1, which is not a valid value for text-align), so the text reverts to being aligned to the left.

p { /* same code as before */ --i: var(--p, 0); text-align: var(--p, right); &:nth-child(2n) { --p: 1 } }

This gives us the result we've been after:

See the Pen by thebabydino (@thebabydino) on CodePen.

Handling responsiveness

While our cards example looks great on wider screens, the same can't be said when shrink things down.

The wide screen result (left) vs. the narrow screen result (right)

In order to fix this, we introduce two more custom properties, --wide and --k to switch between the wide and narrow cases. We set --k to --wide with a fallback value of 0 in the general case and then set --wide to 1 if the viewport width is anything 340px and up.

p { /* same code as before */ --k: var(--wide, 0); @media (min-width: 340px) { --wide: 1 } }

Since we only want our items to be transformed and have rounded corners in the wide case, we multiply the translation, rotation and radius values by --k (which is 0, unless the viewport is wide, which switches its value to 1).

p { /* same code as before */ --k: var(--wide, 0); --r0: calc(var(--k)*var(--j)*#{$r}); --r1: calc(var(--k)*var(--i)*#{$r}); border-radius: var(--r0) /* top left */ var(--r1) /* top right */ var(--r1) /* bottom right */ var(--r0) /* bottom left */; transform: translate(calc(var(--k)*var(--s)*10%)) rotate(calc(var(--k)*var(--s)*5deg)); @media (min-width: 340px) { --wide: 1 } }

This is slightly better, but our content still overflows in narrow viewports. We can fix this by only placing the numbering (the :before pseudo-element) on the left or right side only in the wide case then moving it above the card in the narrow case.

In order to do this, we multiply both its order and its lateral margin values by --k (which is 1 in the wide case and 0 otherwise).

We also set flex-direction to --wide with a fallback value of column.

This means the flex-direction value is column in the general case (since we haven't set --wide explicitly elsewhere). However, if the viewport is wide (min-width: 340px), then our --wide variable gets set to 1. But 1 is an invalid value for flex-direction, so this property reverts back to its initial value of row.

p { /* same code as before */ --k: var(--wide, 0); flex-direction: var(--wide, column); &:before { /* same code as before */ order: calc(var(--k)*var(--i)); margin: 0 /* top */ calc(var(--k)*var(--j)*#{$gap}) /* right */ 0 /* bottom */ calc(var(--k)*var(--i)*#{$gap}) /* left */; } @media (min-width: 340px) { --wide: 1 } }

Coupled with setting a min-width of 160px on the body, we've now eliminated the overflow issue:

Responsive cards, no overflow (live demo).

One more thing we can do is tweak the font-size so that it also depends on --k:

p { /* same code as before */ --k: var(--wide, 0); font: 900 calc(var(--k)*.5em + .75em) cursive; @media (min-width: 340px) { --wide: 1 } }

And that's it, our demo is now nicely responsive!

Responsive cards, font smaller for narrow screens and with no overflow (live demo). A few more quick examples!

Let's look at a few more demos that use the same technique, but quickly without building them from scratch. We'll merely go through the basic ideas behind them.

Disc slices Sliced disc (live demo).

Just like the cards example we completed together, we can use a :before pseudo-element for the numbering and a flex layout on the paragraphs. The sliced disc effect is achieved using clip-path.

The paragraph elements themselves — the horizontal offsets, the position and intensity of the radial-gradient() creating the shadow effect, the direction of the linear-gradient() and the saturation of its stops, the color and the text alignment — all depend on the --parity variable.

p { /* other styles not relevant here */ --p: var(--parity, 1); --q: calc(1 - var(--p)); --s: calc(1 - 2*var(--p)); /* sign depending on parity */ transform: translate((calc(var(--i)*var(--s)*#{-$x}))); background: radial-gradient(at calc(var(--q)*100%) 0, rgba(0, 0, 0, calc(.5 + var(--p)*.5)), transparent 63%) calc(var(--q)*100%) 0/ 65% 65% no-repeat, linear-gradient(calc(var(--s)*-90deg), hsl(23, calc(var(--q)*98%), calc(27% + var(--q)*20%)), hsl(44, calc(var(--q)*92%), 52%)); color: HSL(0, 0%, calc(var(--p)*100%)); text-align: var(--parity, right); &:nth-child(odd) { --parity: 0 } }

For the numbering (the :before pseudo-elements of the paragraphs), we have that both the margin and the order depend on the --parity in the exact same way as the cards example.

If the viewport width is smaller than the disc diameter $d plus twice the horizontal slice offset in absolute value $x, then we're not in the --wide case anymore. This affects the width, padding and margin of our paragraphs, as well as their horizontal offset and their shape (because we don't clip them to get the sliced disc effect at that point).

body { /* other styles not relevant here */ --i: var(--wide, 1); --j: calc(1 - var(--i)); @media (max-width: $d + 2*$x) { --wide: 0 } } p { /* other styles not relevant here */ margin: calc(var(--j)*.25em) 0; padding: calc(var(--i)*#{.5*$r}/var(--n) + var(--j)*5vw) /* vertical */ calc(var(--i)*#{.5*$r} + var(--j)*2vw) /* horizontal */; width: calc(var(--i)*#{$d} /* wide */ + var(--j)*100% /* not wide */); transform: translate((calc(var(--i)*var(--s)*#{-$x}))); clip-path: var(--wide, /* fallback, used in the wide case only */ circle($r at 50% calc((.5*var(--n) - var(--idx))*#{$d}/var(--n)))); }

We're in the narrow case below 270px and have a flex-direction of column on our paragraphs. We also zero out both the lateral margins and the order for the numbering.

body { /* other styles not relevant here */ --k: calc(1 - var(--narr, 1)); @media (min-width: 270px) { --narr: 0 } } p { /* other styles not relevant here */ flex-direction: var(--narr, column); &:before { /* other styles not relevant here */ margin: 0 /* top */ calc(var(--k)*var(--q)*.25em) /* right */ 0 /* bottom */ calc(var(--k)*var(--p)*.25em) /* left */; order: calc(var(--k)*var(--p)); } } Four-step infographic A four-step infographic (live demo).

This works pretty much the same as the previous two examples. We have a flex layout on our paragraphs using a column direction in the narrow case. We also have a smaller font-size in that same case:

body { /* other styles not relevant here */ --k: var(--narr, 1); @media (min-width: 400px) { --narr: 0 } } p { /* other styles not relevant here */ flex-direction: var(--narr, column); font-size: calc((1.25 - .375*var(--k))*1em); }

The parity determines each paragraph's text alignment, which lateral border gets a non-zero value, and the position and direction of the border gradient. Both the parity and whether we're in the wide screen case or not determine the lateral margins and paddings.

body { /* other styles not relevant here */ --i: var(--wide, 1); --j: calc(1 - var(--i)); @media (max-width: $bar-w + .5*$bar-h) { --wide: 0 } } p { /* other styles not relevant here */ margin: .5em /* top */ calc(var(--i)*var(--p)*#{.5*$bar-h}) /* right */ 0 /* bottom */ calc(var(--i)*var(--q)*#{.5*$bar-h}) /* left */; border-width: 0 /* top */ calc(var(--q)*#{$bar-b}) /* right */ 0 /* bottom */ calc(var(--p)*#{$bar-b}) /* left */; padding: $bar-p /* top */ calc((var(--j) + var(--i)*var(--q))*#{$bar-p}) /* right */ $bar-p /* bottom */ calc((var(--j) + var(--i)*var(--p))*#{$bar-p}) /* left */; background: linear-gradient(#fcfcfc, gainsboro) padding-box, linear-gradient(calc(var(--s)*90deg), var(--c0), var(--c1)) calc(var(--q)*100%) /* background-position */ / #{$bar-b} 100% /* background-size */; text-align: var(--parity, right); }

The icon is created using the :before pseudo-element, and its order depends on the parity, but only if we're not in the narrow screen scenario — in which case it's always before the actual text content of the paragraph. Its lateral margin depends both on the parity and whether we are in the wide screen case or not. The big-valued component that positions it half out of its parent paragraph is only present in the wide screen case. The font-size also depends on whether we're in the narrow screen case or not (and this influences its em dimensions and padding).

order: calc((1 - var(--k))*var(--p)); margin: 0 /* top */ calc(var(--i)*var(--p)*#{-.5*$ico-d} + var(--q)*#{$bar-p}) /* right */ 0 /* bottom */ calc(var(--i)*var(--q)*#{-.5*$ico-d} + var(--p)*#{$bar-p}) /* left */; font-size: calc(#{$ico-s}/(1 + var(--k)));

The ring is created using an absolutely positioned :after pseudo-element (and its placement depends on parity), but only for the wide screen case.

content: var(--wide, ''); The two-dimension case Screenshot collage (live demo, no Edge support due to CSS variable and calc() bugs).

Here we have a bunch of article elements, each containing a heading. Let's check out the most interesting aspects of how this responsive layout works!

On each article, we have a two-dimensional layout (grid) — but only if we're not in the narrow screen scenario (--narr: 1), in which case we fall back on the normal document flow with the numbering created using a :before pseudo-element, followed by the heading, followed by the actual text. In this situation, we also add vertical padding on the heading since we don't have the grid gaps anymore and we don't want things to get too crammed.

html { --k: var(--narr, 0); @media (max-width: 250px) { --narr: 1 } } article { /* other styles irrelevant here */ display: var(--narr, grid); } h3 { /* other styles irrelevant here */ padding: calc(var(--k)*#{$hd3-p-narr}) 0; }

For the grid, we create two columns of widths depending both on parity and on whether we're in the wide screen scenario. We make the numbering (the :before pseudo-element) span two rows in the wide screen case, either on the second column or the first, depending on the parity. If we're not in the wide screen case, then the paragraph spans both columns on the second row.

We set the grid-auto-flow to column dense in the wide screen scenario, letting it revert to the initial value of row otherwise. Since our article elements are wider than the combined widths of the columns and the column gap between them, we use place-content to position the actual grid columns inside at the right or left end depending on parity.

Finally, we place the heading at the end or start of the column, depending on parity, and we as well as the paragraph's text alignment if we're in the wide screen scenario.

$col-1-wide: calc(var(--q)*#{$col-a-wide} + var(--p)*#{$col-b-wide}); $col-2-wide: calc(var(--p)*#{$col-a-wide} + var(--q)*#{$col-b-wide}); $col-1-norm: calc(var(--q)*#{$col-a-norm} + var(--p)*#{$col-b-norm}); $col-2-norm: calc(var(--p)*#{$col-a-norm} + var(--q)*#{$col-b-norm}); $col-1: calc(var(--i)*#{$col-1-wide} + var(--j)*#{$col-1-norm}); $col-2: calc(var(--i)*#{$col-2-wide} + var(--j)*#{$col-2-norm}); html { --i: var(--wide, 1); --j: calc(1 - var(--i)); @media (max-width: $art-w-wide) { --wide: 0 } } article { /* other styles irrelevant here */ --p: var(--parity, 1); --q: calc(1 - var(--p)); grid-template-columns: #{$col-1} #{$col-2}; grid-auto-flow: var(--wide, dense column); place-content: var(--parity, center end); &:before { /* other styles irrelevant here */ grid-row: 1/ span calc(1 + var(--i)); grid-column: calc(1 + var(--p))/ span 1; } &:nth-child(odd) { --parity: 0 } } h3 { /* other styles irrelevant here */ justify-self: var(--parity, self-end); } p { grid-column-end: span calc(1 + var(--j)); text-align: var(--wide, var(--parity, right)); }

We also have numerical values such as grid gaps, border radii, paddings, font-sizes, gradient directions, rotation and translation directions depending on the parity and/or whether we're in the wide screen scenario or not.

Even more examples!

If you want more of this, I've created an entire collection of similar responsive demos for you to enjoy!

Collection of responsive demos.

The post DRY State Switching With CSS Variables: Fallbacks and Invalid Values appeared first on CSS-Tricks.

CSS Selectors are Conditional Statements

Css Tricks - Thu, 12/06/2018 - 5:22am
.foo { }

Programmatically, is:

if (element has a class name of "foo") { }

Descendent selectors are && logic and commas are ||. It just gets more complicated from there, with things like combinators and pseudo selectors. Just look at all the ways styles can cascade.

Jeremy Keith:

If you find you can’t select something in the CSS, that’s a sign that you probably need to add another class name to the HTML. The complexity is confined to the markup in order to keep the CSS more straightforward, modular, and maintainable.

Direct Link to ArticlePermalink

The post CSS Selectors are Conditional Statements appeared first on CSS-Tricks.

A Visual, Intuitive Approach to Project Management

Css Tricks - Thu, 12/06/2018 - 5:20am

(This is a sponsored post.)

You know how valuable project management is for teams of any size. Whether you're a small shop or full-blown agency, your clients and projects depend on tracked deliverables, solid communication, and a clear breakdown of the work that's needed.

You may have a love/hate relationship with whatever project management platform you're using or have used in the past. It's common for a platform to be missing that one feature you really need that would make the tool so much better and your work that much easier. But you can't throw the baby out with the bathwater and ditch project management altogether, right?

That's where monday.com comes in.

What separates monday.com from any other project management tool is the way it humanizes your team and connects each person to the project, rather than treating folks like resources. It sets up a workflow that encourages collaboration and transparency so that no one is left out of the process, every one has a voice, and each person is truly contributing to the big picture and greater good of a project.

Speaking of collaboration and transparency, you can upload your project files directly into monday.com so everyone has easy access to the assets they need to get work done. And, if you're already using other services like Slack, Google Calendar, Dropbox, Microsoft Excel, Trello, and Jira, there are clean shortcuts to integrate them right in without hassle. Imagine that — one place for everything!

Oh, and you can use monday.com for client-facing exchanges. That means all your messages are consolidated into a single place so that nothing slips through the cracks. No more digging for some lost email or using Reply All on an email chain. It's all right there so you, your team and your clients are all on the same page.

There's a whole lot more monday.com can do, from task lists and timelines to news feeds and financial reporting, but the real proof comes in using it yourself. Try monday.com for free with no risk and zero commitment. You'll be glad you did.

Get Started

Direct Link to ArticlePermalink

The post A Visual, Intuitive Approach to Project Management appeared first on CSS-Tricks.

The Software We Pay For

Css Tricks - Wed, 12/05/2018 - 11:07am

We did a Web Developer Economics series a few years ago, where we looked at the various costs of being a web developer:

  1. Web Developer Economics: One-Off Software Costs
  2. Web Developer Economics: Hardware Costs
  3. Web Developer Economics: Monthly Service Costs
  4. Web Developer Economics: The Wrapup

I'm sure some of that software and hardware has changed since then, but the spirit is the same. It costs money to have the things you need to do this job.

I just wrote a similar article, but from the perspective of a business paying for the software it needs. That link is to Medium, but it was posted on the CodePen blog originally. Just giving that a shot to see what all the hubbub about Medium is about.

Direct Link to ArticlePermalink

The post The Software We Pay For appeared first on CSS-Tricks.

DRY Switching with CSS Variables: The Difference of One Declaration

Css Tricks - Wed, 12/05/2018 - 4:38am

This is the first post of a two-part series that looks into the way CSS variables can be used to make the code for complex layouts and interactions less difficult to write and a lot easier to maintain. This first installment walks through various use cases where this technique applies. The second post covers the use of fallbacks and invalid values to extend the technique to non-numeric values.

What if I told you a single CSS declaration makes the difference in the following image between the wide screen case (left) and the second one (right)? And what if I told you a single CSS declaration makes the difference between the odd and even items in the wide screen case?

Screenshot collage.

Or that a single CSS declaration makes the difference between the collapsed and expanded cases below?

Expanding search.

How is that even possible?

Well, as you may have guessed from the title, it's all in the power of CSS variables.

There are already plenty of articles out there on what CSS variables are and how to get started with them, so we won't be getting into that here.

Instead, we'll dive straight into why CSS variables are useful for achieving these cases and others, then we'll move on to a detailed explanation of the how for various cases. We'll code an actual example from scratch, step by step, and, finally, you'll be getting some eye candy in the form of a few more demos that use the same technique.

So let's get started!

Why CSS variables are useful

For me, the best thing about CSS variables is that they've opened the door for styling things in a logical, mathematical and effortless way.

One example of this is the CSS variable version of the yin and yang loader I coded last year. For this version, we create the two halves with the two pseudo-elements of the loader element.

Rotating ☯ symbol, with its two lobes increasing and decreasing in size.

We use the same background, border-color, transform-origin and animation-delay values for the two halves. These values all depend on a switch variable --i that's initially set to 0 on both halves (the pseudo-elements), but then we change it to 1 for the second half (the :after pseudo-element), thus dynamically modifying the computed values of all these properties.

Without CSS variables, we'd have to set all these properties (border-color, transform-origin, background, animation-delay) again on the :after pseudo-element and risk making some typo or even forgetting to set some of them.

How switching works in the general case Switching between a zero and a non-zero value

In the particular case of the yin and yang loader, all the properties we change between the two halves (pseudo-elements) go from a zero value for one state of the switch and a non-zero value for the other state.

If we want our value to be zero when the switch is off (--i: 0) and non-zero when the switch is on (--i: 1), then we multiply it with the switch value (var(--i)). This way, if our non-zero value should be, let's say an angular value of 30deg, we have:

  • when the switch is off (--i: 0), calc(var(--i)*30deg) computes to 0*30deg = 0deg
  • when the switch is on (--i: 1), calc(var(--i)*30deg) computes to 1*30deg = 30deg

However, if we want our value to be non-zero when the switch is off (--i: 0) and zero when the switch is on (--i: 1), then we multiply it with the complementary of the switch value (1 - var(--i)). This way, for the same non-zero angular value of 30deg, we have:

  • when the switch is off (--i: 0), calc((1 - var(--i))*30deg) computes to (1 - 0)*30deg = 1*30deg = 30deg
  • when the switch is on (--i: 1), calc((1 - var(--i))*30deg) computes to (1 - 1)*30deg = 0*30deg = 0deg

You can see this concept illustrated below:

Switching between a zero and a non-zero value (live demo, no Edge support due to calc() not working for angle values)

For the particular case of the loader, we use HSL values for border-color and background-color. HSL stands for hue, saturation, lightness and can be best represented visually with the help of a bicone (which is made up of two cones with the bases glued together).

HSL bicone.

The hues go around the bicone, 0° being equivalent to 360° to give us a red in both cases.

Hue wheel.

The saturation goes from 0% on the vertical axis of the bicone to 100% on the bicone surface. When the saturation is 0% (on the vertical axis of the bicone), the hue doesn't matter anymore; we get the exact same grey for all hues in the same horizontal plane.

The "same horizontal plane" means having the same lightness, which increases along the vertical bicone axis, going from 0% at the black bicone vertex to 100% at the white bicone vertex. When the lightness is either 0% or 100%, neither the hue nor the saturation matter anymore - we always get black for a lightness value of 0% and white for a lightness value of 100%.

Since we only need black and white for our ☯ symbol, the hue and saturation are irrelevant, so we zero them and then switch between black and white by switching the lightness between 0% and 100%.

.yin-yang { /* other styles that are irrelevant here */ &:before, &:after { /* other styles that are irrelevant here */ --i: 0; /* lightness of border-color when * --i: 0 is (1 - 0)*100% = 1*100% = 100% (white) * --i: 1 is (1 - 1)*100% = 0*100% = 0% (black) */ border: solid $d/6 hsl(0, 0%, calc((1 - var(--i))*100%)); /* x coordinate of transform-origin when * --i: 0 is 0*100% = 0% (left) * --i: 1 is 1*100% = 100% (right) */ transform-origin: calc(var(--i)*100%) 50%; /* lightness of background-color when * --i: 0 is 0*100% = 0% (black) * --i: 1 is 1*100% = 100% (white) */ background: hsl(0, 0%, calc(var(--i)*100%)); /* animation-delay when * --i: 0 is 0*-$t = 0s * --i: 1 is 1*-$t = -$t */ animation: s $t ease-in-out calc(var(--i)*#{-$t}) infinite alternate; } &:after { --i: 1 } }

Note that this approach doesn't work in Edge due to the fact that Edge doesn't support calc() values for animation-delay.

But what if we want to have a non-zero value when the switch is off (--i: 0) and another different non-zero value when the switch is on (--i: 1)?

Switching between two non-zero values

Let's say we want an element to have a grey background (#ccc) when the switch is off (--i: 0) and an orange background (#f90) when the switch is on (--i: 1).

The first thing we do is switch from hex to a more manageable format such as rgb() or hsl().

We could do this manually either by using a tool such as Lea Verou's CSS Colors or via DevTools. If we have a background set on an element we can cycle through formats by keeping the Shift key pressed while clicking on the square (or circle) in front of the value in DevTools. This works in both Chrome and Firefox, though it doesn't appear to work in Edge.

value."/>Changing the format from DevTools.

Even better, if we're using Sass, we can extract the components with red()/ green()/ blue() or hue()/ saturation()/ lightness() functions.

While rgb() may be the better known format, I tend to prefer hsl() because I find it more intuitive and it's easier for me to get an idea about what to expect visually just by looking at the code.

So we extract the three components of the hsl() equivalents of our two values ($c0: #ccc when the switch is off and $c1: #f90 when the switch is on) using these functions:

$c0: #ccc; $c1: #f90; $h0: round(hue($c0)/1deg); $s0: round(saturation($c0)); $l0: round(lightness($c0)); $h1: round(hue($c1)/1deg); $s1: round(saturation($c1)); $l1: round(lightness($c1))

Note that we've rounded the results of the hue(), saturation() and lightness() functions as they may return a lot of decimals and we want to keep our generated code clean. We've also divided the result of the hue() function by 1deg, as the returned value is a degree value in this case and Edge only supports unit-less values inside the CSS hsl() function. Normally, when using Sass, we can have degree values, not just unit-less ones for the hue inside the hsl() function because Sass treats it as the Sass hsl() function, which gets compiled into a CSS hsl() function with a unit-less hue. But here, we have a dynamic CSS variable inside, so Sass treats this function as the CSS hsl() function that doesn't get compiled into anything else, so, if the hue has a unit, this doesn't get removed from the generated CSS.

Now we have that:

  • if the switch is off (--i: 0), our background is
    hsl($h0, $s0, $l0)
  • if the switch is on (--i: 1), our background is
    hsl($h1, $s1, $l1)

We can write our two backgrounds as:

  • if the switch is off (--i: 0),
    hsl(1*$h0 + 0*$h1, 1*$s0 + 0*$s1, 1*$l0 + 1*$l1)
  • if the switch is on (--i: 1),
    hsl(0*$h0 + 1*$h1, 0*$s0 + 1*$s1, 0*$l0 + 1*$l1)

Using the switch variable --i, we can unify the two cases:

--j: calc(1 - var(--i)); background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), calc(var(--j)*#{$s0} + var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1}))

Here, we've denoted by --j the complementary value of --i (when --i is 0, --j is 1 and when --i is 1, --j is 0).

Switching between two backgrounds (live demo)

The formula above works for switching in between any two HSL values. However, in this particular case, we can simplify it because we have a pure grey when the switch is off (--i: 0).

Purely grey values have equal red, green and blue values when taking into account the RGB model.

When taking into account the HSL model, the hue is irrelevant (our grey looks the same for all hues), the saturation is always 0% and only the lightness matters, determining how light or dark our grey is.

In this situation, we can always keep the hue of the non-grey value (the one we have for the "on" case, $h1).

Since the saturation of any grey value (the one we have for the "off" case, $s0) is always 0%, multiplying it with either 0 or 1 always gives us 0%. So, given the var(--j)*#{$s0} term in our formula is always 0%, we can just ditch it and our saturation formula reduces to the product between the saturation of the "on" case $s1 and the switch variable --i.

This leaves the lightness as the only component where we still need to apply the full formula.

--j: calc(1 - var(--i)); background: hsl($h1, calc(var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{d1l}))

The above can be tested in this demo.

Similarly, let's say we want the font-size of some text to be 2rem when our switch is off (--i: 0) and 10vw when the switch is on (--i: 1). Applying the same method, we have:

font-size: calc((1 - var(--i))*2rem + var(--i)*10vw) Switching between two font sizes (live demo)

Alright, let's now move on to clearing another aspect of this: what is it exactly that causes the switch to flip from on to off or the other way around?

What triggers switching

We have a few options here.

Element-based switching

This means the switch is off for certain elements and on for other elements. For example, this can be determined by parity. Let's say we want all the even elements to be rotated and have an orange background instead of the initial grey one.

.box { --i: 0; --j: calc(1 - var(--i)); transform: rotate(calc(var(--i)*30deg)); background: hsl($h1, calc(var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1})); &:nth-child(2n) { --i: 1 } } Switching triggered by item parity (live demo, not fully functional in Edge due to calc() not working for angle values)

In the parity case, we flip the switch on for every second item (:nth-child(2n)), but we can also flip it on for every seventh item (:nth-child(7n)), for the first two items (:nth-child(-n + 2)), for all items except the first and last two (:nth-child(n + 3):nth-last-child(n + 3)). We can also flip it on just for headings or just for elements that have a certain attribute.

State-based switching

This means the switch is off when the element itself (or a parent or one of its previous siblings) is in one state and off when it's another state. In the interactive examples from the previous section, the switch was flipped when a checkbox before our element got checked or unchecked.

We can also have something like a white link that scales up and turns orange when focused or hovered:

$c: #f90; $h: round(hue($c)/1deg); $s: round(saturation($c)); $l: round(lightness($c)); a { --i: 0; transform: scale(calc(1 + var(--i)*.25)); color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); &:focus, &:hover { --i: 1 } }

Since white is any hsl() value with a lightness of 100% (the hue and saturation are irrelevant), we can simplify things by always keeping the hue and saturation of the :focus/ :hover state and only changing the lightness.

Switching triggered by state change (live demo, not fully functional in Edge due to calc() values not being supported inside scale() functions) Media query-based switching

Another possibility is that switching is triggered by a media query, for example, when the orientation changes or when going from one viewport range to another.

Let's say we have a white heading with a font-size of 1rem up to 320px, but then it turns orange ($c) and the font-size becomes 5vw and starts scaling with the viewport width.

h5 { --i: 0; color: hsl($h, $s, calc(var(--i)*#{$l} + (1 - var(--i))*100%)); font-size: calc(var(--i)*5vw + (1 - var(--i))*1rem); @media (min-width: 320px) { --i: 1 } } Switching triggered by viewport change (live demo) Coding a more complex example from scratch

The example we dissect here is that of the expanding search shown at the beginning of this article, inspired by this Pen, which you should really check out because the code is pretty damn clever.

Expanding search.

Note that from a usability point of view, having such a search box on a website may not be the best idea as one would normally expect the button following the search box to trigger the search, not close the search bar, but it's still an interesting coding exercise, which is why I've chosen to dissect it here.

To begin with, my idea was to do it using only form elements. So, the HTML structure looks like this:

<input id='search-btn' type='checkbox'/> <label for='search-btn'>Show search bar</label> <input id='search-bar' type='text' placeholder='Search...'/>

What we do here is initially hide the text input and then reveal it when the checkbox before it gets checked — let's dive into how that works!

First off, we use a basic reset and set a flex layout on the container of our input and label elements. In our case, this container is the body, but it could be another element as well. We also absolutely position the checkbox and move it out of sight (outside the viewport).

*, :before, :after { box-sizing: border-box; margin: 0; padding: 0; font: inherit } html { overflow-x: hidden } body { display: flex; align-items: center; justify-content: center; margin: 0 auto; min-width: 400px; min-height: 100vh; background: #252525 } [id='search-btn'] { position: absolute; left: -100vh }

So far, so good...

See the Pen by thebabydino (@thebabydino) on CodePen.

So what? We have to admit it's not exciting at all, so let's move on to the next step!

We turn the checkbox label into a big round green button and move its text content out of sight using a big negative-valued text-indent and overflow: hidden.

$btn-d: 5em; /* same as before */ [for='search-btn'] { overflow: hidden; width: $btn-d; height: $btn-d; border-radius: 50%; box-shadow: 0 0 1.5em rgba(#000, .4); background: #d9eb52; text-indent: -100vw; cursor: pointer; }

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we polish the actual search bar by:

  • giving it explicit dimensions
  • providing a background for its normal state
  • defining a different background and a glow for its focused state
  • rounding the corners on the left side using a border-radius that equals half its height
  • Cleaning up the placeholder a bit
$btn-d: 5em; $bar-w: 4*$btn-d; $bar-h: .65*$btn-d; $bar-r: .5*$bar-h; $bar-c: #ffeacc; /* same as before */ [id='search-bar'] { border: none; padding: 0 1em; width: $bar-w; height: $bar-h; border-radius: $bar-r 0 0 $bar-r; background: #3f324d; color: #fff; font: 1em century gothic, verdana, arial, sans-serif; &::placeholder { opacity: .5; color: inherit; font-size: .875em; letter-spacing: 1px; text-shadow: 0 0 1px, 0 0 2px } &:focus { outline: none; box-shadow: 0 0 1.5em $bar-c, 0 1.25em 1.5em rgba(#000, .2); background: $bar-c; color: #000; } }

See the Pen by thebabydino (@thebabydino) on CodePen.

At this point, the right edge of the search bar coincides with the left edge of the button. However, we want a bit of overlap — let's say an overlap such that the right edge of the search bar coincides with the button's vertical midline. Given that we have a flexbox layout with align-items: center on the container (the body in our case), the assembly made up of our two items (the bar and the button) remains middle-aligned horizontally even if we set a margin on one or on the other or on both in between those items. (On the left of the leftmost item or on the right of the rightmost item is a different story, but we won't be getting into that now.)

Creating overlap, keeping alignment (live demo).

That's an overlap of .5*$btn-d minus half a button diameter, which is equivalent to the button's radius. We set this as a negative margin-right on the bar. We also adjust the padding on the right of the bar so that we compensate for the overlap:

$btn-d: 5em; $btn-r: .5*$btn-d; /* same as before */ [id='search-bar'] { /* same as before */ margin-right: -$btn-r; padding: 0 calc(#{$btn-r} + 1em) 0 1em; }

We now have the bar and the button in the positions for the expanded state:

See the Pen by thebabydino (@thebabydino) on CodePen.

Except the bar follows the button in DOM order, so it's placed on top of it, when we actually want the button on top. Fortunately, this has an easy fix (at least for now — it won't be enough later, but let's deal with one issue at a time).

[for='search-btn'] { /* same as before */ position: relative; }

Now that we've given the button a non-static position value, it's on top of the bar:

See the Pen by thebabydino (@thebabydino) on CodePen.

In this state, the total width of the bar and button assembly is the bar width $bar-w plus the button's radius $btn-r (which is half the button diameter $btn-d) because we have an overlap for half the button. In the collapsed state, the total width of the assembly is just the button diameter $btn-d.

Expanded vs. collapsed state (live).

Since we want to keep the same central axis when going from the expanded to the collapsed state, we need to shift the button to the left by half the assembly width in the expanded state (.5*($bar-w + $btn-r)) minus the button's radius ($btn-r).

We call this shift $x and we use it with minus on the button (since we shift the button to the left and left is the negative direction of the x axis). Since we want the bar to collapse into the button, we set the same shift $x on it, but in the positive direction (as we shift the bar to the right of the x axis).

We're in the collapsed state when the checkbox isn't checked and in the expanded state when it isn't. This means our bar and button are shifted with a CSS transform when the checkbox isn't checked and in the position we currently have them in (no transform) when the checkbox is checked.

In order to do this, we set a variable --i on the elements following our checkbox — the button (created with the label for the checkbox) and the search bar. This variable is 0 in the collapsed state (when both elements are shifted and the checkbox isn't checked) and 1 in the expanded state (when our bar and button are in the positions they currently occupy, no shift, and the checkbox is checked).

$x: .5*($bar-w + $btn-r) - $btn-r; [id='search-btn'] { position: absolute; left: -100vw; ~ * { --i: 0; --j: calc(1 - var(--i)) /* 1 when --i is 0, 0 when --i is 1 */ } &:checked ~ * { --i: 1 } } [for='search-btn'] { /* same as before */ /* if --i is 0, --j is 1 => our translation amount is -$x * if --i is 1, --j is 0 => our translation amount is 0 */ transform: translate(calc(var(--j)*#{-$x})); } [id='search-bar'] { /* same as before */ /* if --i is 0, --j is 1 => our translation amount is $x * if --i is 1, --j is 0 => our translation amount is 0 */ transform: translate(calc(var(--j)*#{$x})); }

And we now have something interactive! Clicking the button toggles the checkbox state (because the button has been created using the label of the checkbox).

See the Pen by thebabydino (@thebabydino) on CodePen.

Except now the button is a bit difficult to click since it's under the text input again (because we've set a transform on the bar and this establishes a stacking context). The fix is pretty straightforward — we need to add a z-index to the button and this moves it above the bar.

[for='search-btn'] { /* same as before */ z-index: 1; }

See the Pen by thebabydino (@thebabydino) on CodePen.

But we still have another bigger problem: we can see the bar coming out from under the button on the right side. In order to fix this, we set clip-path with an inset() value on the bar. This specifies a clipping rectangle with the help of the distances from the top, right, bottom and left edges of the element's border-box. Everything outside this clipping rectangle gets cut out and only what's inside is displayed.

How the inset() function works (live).

In the illustration above, each distance is going inward from the edges of the border-box. In this case, they're positive. But they can also go outwards, in which case they're negative and the corresponding edges of the clipping rectangle are outside the element's border-box.

At first, you may think we'd have no reason to ever do that, but in our particular case, we do!

We want the distances from the top (dt), bottom (db) and left (dl) to be negative and big enough to contain the box-shadow that extends outside the element's border-box in the :focus state as we don't want it to get clipped out. So the solution is to create a clipping rectangle with edges outside the element's border-box in these three directions.

The distance from the right (dr) is the full bar width $bar-w minus a button radius $btn-r in the collapsed case (checkbox not checked, --i: 0) and 0 in the expanded case (checkbox checked, --i: 1).

$out-d: -3em; [id='search-bar'] { /* same as before */ clip-path: inset($out-d calc(var(--j)*#{$bar-w - $btn-r}) $out-d $out-d); }

We now have a search bar and button assembly that expands and collapses on clicking the button.

See the Pen by thebabydino (@thebabydino) on CodePen.

Since we don't want an abrupt change in between the two states, we use a transition:

[id='search-btn'] { /* same as before */ ~ * { /* same as before */ transition: .65s; } }

We also want our button's background to be green in the collapsed case (checkbox not checked, --i: 0) and pink in the expanded case (checkbox checked, --i: 1). For this, we use the same technique as before:

[for='search-btn'] { /* same as before */ $c0: #d9eb52; // green for collapsed state $c1: #dd1d6a; // pink for expanded state $h0: round(hue($c0)/1deg); $s0: round(saturation($c0)); $l0: round(lightness($c0)); $h1: round(hue($c1)/1deg); $s1: round(saturation($c1)); $l1: round(lightness($c1)); background: hsl(calc(var(--j)*#{$h0} + var(--i)*#{$h1}), calc(var(--j)*#{$s0} + var(--i)*#{$s1}), calc(var(--j)*#{$l0} + var(--i)*#{$l1})); }

Now we're getting somewhere!

See the Pen by thebabydino (@thebabydino) on CodePen.

What we still need to do is create the icon that morphs between a magnifier in the collapsed state and an "x" in the expanded state to indicate a closing action. We do this with the :before and :after pseudo-elements. We begin by deciding on a diameter for the magnifier and how much of this diameter the width of the icon lines represent.

$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d;

We absolutely position both pseudo-elements in the middle of the button taking their dimensions into account. We then make them inherit their parent's transition. We give the :before a background, as this will be the handle of our magnifier, make the :after round with border-radius and give it an inset box-shadow.

[for='search-btn'] { /* same as before */ &:before, &:after { position: absolute; top: 50%; left: 50%; margin: -.5*$ico-d; width: $ico-d; height: $ico-d; transition: inherit; content: '' } &:before { margin-top: -.4*$ico-w; height: $ico-w; background: currentColor } &:after { border-radius: 50%; box-shadow: 0 0 0 $ico-w currentColor } }

We can now see the magnifier components on the button:

See the Pen by thebabydino (@thebabydino) on CodePen.

In order to make our icon to look more like a magnifier, we translate both of its components outwards by a quarter of the magnifier's diameter. This means translating the handle to the right, in the positive direction of the x axis by .25*$ico-d and the main part to the left, in the negative direction of the x axis by the same .25*$ico-d.

We also scale the handle (the :before pseudo-element) horizontally to half its width with respect to its right edge (which means a transform-origin of 100% along the x axis).

We only want this to happen in the collapsed state (checkbox not checked, --i is 0 and, consequently --j is 1), so we multiply the translation amounts by --j and also use --j to condition the scaling factor:

[for='search-btn'] { /* same as before */ &:before { /* same as before */ height: $ico-w; transform: /* collapsed: not checked, --i is 0, --j is 1 * translation amount is 1*.25*$d = .25*$d * expanded: checked, --i is 1, --j is 0 * translation amount is 0*.25*$d = 0 */ translate(calc(var(--j)*#{.25*$ico-d})) /* collapsed: not checked, --i is 0, --j is 1 * scaling factor is 1 - 1*.5 = 1 - .5 = .5 * expanded: checked, --i is 1, --j is 0 * scaling factor is 1 - 0*.5 = 1 - 0 = 1 */ scalex(calc(1 - var(--j)*.5)) } &:after { /* same as before */ transform: translate(calc(var(--j)*#{-.25*$ico-d})) } }

We now have thew magnifier icon in the collapsed state:

See the Pen by thebabydino (@thebabydino) on CodePen.

Since we want both icon components to be rotated by 45deg, we add this rotation on the button itself:

[for='search-btn'] { /* same as before */ transform: translate(calc(var(--j)*#{-$x})) rotate(45deg); }

Now we have the look we want for the collapsed state:

See the Pen by thebabydino (@thebabydino) on CodePen.

This still leaves the expanded state, where we need to turn the round :after pseudo-element into a line. We do this by scaling it down along the x axis and bringing its border-radius from 50% to 0%. The scaling factor we use is the ratio between the width $ico-w of the line we want to get and the diameter $ico-d of the circle it forms in the collapsed state. We've called this ratio $ico-f.

Since we only want to do this in the expanded state, when the checkbox is checked and --i is 1, we make both the scaling factor and the border-radius depend on --i and --j:

$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d; [for='search-btn'] { /* same as before */ &:after{ /* same as before */ /* collapsed: not checked, --i is 0, --j is 1 * border-radius is 1*50% = 50% * expanded: checked, --i is 1, --j is 0 * border-radius is 0*50% = 0 */ border-radius: calc(var(--j)*50%); transform: translate(calc(var(--j)*#{-.25*$ico-d})) /* collapsed: not checked, --i is 0, --j is 1 * scaling factor is 1 + 0*$ico-f = 1 * expanded: checked, --i is 1, --j is 0 * scaling factor is 0 + 1*$ico-f = $ico-f */ scalex(calc(1 - var(--j)*.5)) } }

See the Pen by thebabydino (@thebabydino) on CodePen.

Hmm, almost, but not quite. Scaling has also shrunk our inset box-shadow along the x axis, so let's fix that with a second inset shadow that we only get in the expanded state (when the checkbox is checked and --i is 1) and therefore, its spread and alpha depend on --i:

$ico-d: .5*$bar-h; $ico-f: .125; $ico-w: $ico-f*$ico-d; [for='search-btn'] { /* same as before */ --hsl: 0, 0%, 0%; color: HSL(var(--hsl)); &:after{ /* same as before */ box-shadow: inset 0 0 0 $ico-w currentcolor, /* collapsed: not checked, --i is 0, --j is 1 * spread radius is 0*.5*$ico-d = 0 * alpha is 0 * expanded: checked, --i is 1, --j is 0 * spread radius is 1*.5*$ico-d = .5*$ico-d * alpha is 1 */ inset 0 0 0 calc(var(--i)*#{.5*$ico-d}) HSLA(var(--hsl), var(--i)) } }

This gives us our final result!

See the Pen by thebabydino (@thebabydino) on CodePen.

A few more quick examples

The following are a few more demos that use the same technique. We won't be building these from scratch — we'll merely go through the basic ideas behind them.

Responsive banners Screenshot collage (live demo, not fully functional in Edge due to using a calc() value for font-size).

In this case, our actual elements are the smaller rectangles in front, while the number squares and the bigger rectangles in the back are created with the :before and :after pseudo-elements, respectively.

The backgrounds of the number squares are individual and set using a stop list variable --slist that's different for each item.

<p style='--slist: #51a9ad, #438c92'><!-- 1st paragraph text --></p> <p style='--slist: #ebb134, #c2912a'><!-- 2nd paragraph text --></p> <p style='--slist: #db4453, #a8343f'><!-- 3rd paragraph text --></p> <p style='--slist: #7eb138, #6d982d'><!-- 4th paragraph text --></p>

The things that influence the styles on the banners are the parity and whether we're in the wide, normal or narrow case. These give us our switch variables:

html { --narr: 0; --comp: calc(1 - var(--narr)); --wide: 1; @media (max-width: 36em) { --wide: 0 } @media (max-width: 20em) { --narr: 1 } } p { --parity: 0; &:nth-child(2n) { --parity: 1 } }

The number squares are absolutely positioned and their placement depends on parity. If the --parity switch is off (0), then they're on the left. If it's on (1), then they're on the right.

A value of left: 0% aligns with the left edge of the number square along the left edge of its parent, while a value of left: 100% aligns its left edge along the parent's right edge.

In order to have the right edge of the number square aligned with the right edge of its parent, we need to subtract its own width out of the previous 100% value. (Remember that % values in the case of offsets are relative to the parent's dimensions.)

left: calc(var(--parity)*(100% - #{$num-d}))

...where $num-d is the size of the numbering square.

In the wide screen case, we also push the numbering outwards by 1em — this means subtracting 1em from the offset we have so far for odd items (having the --parity switch off) and adding 1em to the offset we have so far for even items (having the --parity switch on).

Now the question here is... how do we switch the sign? The simplest way to do it is by using the powers of -1. Sadly, we don't have a power function (or a power operator) in CSS, even though it would be immensely useful in this case:

/* * for --parity: 0, we have pow(-1, 0) = +1 * for --parity: 1, we have pow(-1, 1) = -1 */ pow(-1, var(--parity))

This means we have to make it work with what we do have (addition, subtraction, multiplication and division) and that leads to a weird little formula... but, hey, it works!

/* * for --parity: 0, we have 1 - 2*0 = 1 - 0 = +1 * for --parity: 1, we have 1 - 2*1 = 1 - 2 = -1 */ --sign: calc(1 - 2*var(--parity))

This way, our final formula for the left offset, taking into account both the parity and whether we're in the wide case (--wide: 1) or not (--wide: 0), becomes:

left: calc(var(--parity)*(100% - #{$num-d}) - var(--wide)*var(--sign)*1em)

We also control the width of the paragraphs with these variables and max-width as we want it to have an upper limit and only fully cover its parent horizontally in the narrow case (--narr: 1):

width: calc(var(--comp)*80% + var(--narr)*100%); max-width: 35em;

The font-size also depends on whether we're in the narrow case (--narr: 1) or not (--narr: 0):

calc(.5rem + var(--comp)*.5rem + var(--narr)*2vw)

...and so do the horizontal offsets for the :after pseudo-element (the bigger rectangle in the back) as they're 0 in the narrow case (--narr: 1) and a non-zero offset $off-x otherwise (--narr: 0):

right: calc(var(--comp)*#{$off-x}); left: calc(var(--comp)*#{$off-x}); Hover and focus effects Effect recording (live demo, not fully functional in Edge due to nested calc() bug).

This effect is created with a link element and its two pseudo-elements sliding diagonally on the :hover and :focus states. The link's dimensions are fixed and so are those of its pseudo-elements, set to the diagonal of their parent $btn-d (computed as the hypotenuse in the right triangle formed by a width and a height) horizontally and the parent's height vertically.

The :before is positioned such that its bottom left corner coincides to that of its parent, while the :after is positioned such that its top right corner coincides with that of its parent. Since both should have the same height as their parent, the vertical placement is resolved by setting top: 0 and bottom: 0. The horizontal placement is handled in the exact same way as in the previous example, using --i as the switch variable that changes value between the two pseudo-elements and --j, its complementary (calc(1 - var(--i))):

left: calc(var(--j)*(100% - #{$btn-d}))

We set the transform-origin of the :before to its left-bottom corner (0% 100%) and :after to its right-top corner (100% 0%), again, with the help of the switch --i and its complementary --j:

transform-origin: calc(var(--j)*100%) calc(var(--i)*100%)

We rotate both pseudo-elements to the angle between the diagonal and the horizontal $btn-a (also computed from the triangle formed by a height and a width, as the arctangent of the ratio between the two). With this rotation, the horizontal edges meet along the diagonal.

We then shift them outwards by their own width. This means we'll use a different sign for each of the two, again depending on the switch variable that changes value in between the :before and :after, just like in the previous example with the banners:

transform: rotate($btn-a) translate(calc((1 - 2*var(--i))*100%))

In the :hover and :focus states, this translation needs to go back to 0. This means we multiply the amount of the translation above by the complementary --q of the switch variable --p that's 0 in the normal state and 1 in the :hover or :focus state:

transform: rotate($btn-a) translate(calc(var(--q)*(1 - 2*var(--i))*100%))

In order to make the pseudo-elements slide out the other way (not back the way they came in) on mouse-out or being out of focus, we set the switch variable --i to the value of --p for :before and to the value of --q for :after, reverse the sign of the translation, and make sure we only transition the transform property.

Responsive infographic Screenshot collage with the grid lines and gaps highlighted (live demo, no Edge support due to CSS variable and calc() bugs).

In this case, we have a three-row, two-column grid for each item (article element), with the third row collapsed in the wide screen scenario and the second column collapsed in the narrow screen scenario. In the wide screen scenario, the widths of the columns depend on the parity. In the narrow screen scenario, the first column spans the entire content-box of the element and the second one has width 0. We also have a gap in between the columns, but only in the wide screen scenario.

// formulas for the columns in the wide screen case, where // $col-a-wide is for second level heading + paragraph // $col-b-wide is for the first level heading $col-1-wide: calc(var(--q)*#{$col-a-wide} + var(--p)*#{$col-b-wide}); $col-2-wide: calc(var(--q)*#{$col-b-wide} + var(--p)*#{$col-a-wide}); // formulas for the general case, combining the wide and normal scenarios $row-1: calc(var(--i)*#{$row-1-wide} + var(--j)*#{$row-1-norm}); $row-2: calc(var(--i)*#{$row-2-wide} + var(--j)*#{$row-2-norm}); $row-3: minmax(0, auto); $col-1: calc(var(--i)*#{$col-1-wide} + var(--j)*#{$col-1-norm}); $col-2: calc(var(--i)*#{$col-2-wide}); $art-g: calc(var(--i)*#{$art-g-wide}); html { --i: var(--wide, 1); // 1 in the wide screen case --j: calc(1 - var(--i)); @media (max-width: $art-w-wide + 2rem) { --wide: 0 } } article { --p: var(--parity, 0); --q: calc(1 - var(--p)); --s: calc(1 - 2*var(--p)); display: grid; grid-template: #{$row-1} #{$row-2} #{$row-3}/ #{$col-1} #{$col-2}; grid-gap: 0 $art-g; grid-auto-flow: column dense; &:nth-child(2n) { --parity: 1 } }

Since we've set grid-auto-flow: column dense, we can get away with only setting the first level heading to cover an entire column (second one for odd items and first one for even items) in the wide screen case and let the second level heading and the paragraph text fill the first free available cells.

// wide case, odd items: --i is 1, --p is 0, --q is 1 // we're on column 1 + 1*1 = 2 // wide case, even items: --i is 1, --p is 1, --q is 0 // we're on column 1 + 1*0 = 1 // narrow case: --i is 0, so var(--i)*var(--q) is 0 and we're on column 1 + 0 = 1 grid-column: calc(1 + var(--i)*var(--q)); // always start from the first row // span 1 + 2*1 = 3 rows in the wide screen case (--i: 1) // span 1 + 2*0 = 1 row otherwise (--i: 0) grid-row: 1/ span calc(1 + 2*var(--i));

For each item, a few other properties depend on whether we're in the wide screen scenario or not.

The vertical margin, vertical and horizontal padding values, box-shadow offsets and blur are all bigger in the wide screen case:

$art-mv: calc(var(--i)*#{$art-mv-wide} + var(--j)*#{$art-mv-norm}); $art-pv: calc(var(--i)*#{$art-pv-wide} + var(--j)*#{$art-p-norm}); $art-ph: calc(var(--i)*#{$art-ph-wide} + var(--j)*#{$art-p-norm}); $art-sh: calc(var(--i)*#{$art-sh-wide} + var(--j)*#{$art-sh-norm}); article { /* other styles */ margin: $art-mv auto; padding: $art-pv $art-ph; box-shadow: $art-sh $art-sh calc(3*#{$art-sh}) rgba(#000, .5); }

We have a non-zero border-width and border-radius in the wide screen case:

$art-b: calc(var(--i)*#{$art-b-wide}); $art-r: calc(var(--i)*#{$art-r-wide}); article { /* other styles */ border: solid $art-b transparent; border-radius: $art-r; }

In the wide screen scenario, we limit the items' width, but let it be 100% otherwise.

$art-w: calc(var(--i)*#{$art-w-wide} + var(--j)*#{$art-w-norm}); article { /* other styles */ width: $art-w; }

The direction of the padding-box gradient also changes with the parity:

background: linear-gradient(calc(var(--s)*90deg), #e6e6e6, #ececec) padding-box, linear-gradient(to right bottom, #fff, #c8c8c8) border-box;

In a similar manner, margin, border-width, padding, width, border-radius, background gradient direction, font-size or line-height for the headings and the paragraph text also depend on whether we're in the wide screen scenario or not (and, in the case of the first level heading's border-radius or background gradient direction, also on the parity).

The post DRY Switching with CSS Variables: The Difference of One Declaration appeared first on CSS-Tricks.

The All Powerful Front-End Developer

Css Tricks - Wed, 12/05/2018 - 4:33am

I posted a video of this talk some months back, but it was nearly an hour and a half long. Here's an updated version that I gave at JAMstack_conf that's only 30 minutes:

The gist is that the front-end stack is wildly powerful these days. Our front-end skillset can be expanded to give us power to do back-end-ish things and talk with APIs that allow us to build entire products in a way we haven't quite been able to before.

The post The All Powerful Front-End Developer appeared first on CSS-Tricks.

It’s not about the device.

Css Tricks - Tue, 12/04/2018 - 5:39am

Ever have that, "Ugighgk, another device to support?!" feeling? Like, perhaps when you heard that wrist devices have browsers? Ethan's latest post is about that.

Personally, the Apple Watch is interesting to me not because it’s a watch. Rather, it’s interesting to me because it’s a smaller-than-normal touchscreen attached to a cellular antenna, and one that’s not necessarily on the most reliable connection. It helps me look past the device, and to think about how someone will interact with my work under those conditions. Once I do that, I can start to design accordingly.

The post is nice reminder to revisit the idea of responsive design in our heads. The seismic shifts in how we consume the web is why web design and development shifted this way. So, enough thinking about specific devices. Instead, let's make our approaches responsive and flexible, then new devices will come along. They will inevitably slot themselves right in without us having to re-design or re-code anything.

Direct Link to ArticlePermalink

The post It’s not about the device. appeared first on CSS-Tricks.

Bridging the Gap Between CSS and JavaScript: CSS Modules, PostCSS and the Future of CSS

Css Tricks - Tue, 12/04/2018 - 5:03am

In the previous post in this two-part series, we explored the CSS-in-JS landscape and, we realized not only that CSS-in-JS can produce critical styles, but also that some libraries don’t even have a runtime. We saw that user experience can significantly improve by adding clever optimizations, which is why this series focuses on developer experience (the experience of authoring styles).

In this part, we’ll explore the tools for "plain ol’ CSS" by refactoring the Photo component from our existing example.

Controversy and #hotdrama

One of the most famous CSS debates is whether the language is fine just the way that it is. I think this debate stays alive because there is some truth to both sides. For example, while it’s true that CSS was initially designed to style a document rather than components of an application, it’s also true that upcoming CSS features will dramatically change this, and that many CSS mistakes stem from treating styling as an afterthought instead of taking time to learn it properly or hiring someone who’s good at it.

I don’t think that CSS tools themselves are the source of the controversy; we’ll probably always use them to some extent at the very least. But approaches like CSS-in-JS are different in that they patch up the shortcomings of CSS with client-side JavaScript. However, CSS-in-JS is not the only approach here; it is merely the newest. Remember when we used to have similar debates about preprocessors, like Sass? Sass has features, like mixins, that aren’t based on any CSS proposal (not to mention the entire indented syntax). However, Sass was born in a much different time and has reached a point where it’s no longer fair to include it in the debate because the debate itself has changed — so we started criticizing CSS-in-JS because it’s an easier target.

I think we should use tools that let us use proposed syntax today. Let’s use JavaScript Promises as an analogy. This feature isn’t supported by Internet Explorer, so many people include a polyfill for it. The point of polyfills is to enable us to pretend like the feature is supported everywhere by substituting native browser implementations with a patch. Same goes for transpiling new syntax with tools, like Babel. We can use it today because the code will be compiled to an older, well-supported syntax. This is a good approach because it allows us to use future features today while pushing JavaScript forward the way preprocessing tools, like Sass, have pushed CSS forward.

My take on the CSS controversy is that we should use tools that enable us to use future CSS today.

Preprocessors

We’ve already talked a bit about CSS preprocessors, so it’s worth discussing them in a little more details and how they fit into the CSS-in-JS conversation. We have Sass, Less and PostCSS (among others) that can imbue our CSS code with all kinds of new features.

For our example, we’re only going to be concerned with nesting, one of the most common and powerful features of preprocessors. I suggest using PostCSS because it gives us fine-grained control over the features we’re adding, which is exactly what we need in this case. The PostCSS plugin that we’re going to use is postcss-nesting because it follows the actual proposal for native CSS nesting.

The best way to use PostCSS with our compiling tool, webpack, is to add postcss-loader after css-loader in the configuration. When adding loaders after css-loader, it’s important to account for them in the css-loader options by setting importLoaders to the number of succeeding loaders, which in this case is 1:

{ test: /\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 1, }, }, 'postcss-loader', ], }

This ensures that CSS files imported from other CSS files will be processed with postcss-loader as well.

After setting up postcss-loader, we’ll install postcss-nesting and include it in the PostCSS configuration:

yarn add postcss-nesting

There are many ways to configure PostCSS. In this case, we’re going to add a postcss.config.js file at the root of our project:

module.exports = { plugins: { "postcss-nesting": {}, }, }

Now, we can write a CSS file for our Photo component. Let’s call it Photo.css:

.photo { width: 200px; &.rounded { border-radius: 1rem; } } @media (min-width: 30rem) { .photo { width: 400px; } }

Let’s also add a file called utils.css that contains a class for visually hiding elements, as we covered in the the first part of this series:

.visuallyHidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; white-space: nowrap; }

Since our component relies on this utility, let’s include utils.css to Photo.css by adding an @import statement to the top:

@import url('utils.css');

This will ensure that webpack requires utils.css, thanks to css-loader. We can place utils.css anywhere we want and adjust the @import path. In this particular case, it’s a sibling of Photo.css.

Next, let’s import Photo.css into our JavaScript file and use the classes to style our component:

import React from 'react' import { getSrc, getSrcSet } from './utils' import './Photo.css' const Photo = ({ publicId, alt, rounded }) => ( <figure> <img className={rounded ? 'photo rounded' : 'photo'} src={getSrc({ publicId, width: 200 })} srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })} sizes="(min-width: 30rem) 400px, 200px" /> <figcaption className="visuallyHidden">{alt}</figcaption> </figure> ) Photo.defaultProps = { rounded: false, } export default Photo

While this will work, our class names are way too simple and they will most certainly clash with others completely unrelated to our .photo class. One of the ways of working around this is using a naming methodology, like BEM, to rename our classes (e.g. photo_rounded and photo__what-is-this--i-cant-even) to help prevent clashes from happening, but components quickly get complex and class names tend to get long, depending on the overall complexity of the project.

Meet CSS Modules.

CSS Modules

Simply put, CSS Modules are CSS files in which all class names and animations are scoped locally by default. They look a lot like regular CSS. For example, we can use our Photo.css and utils.css files as CSS Modules without modifying them at all, simply by passing modules: true to css-loader’s options:

{ loader: 'css-loader', options: { importLoaders: 1, modules: true, }, }

CSS Modules are an evolving feature and could be discussed at even greater length. Robin’s three-part series on it is a good overview and introduction.

While CSS Modules themselves look very similar to regular CSS, the way we use them is quite different. They are imported into JavaScript as objects where keys correspond to authored class names, and values are unique class names that are auto-generated for us that keep the scope limited to a component:

import React from 'react' import { getSrc, getSrcSet } from './utils' import styles from './Photo.css' import stylesUtils from './utils.css' const Photo = ({ publicId, alt, rounded }) => ( <figure> <img className={rounded ? `${styles.photo} ${styles.rounded}` : styles.photo} src={getSrc({ publicId, width: 200 })} srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })} sizes="(min-width: 30rem) 400px, 200px" /> <figcaption className={stylesUtils.visuallyHidden}>{alt}</figcaption> </figure> ) Photo.defaultProps = { rounded: false, } export default Photo

Since we’re using utils.css as a CSS Module, we can remove the @import statement at the top of Photo.css. Also, notice that using camelCase to format class names makes them easier to use in JavaScript. If we had used dashes, we’d have to write things out in full, like stylesUtils['visually-hidden'].

CSS Modules have additional features, like composition. Right now, we’re importing utils.css into Photo.js to apply our component styles, but let’s say that we want to shift the responsibility of styling the caption to Photo.css instead. That way, as far as our JSX code is concerned, styles.caption is just another class name; it just so happens to visually hide the element, but it might be styled differently in the future. Either way, Photo.css will be making those decisions.

So let’s add a caption style to Photo.css to extend the properties of the visuallyHidden utility using composes:

.caption { composes: visuallyHidden from './utils.css'; }

We could just as well add more rules to that class, but this is all we need in this case. Now, we no longer need to import utils.css into Photo.js; we can simply use styles.caption instead:

<figcaption className={styles.caption}>{alt}</figcaption>

How does this work? Do the styles from visuallyHidden get copied over to caption? Let’s examine the value of styles.caption — whoa, two classes! That’s right: one is from visuallyHidden and the other one will apply any other styles we add to caption. CSS-in-JS makes it too easy to duplicate styles with libraries, like polished, but CSS Modules encourage you to reuse existing styles. No need to create a new VisuallyHidden React component to only apply several CSS rules.

Let’s take it even further by examining this uncomfortable class composition:

rounded ? `${styles.photo} ${styles.rounded}` : styles.photo

There are libraries for these situations, like classnames, which are useful for more complex class composition. In our example, though, we can keep on using composes and rename .rounded to .roundedPhoto:

.photo { width: 200px; } .roundedPhoto { composes: photo; border-radius: 1rem; } @media (min-width: 30rem) { .photo { width: 400px; } } .caption { composes: visuallyHidden from './utils.css'; }

Now we can apply the class names to our component in a much more readable fashion:

rounded ? styles.roundedPhoto : styles.photo

But wait, what if we accidentally place the .roundedPhoto ruleset before .photo and some rules from .photo end up overriding rules from .roundedPhoto due to specificity? Don’t worry, CSS Modules prevent us from composing classes defined after the current class by throwing an error like this:

referenced class name "photo" in composes not found (2:3) 1 | .roundedPhoto { > 2 | composes: photo; | ^ 3 | border-radius: 1rem; 4 | }

Note that it’s generally a good idea to use a file naming convention for CSS Modules, for example using the extension .module.css, because it’s common to want to apply some global styles as well.

Dynamic styles

So far, we’ve been conditionally applying predefined sets of styles, which is called conditional styling. What if we also want to be able to fine-tune the border radius of the rounded photos? This is called dynamic styling because we don’t know what the value is going to be in advance; it can change while the application is running.

There aren’t many use cases for dynamic styling — usually we’re styling conditionally, but in cases when we need this, how would we approach this? While we could get by with inline styles, a native solution for this type of problems is custom properties (a.k.a. CSS variables). A really valuable aspect of this feature is that browsers will update styles using custom properties when JavaScript changes them. We can set a custom property on an element through inline styles, which means that it will be scoped to that element and that element only:

style={typeof borderRadius !== 'undefined' ? { '--border-radius': borderRadius, } : null}

In Photo.css, we can use this custom property by using var() and passing the default value as the second argument:

.roundedPhoto { composes: photo; border-radius: var(--border-radius, 1rem); }

As far as JavaScript is concerned, it’s only passing a dynamic parameter to CSS, then when CSS takes over, it can apply the value as-is, calculate a new value from it using calc(), etc.

Fallback

At the time of this writing, the browser support for custom properties is... well, you decide for yourself. Not supporting these browsers is (probably) out of the question for a real-world application, but keep in mind that some styles are less important than others. In this case, it’s not a big deal if the border radius on IE is always 1rem. The application doesn’t have to look the same way on every browser.

The way we can automatically provide fallbacks for all custom properties is to install postcss-custom-properties and add it to our PostCSS configuration:

yarn add postcss-custom-properties module.exports = { plugins: { 'postcss-nesting': {}, 'postcss-custom-properties': {}, }, }

This will generate a fallback for our border-radius rule:

.roundedPhoto { composes: photo; border-radius: 1rem; border-radius: var(--border-radius, 1rem); }

Browsers that don’t understand var() will ignore that rule and use the previous one. Don’t let the name of the plugin fool you; it only partially improves the support for custom properties by providing static fallbacks. The dynamic aspect can’t be polyfilled.

Exposing values to JavaScript

In the previous part of this series, we explored how CSS-in-JS allows us to share almost anything between CSS and JavaScript, using media queries as an example. There is no possible way to achieve this here, right?

Thanks to Jonathan Neal, you can!

First, meet postcss-preset-env, the successor to cssnext. It’s a PostCSS plugin that acts as a preset similar to @babel/preset-env. It contains plugins like postcss-nesting, postcss-custom-properties, autoprefixer etc. so we can use future CSS today. It splits the plugins across four stages of standardization. Some of the features I’d like to show you aren’t included in the default range (stage 2+), so we’ll explicitly enable the ones we need:

yarn add postcss-preset-env module.exports = { plugins: { 'postcss-preset-env': { features: { 'nesting-rules': true, 'custom-properties': true, // already included in stage 2+ 'custom-media-queries': true, // oooh, what's this? :) }, }, }, }

Note that we replaced our existing plugins because this postcss-preset-env configuration includes them, meaning our existing code should work the same as before.

Using custom properties in media queries is invalid because that’s not what they were designed for. Instead we’ll use custom media queries:

@custom-media --photo-breakpoint (min-width: 30em); .photo { width: 200px; } @media (--photo-breakpoint) { .photo { width: 400px; } }

Even though this feature is in the experimental stage and therefore not supported in any browser, thanks to postcss-preset-env it just works! One catch is that PostCSS operates on a per-file basis, so this way only Photo.css can use --photo-breakpoint. Let’s do something about that.

Jonathan Neal recently implemented an importFrom option in postcss-preset-env, which is passed to other plugins that support it as well, like postcss-custom-properties and postcss-custom-media. Its value can be many things, but for the purpose of our example, it’s a path to a file that will be imported to the files PostCSS processes. Let’s call this one global.css and move our custom media query there:

@custom-media --photo-breakpoint (min-width: 30em);

...and let’s define importFrom, providing the path to global.css:

module.exports = { plugins: { 'postcss-preset-env': { importFrom: 'src/global.css', features: { 'nesting-rules': true, 'custom-properties': true, 'custom-media-queries': true, }, }, }, }

Now we can delete the @custom-media line at the top of Photo.css and our --photo-breakpoint value will still work, because postcss-preset-env will use the one from global.css to compile it. Same goes for custom properties and custom selectors.

Now, how to expose it to JavaScript? When experimental features like custom media queries get standardized and implemented in major browsers, we will be able to retrieve them natively from CSS. For example, this is how we would access a custom property called --font-family defined on :root:

const rootStyles = getComputedStyle(document.body) const fontFamily = rootStyles.getPropertyValue('--font-family')

If custom media queries get standardized we will probably be able to access them in a similar way, but in the meantime we have to find an alternative. We could use the exportTo option to generate a JavaScript or JSON file, which we would import into JavaScript. However, the problem is that webpack would try to require it before it’s generated. Even if we generated it before running webpack, every update to global.css would cause webpack to re-compile twice, once to generate the output file, and once more to import it. I wanted a solution that’s unencumbered by its implementation.

For this series, I’ve created a brand new webpack loader called css-customs-loader just for you! It makes this task easy: all we need to is include it in our webpack configuration before css-loader:

{ test: /\.css$/, use: [ 'style-loader', 'css-customs-loader', { loader: 'css-loader', options: { importLoaders: 1, }, }, 'postcss-loader', ], }

This exposes custom media queries, as well as custom properties, to JavaScript. We can access them simply by importing global.css:

import React from 'react' import { getSrc, getSrcSet } from './utils' import styles from './photo.module.css' import { customMedia } from './global.css' const Photo = ({ publicId, alt, rounded, borderRadius }) => ( <figure> <img className={rounded ? styles.roundedPhoto : styles.photo} style={ typeof borderRadius !== 'undefined' ? { ['--border-radius']: borderRadius } : null } src={getSrc({ publicId, width: 200 })} srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })} sizes={`${customMedia['--photo-breakpoint']} 400px, 200px`} /> <figcaption className={styles.caption}>{alt}</figcaption> </figure> ) Photo.defaultProps = { rounded: false, } export default Photo

That’s it!

I created a repository demonstrating all of the concepts discussed in this series. Its readme also contains some advanced tips about the approach described in this post.

View Repo

Conclusion

It’s safe to say that tools like CSS Modules and PostCSS and upcoming CSS features are up to the task of dealing with many challenges of CSS. Whichever side of the CSS debate you’re on, this approach is worth exploring.

I have a strong CSS-in-JS background, but I’m very susceptible to hype, so keeping up with that world is very hard for me. While having styles next to the behavior can be succinct, it’s also mixing two very different languages — CSS is very verbose compared to JavaScript. This incentivized me to write less CSS because I wanted to avoid getting the file too crowded. This may be a matter of personal preference, but I didn’t want that to be an issue. Using a separate file for CSS finally gave my code some air.

While mastering this approach may not be as straightforward as CSS-in-JS, I believe it's more rewarding in the long run. It will improve your CSS skills and make you better prepared for its future.

Article Series:
  1. CSS-in-JS
  2. CSS Modules, PostCSS and the Future of CSS (This post)

The post Bridging the Gap Between CSS and JavaScript: CSS Modules, PostCSS and the Future of CSS appeared first on CSS-Tricks.

Sayonara Edge

Css Tricks - Tue, 12/04/2018 - 3:59am

Sounds like Edge is going to spin down EdgeHTML, the engine that powers edge, and go with Chromium. It's not entirely clear as I write whether the browser will still be called Edge or not. Opera did this same thing in 2013. We'll surely be seeing much more information about this directly from Microsoft, and hot takes galore.

Probably three major categories of hot-take:

  1. Hallelujah, I dislike supporting Edge, this will make my job easier and make the web better for users.
  2. Yikes, this is bad for the web. Browser engine diversity is a very good thing for the web. See Rachel Nabors The Ecological Impact of Browser Diversity.
  3. This might be good in that combining forces makes a stronger team. If many teams each build a 50-meter tower, maybe working together they can build a 100-meter tower.

I'm not quite sure what to think yet, except that it's a good reminder that businesses will be businesses.

Direct Link to ArticlePermalink

The post Sayonara Edge appeared first on CSS-Tricks.

Linkbait 42

QuirksBlog - Tue, 12/04/2018 - 12:22am

Full-stack edition. Look no further than here.

  • Microsoft is rumoured to pull the plug on their EdgeHTML rendering engine and move to Chromium for the next Edge version. It’s always a sad moment when a rendering engine leaves us.
    Now as anyone who read my Chromium blog posts knows, one Chromium is not necessarily exactly the same as another. Thus I do not expect EdgeChromium to be 100% the same as Chrome — more like 98%.
    How to solve this? Test more in Edge — 3 years ago. Of course my readers know this and do this; it’s the full-stackers that are the problem.
  • Speaking of which. Heydon takes a stab at defining the problem with full-stack developers. They try to do too much, and as a result, code quality suffers.

    By assuming the role of the Full Stack Developer (which is, in practice, a computer scientist who also writes HTML and CSS), one takes responsibility for all the code, in spite of its radical variance in syntax and purpose, and becomes the gatekeeper of at least some kinds of code one simply doesn’t care about writing well.

    He also points out that one full-stack developer is cheaper than three specialised developers, and cost cutting is certainly part of the reason why full-stack has become so popular.
    Still, to me, it all comes down to the low esteem in which HTML/CSS is held (and Heydon also mentions that). It’s much easier to declare a low-status job redundant than a high-status one. I mean, are there large companies that have the same people do development and devops?
  • In case you’re wondering why full-stack programmers have such problems with CSS (and HTML), Robin Rendle explains. Front-end (i.e. HTML and CSS) is not a problem to be solved because, essentially, it’s already been solved. Just not by full-stack programmers.

    What’s the point in learning about vanilla HTML, CSS and JavaScript if they wind up becoming transpiled by other tools and languages?

    [...] Front-end development is complex because design is complex. Transpiling [...] into HTML and CSS requires vim and nuance, and always will. That’s not going to be resolved by a tool but by diligent work over a long period of time.

  • Jeremy compares CSS to a programming language, and finds that selectors, especially, qualify. Still, that’s exactly the part of CSS that isn’t used a lot, since we all have to go modular and use classes everywhere.
    Foor for thought; also for the book.
  • While ostensibly explaining a React feature, Dan Abramov gives a lesson about JavaScript prototypes and inheritance. Useful reading for all JavaScripters — I learned (or relearned) a thing or two here.
  • Addy Osmani takes a look at the cost of JavaScript. While they work OK-ish on modern desktop computers, huge scripts cause a lot of problems on low-end phones. (News? No, not really. But people don’t listen.) As he says,

    The web is bloated by user “experience”

    If you want facts and figures to convince others, look no further than this article.
  • Harry takes an exhaustive dive into CSS and network performance.

    Your page will only render as quickly as your slowest stylesheet.

    If you ever wanted to know absolutely everything about the way stylesheets can block page rendering, read it all. Did you know, for instance, that you should not place stylesheets before async script snippets? That’s because browsers await the full stylesheet before evaluating the snippet. What if the script requests the colour of a certain element, and the CSS hasn’t come in yet? Better to wait.
    Tons more good stuff here.
  • Interesting experiment by Daniel Buchner. He shows how to use the CSS :invalid pseudo, which triggers onkeypress, in a script, where normally the invalid event would fire onsubmit (or in a few edge cases).
    Read my native form validation series for a LOT more background information about the differences. (I didn’t think of Daniel’s trick, though. Simple, elegant, useful.)
  • Have a tip for the next Linkbait? Or a comment on this one? Let me know (or here or here).

Linkbait 42

QuirksBlog - Tue, 12/04/2018 - 12:22am

Full-stack edition. Look no further than here.

  • Microsoft is rumoured to pull the plug on their EdgeHTML rendering engine and move to Chromium for the next Edge version. It’s always a sad moment when a rendering engine leaves us.
    Now as anyone who read my Chromium blog posts knows, one Chromium is not necessarily exactly the same as another. Thus I do not expect EdgeChromium to be 100% the same as Chrome — more like 98%.
    How to solve this? Test more in Edge — 3 years ago. Of course my readers know this and do this; it’s the full-stackers that are the problem.
  • Speaking of which. Heydon takes a stab at defining the problem with full-stack developers. They try to do too much, and as a result, code quality suffers.

    By assuming the role of the Full Stack Developer (which is, in practice, a computer scientist who also writes HTML and CSS), one takes responsibility for all the code, in spite of its radical variance in syntax and purpose, and becomes the gatekeeper of at least some kinds of code one simply doesn’t care about writing well.

    He also points out that one full-stack developer is cheaper than three specialised developers, and cost cutting is certainly part of the reason why full-stack has become so popular.
    Still, to me, it all comes down to the low esteem in which HTML/CSS is held (and Heydon also mentions that). It’s much easier to declare a low-status job redundant than a high-status one. I mean, are there large companies that have the same people do development and devops?
  • In case you’re wondering why full-stack programmers have such problems with CSS (and HTML), Robin Rendle explains. Front-end (i.e. HTML and CSS) is not a problem to be solved because, essentially, it’s already been solved. Just not by full-stack programmers.

    What’s the point in learning about vanilla HTML, CSS and JavaScript if they wind up becoming transpiled by other tools and languages?

    [...] Front-end development is complex because design is complex. Transpiling [...] into HTML and CSS requires vim and nuance, and always will. That’s not going to be resolved by a tool but by diligent work over a long period of time.

  • Jeremy compares CSS to a programming language, and finds that selectors, especially, qualify. Still, that’s exactly the part of CSS that isn’t used a lot, since we all have to go modular and use classes everywhere.
    Foor for thought; also for the book.
  • While ostensibly explaining a React feature, Dan Abramov gives a lesson about JavaScript prototypes and inheritance. Useful reading for all JavaScripters — I learned (or relearned) a thing or two here.
  • Addy Osmani takes a look at the cost of JavaScript. While they work OK-ish on modern desktop computers, huge scripts cause a lot of problems on low-end phones. (News? No, not really. But people don’t listen.) As he says,

    The web is bloated by user “experience”

    If you want facts and figures to convince others, look no further than this article.
  • Harry takes an exhaustive dive into CSS and network performance.

    Your page will only render as quickly as your slowest stylesheet.

    If you ever wanted to know absolutely everything about the way stylesheets can block page rendering, read it all. Did you know, for instance, that you should not place stylesheets before async script snippets? That’s because browsers await the full stylesheet before evaluating the snippet. What if the script requests the colour of a certain element, and the CSS hasn’t come in yet? Better to wait.
    Tons more good stuff here.
  • Interesting experiment by Daniel Buchner. He shows how to use the CSS :invalid pseudo, which triggers onkeypress, in a script, where normally the invalid event would fire onsubmit (or in a few edge cases).
    Read my native form validation series for a LOT more background information about the differences. (I didn’t think of Daniel’s trick, though. Simple, elegant, useful.)
  • Have a tip for the next Linkbait? Or a comment on this one? Let me know (or here or here).

Too Much Accessibility

Css Tricks - Mon, 12/03/2018 - 11:16am

I like to blog little veins of thought as I see them. We recently linked to an article by Facundo Corradini calling out a tweet of ours where we used an <em> where we probably should have used an <i>.

Bruce Lawson checks if screen readers are the victims of these semantic mistakes...

Whenever I read “some browsers” or “some screenreaders”, I always ask “but which ones?”, as did Ilya Streltsyn, who asked me “what is the current state of the text-level semantics in HTML reality?”

Léonie Watson to the rescue! Over Twitter, Watters wrote

Most are capable of reporting these things on demand, but do not as standard. So you don’t hear the text/font characteristics being announced as you read, but you can query a character/word etc. to discover its characteristics. This is based on the visual presentation of the text though, rather than through any recognition of the elements themselves

Which I suppose is to say that if you're really fretting about screen readers misinterpreting the nuanced usage of your semantic emphasis... you can relax on that one.

Bruce's article led me toward Steve Faulkner's article "Screen Readers lack emphasis" from 2008.

Using the semantic elements strong and em does not convey any useful information to users of JAWS or Window Eyes under typical browsing conditions. While it is good to know this, it is not a reason to not use these elements to convey meaning. Accessibility is not just about people with vision impairment, it’s about all user’s with disabilities, and web standards is not just about accessibility.

So, not much has changed there in a decade. It's unclear to me if things should change here or not, but if virtual voices are improving, it stands to reason they could get better at voice inflections that convey emphasis. I certainly am thinking of voice emphasis when I write those HTML tags.

This idea of too much accessibility has been a bit of a theme.

Please don't read this article as suggesting that we worry too much about accessibility — only that it's possible to worry about the wrong things and even screw up accessibility in the process, just like anything else.

The post Too Much Accessibility appeared first on CSS-Tricks.

Bridging the Gap Between CSS and JavaScript: CSS-in-JS

Css Tricks - Mon, 12/03/2018 - 4:51am

In this article, we’re going to dig into the concept of CSS-in-JS. If you’re already acquainted with this concept, you might still enjoy a stroll through the philosophy of that approach, and you might be even more interested in the next article.

Web development is very interdisciplinary. We’re used to working closely with multiple languages. And, as developing web applications becomes more commonplace and nuanced, we often look for creative ways to bridge the gaps between those languages to make our development environments and workflows easier and more efficient.

The most common examples are typically when using templating languages. For example, one language might be used to generate the code of a more verbose language (often HTML). This is one of the key aspects of front end frameworks — what does manipulating HTML look like? The most recent twist in this area was JSX because it’s not really a templating language; it’s a syntax extension to JavaScript, and it makes working with HTML really succinct.

Web applications go through many state combinations and it’s often challenging to manage content alone. This is why CSS sometimes falls by the wayside — even though managing styling through different states and media queries is equally important and just as challenging. In this two-part series, I would like to place CSS in the spotlight and explore bridging the gap between it and JavaScript. Throughout this series, I will assume that you’re using a module bundler like webpack. As such, I will use React in my examples, but the same or similar principles are applicable to other JavaScript frameworks, including Vue.

The CSS landscape is evolving in many directions because there are a lot of challenges to solve and there is no "correct" path. I’ve been spending considerable effort experimenting with various approaches, mostly on personal projects, so the intention behind this series is only to inform, not to prescribe.

Challenges of CSS

Before diving into code, it’s worth explaining the most notable challenges of styling web applications. The ones I’ll talk about in this series are scoping, conditional and dynamic styles, and reusability.

Scoping

Scoping is a well-known CSS challenge, it’s the idea of writing styles that don’t leak outside of the component, thus avoid unintended side effects. We would like to achieve it ideally without compromising authoring experience.

Conditional and dynamic styles

While the state in front-end applications started getting more and more advanced, CSS was still static. We were only able to apply sets of styles conditionally — if a button was primary, we would probably apply the class "primary" and define its styles in a separate CSS file to apply how it’s going to look like on the screen. Having a couple of predefined button variations was manageable, but what if we want to have a variety of buttons, like specific ones tailored for Twitter, Facebook, Pinterest and who knows what else? What we really want to do is simply pass a color and define states with CSS like hover, focus, disabled etc. This is called dynamic styling because we’re no longer switching between predefined styles — we don’t know what’s coming next. Inline styles might come to mind for tackling this problem, but they don’t support pseudo-classes, attribute selectors, media queries, or the like.

Reusability

Reusing rulesets, media queries etc. is a topic I rarely see mentioned lately because it’s been solved by preprocessors like Sass and Less. But I’d still like to revisit it in this series.

I will list some techniques for dealing with these challenges along with their limitations in both parts of this series. No technique is superior to the others and they aren’t even mutually exclusive; you can choose one or combine them, depending on what you decide will improve the quality of your project.

Setup

We’ll demonstrate different styling techniques using an example component called Photo. We’ll render a responsive image that may have rounded corners while displaying alternative text as a caption. It will be used like this:

<Photo publicId="balloons" alt="Hot air balloons!" rounded />

Before building the actual component, we’ll abstract away the srcSet attribute to keep the example code brief. So, let’s create a utils.js file with two utilities for generating images of different widths using Cloudinary:

import { Cloudinary } from 'cloudinary-core' const cl = Cloudinary.new({ cloud_name: 'demo', secure: true }) export const getSrc = ({ publicId, width }) => cl.url(publicId, { crop: 'scale', width }) export const getSrcSet = ({ publicId, widths }) => widths .map(width => `${getSrc({ publicId, width })} ${width}w`) .join(', ')

We set up our Cloudinary instance to use the name of Cloudinary’s demo cloud, as well as its url method to generate URLs for the image publicId according to the specified options. We’re only interested in modifying the width in this component.

We’ll use these utilities for the src and srcset attributes, respectively:

getSrc({ publicId: 'balloons', width: 200 }) // => 'https://res.cloudinary.com/demo/image/upload/c_scale,w_200/balloons' getSrcSet({ publicId: 'balloons', widths: [200, 400] }) // => 'https://res.cloudinary.com/demo/image/upload/c_scale,w_200/balloons 200w, https://res.cloudinary.com/demo/image/upload/c_scale,w_400/balloons 400w'

If you’re unfamiliar with srcset and sizes attributes, I suggest reading a bit about responsive images first. That way, you’ll have an easier time following the examples.

CSS-in-JS

CSS-in-JS is a styling approach that abstracts the CSS model to the component level, rather than the document level. This idea is that CSS can be scoped to a specific component — and only that component — to the extent that those specific styles aren’t shared with or leaked to other components, and further, called only when they’re needed. CSS-in-JS libraries create styles at runtime by inserting <style> tags in the <head>.

One of the first libraries to put this concept to use is JSS. Here is and example employing its syntax:

import React from 'react' import injectSheet from 'react-jss' import { getSrc, getSrcSet } from './utils' const styles = { photo: { width: 200, '@media (min-width: 30rem)': { width: 400, }, borderRadius: props => (props.rounded ? '1rem' : 0), }, } const Photo = ({ classes, publicId, alt }) => ( <figure> <img className={classes.photo} src={getSrc({ publicId, width: 200 })} srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })} sizes="(min-width: 30rem) 400px, 200px" /> <figcaption>{alt}</figcaption> </figure> ) Photo.defaultProps = { rounded: false, } export default injectSheet(styles)(Photo)

At first glance, the styles object looks like CSS written in object notation with additional features, like passing a function to set the value based on props. The generated classes are unique, so you never need to worry about them clashing with other styles. In other words, you get scoping for free! This is how most CSS-in-JS libraries work — of course, with some twists in features and syntax that we’ll cover as we go.

You can see by the attributes that the width of our rendered image starts at 200px, then when the viewport width becomes at least 30rem, the width increases to 400px wide. We generated an extra 800 source to cover even larger screen densities:

  • 1x screens will use 200 and 400
  • 2x screens will use 400 and 800

styled-components is another CSS-in-JS library, but with a much more familiar syntax that cleverly uses tagged template literals instead of objects to look more like CSS:

import React from 'react' import styled, { css } from 'styled-components' import { getSrc, getSrcSet } from './utils' const mediaQuery = '(min-width: 30rem)' const roundedStyle = css` border-radius: 1rem; ` const Image = styled.img` width: 200px; @media ${mediaQuery} { width: 400px; } ${props => props.rounded && roundedStyle}; ` const Photo = ({ publicId, alt, rounded }) => ( <figure> <Image src={getSrc({ publicId, width: 200 })} srcSet={getSrcSet({ publicId, widths: [200, 400, 800] })} sizes={`${mediaQuery} 400px, 200px`} rounded={rounded} /> <figcaption>{alt}</figcaption> </figure> ) Photo.defaultProps = { rounded: false, } export default Photo

We often create semantically-neutral elements like <div> and <span> solely for styling purposes. This library, and many others, allow us to create and style them in a single motion.

My favorite benefit of this syntax is that it’s like regular CSS, minus interpolations. This means that we can migrate our CSS code more easily and we get to use our existing muscle memory instead of having to familiarize ourselves with writing CSS in the object syntax.

Notice that we can interpolate almost anything into our styles. This specific example demonstrates how we can save the media query in the variable and reuse it in multiple places. Responsive images are an excellent use case for this because the sizes attribute contains basically CSS, so we can use JavaScript to make the code more DRY.

Let’s say that we decided we want to visually hide the caption, but still make it accessible for screen readers. I know that a better way of achieving this would be to use an alt attribute instead, but let’s use a different way for the sake of this example. We can use a library of style mixins called polished — it works great with CSS-in-JS libraries making it great for our example. This library includes a mixin called hideVisually which does exactly what we want and we can use it by interpolating its return value:

import { hideVisually } from 'polished' const Caption = styled.figcaption` ${hideVisually()}; ` <Caption>{alt}</Caption>

Even though hideVisually outputs an object, the styled-components library knows how to interpolate it as styles.

CSS-in-JS libraries have many advanced features like theming, vendor prefixing and even inlining critical CSS, which makes it easy to stop writing CSS files entirely. At this point, you can start to see why CSS-in-JS becomes an enticing concept.

Downsides and limitations

The obvious downside to CSS-in-JS is that it introduces a runtime: the styles need to be loaded, parsed and executed via JavaScript. Authors of CSS-in-JS libraries are adding all kinds of smart optimizations, like Babel plugins, but some runtime costs will nevertheless exist.

It’s also important to note that these libraries aren’t being parsed by PostCSS because PostCSS wasn’t designed to be brought into the runtime. Many use stylis instead as a result because it’s much faster. This means that we unfortunately can’t use PostCSS plugins.

The last downside I’ll mention is the tooling. CSS-in-JS is evolving at a really fast rate and text editor extension, linters, code-formatters etc. need to play catch-up with new features to stay on par. For example, people are using the VS Code extension styled-components for similar CSS-in-JS libraries like emotion, even though they don’t all have the same features. I’ve even seen API choices of proposed features being influenced by the goal of retaining syntax highlighting!

The future

There are two new CSS-in-JS libraries, Linaria and astroturf, that have managed zero runtime by extracting CSS into files. Their APIs are similar to styled-components, but they vary in features and goals.

The goal of Linaria is to mimic the API of CSS-in-JS libraries like styled-components by having built-in features like scoping, nesting and vendor prefixing. Conversely, astroturf is built upon CSS Modules, has limited interpolation capabilities, and encourages using a CSS ecosystem instead of deferring to JavaScript.

I built Gatsby plugins for both libraries if you want to play with them:

Two things to have in mind when using these libraries:

  1. having actual CSS files means that we can process them with familiar tools like PostCSS
  2. Linaria uses custom properties (a.k.a. CSS variables) under the hood, be sure to take their browser support into consideration before using this library
Conclusion

CSS-in-JS are all-in-one styling solutions for bridging the gap between CSS and JavaScript. They are easy to use and they contain useful built-in optimizations — but all of that comes at a cost. Most notably, by using CSS-in-JS, we’re essentially ejecting from the CSS ecosystem and deferring to JavaScript to solve our problems.

Zero-runtime solutions mitigate some of the downsides by bringing back the CSS tools, which ascends the CSS-in-JS discussion to a much more interesting level. What are the actual limitations of preprocessing tools compared to CSS-in-JS? This will be covered in the next part of this series.

Article Series:
  1. CSS-in-JS (This post)
  2. CSS Modules, PostCSS and the Future of CSS

The post Bridging the Gap Between CSS and JavaScript: CSS-in-JS appeared first on CSS-Tricks.

Blue Beanie Day 2018

Css Tricks - Fri, 11/30/2018 - 5:21am

Another year!

You better not cry, you better not shout, I’m telling you why: @BlueBeanieDay is coming Nov. 30! Start sharing your #bbd photos, links, articles, and videos now: https://t.co/3US4vHBsDR#a11y #WebStandards #InclusiveDesign #ProgressiveEnhancement pic.twitter.com/AiV3ktRqka

— zeldman (@zeldman) October 24, 2018

I feel the same this year as I have in the past. Web standards, as an overall idea, has entirely taken hold and won the day. That's worth celebrating, as the web would be kind of a joke without them. So now, our job is to uphold them. We need to cry foul when we see a browser go rogue and ship an API outside the standards process. That version of competition is what could lead the web back to a dark place where we're creating browser-specific versions. That becomes painful, we stop doing it, and slowly, the web loses.

Direct Link to ArticlePermalink

The post Blue Beanie Day 2018 appeared first on CSS-Tricks.

Nesting Components in Figma

Css Tricks - Fri, 11/30/2018 - 5:06am

For the past couple of weeks, I’ve been building our UI Kit at Gusto, where I work, and this is a Figma document that contains all of our design patterns and components so that designers on our team can hop in, go shopping for a component that they need, and then get back to working on the problem that they’re trying to solve.

There’s a couple things that I’ve learned since I started. First, building a UI Kit is immensely delicate work and takes a really long time (although it happens to be very satisfying all the while). But, most importantly, embedding Figma components within other components is sort of magic.

Here’s why.

First, it’s important to note that I’ve tried to break down our components into the smallest, littlest chunks. So, for example, our Breadcrumbs, Tabs, and Progress Bar components are all separate from one another and I’ve dumped them all into a Symbols page.

Here’s an example of how I’ve started to build our form elements:

From what I can tell, this is how a lot of UI Kits are designed — there’s a welcome page that introduces what this document is and how to use it; there’s a symbols page that the design systems folks will maintain that has everything from buttons to forms inside it as symbols or components; and then there’s typically another page that has examples of these symbols that represent the final application.

Shopify’s design system, Polaris, does also this with their Sketch file, but so do a lot of examples I’ve seen from other big design teams:

But anyway, going back to my design in Figma — notice below that a forward slash (/) is used in the name of ProgressBar/Two and ProgressBar/Three components.

Well, that’s Figma’s naming convention for identifying Instances. What this means is that when a designer drags in the ProgressBar component from the UI Kit, they can switch between different options, like this:

That’s nifty! But once I broke up our UI into these tiny components, I started to wonder how I might combine these pieces together to make things even easier for our design team. I soon realized that in our app we have navigation items like breadcrumbs or progress bars but they always have a title associated with them. Once I figured that out, I started a series of new components called Header/Default, Header/Breadcrumbs, Header/ProgressBar, etc., which have all these components embedded within them.

So, now when a designer drags in the Header component into their mockups, they can do the following:

We’re switching between the different Header instances there and that doesn’t look like much, yet. But! Since we’re nesting components within our Header component, designers can jump down into the subcomponents, like ProgressBar and update that, too:

How neat is that? And again, this doesn’t look particularly useful just yet but nesting components within larger components means that you can start to use them in clever ways.

Where this gets interesting is here: at Gusto, we have two different UIs for our types of customers. We have admins that run payroll and then their employees that can access their account to see how much they’ve been paid. There’s different navigation and options for both, so I created two components for them: Frame/Admin and Frame/Employee.

These two components have the sidebar and navigation items but are then placed into a separate component called Layout/Default where we’ve placed our Header component. But since these components are instances and nested together, we can begin to click-clack bits of the UI together to get the precise interface that we want.

Now, whenever designers need to switch between different UIs, they can use these nested components and instances to toggle between them super fast. I’ve only just started experimenting with this but the idea is that by using these nested components you give folks a way to toggle between the different variants inside them whilst also providing a nice API for larger layouts.

If you’re using instances in Figma, Sketch, or another design tool — let me know! I’m constantly on the lookout for improving things here, but I think this is certainly a good start.

The post Nesting Components in Figma appeared first on CSS-Tricks.

Embed a Blog Onto Any Website With DropInBlog

Css Tricks - Fri, 11/30/2018 - 5:02am

With DropInBlog, you can embed a blog into your site in only three minutes. A quick JavaScript/HTML widget, or a full-featured JSON API, is all it takes.

A headless blog you can take anywhere

Ever been working on your existing static site or anything that wasn’t built with WordPress, wanted to integrate a blog, but then couldn’t find a clean solution?

DropInBlog to the rescue.

Now you can quickly integrate a full featured blog into any existing site. Actually, any existing page. Drop it right into your existing template. It’s so easy you can be up and running in just a few minutes. Check out this live example:

See Project

Blogging is simple with the full-featured editor

You’ll feel at home with the simple admin panel. Effortlessly create posts, categories, and authors. Tweak the blog output settings and enable features like share buttons, Disqus or Facebook comments with a couple of clicks.

Try it Free

Embed your blog in no time

You can embed your blog on a site in about three quick minutes. From there, you have a ton of control to customize and even put a variety of widgets to use, like Author List, Recent Post List, Category List... and more!

Check out this short video to see how quickly you can get up and running:

Need help getting setup? Hit up their friendly support on Messenger.

Simple JavaScript or JSON API... it’s your call.

There are a few integration options ranging from the drop-dead simple JavaScript method to the super flexible JSON API. You get to choose what works best for you.

Ready to give DropInBlog a try? Get started for free.

The post Embed a Blog Onto Any Website With DropInBlog appeared first on CSS-Tricks.

DevTools for Designers

Css Tricks - Thu, 11/29/2018 - 12:40pm

This is such an interesting conversation thread that keeps popping up year after year. The idea is that there could (and perhaps should) be in-browser tooling that helps web designers do their job. This tooling already exists to some degree. Let's check in on perspectives from a wide array of people and companies who have shared thoughts on this topic.

Ahmad Shadeed wrote for us last year about how DevTools can be useful to designers in a number of ways, like changing state, content, colors, variables, etc.:

Editing things visually like that will give [designers] more control over some design details, they can tweak things in the browser and show the result to the developer to be implemented.

In a post titled, "A DevTools for Designers", A.J. Kandy wrote that, just because you're a designer, it doesn't mean you don't know how to code — but you might not be production-level and might be faster elsewhere:

I can edit front-end markup; I’m just way faster at drawing rectangles and arranging them into user interfaces. I’m technical, but not a coder.

It sparked a lot of responses and thoughts back when we originally shared the post:

It's one thing to augment the existing DevTools to be better for designers. Firefox has done great work in that area with stuff like their animation tooling, flexbox and grid inspectors. At the same time, it's also nice to see entirely fresh takes on how we can approach it! For example, Google dropped VisBug, an extension with designers squarely in mind. The video is only 30 seconds:

There have been a lot of opinions about browser extensions that allow design editing over the years. Check out options like Stylebot (Chrome store link).

There is another visual design browser plugin called Visual Inspector:

Don't forget this classic trick:

My favourite trick to do in devTools is probably `document.designMode = "on"`. Turn it on and start editing text on any element on a website. Super cool! https://t.co/bdV9yONayT pic.twitter.com/rkC0ZsTCcD

— Simon Vrachliotis (@simonswiss) November 14, 2018

Oliver Williams wrote the following in "The ultimate web design tool: a browser":

Browser dev tools were traditionally useful for debugging JavaScript and inspecting network requests. More recently, we’ve seen browsers add graphical interfaces for manipulating CSS. Most browsers offer a color picker and eyedropper tool for selecting colors. In Chrome, this tool will helpfully display a color-contrast ratio. Chrome also offers a GUI for adding or tweaking text-shadow and box-shadow.

Perhaps design tooling will lead us in this direction in a big way?

✨isual
✨ evelopment
✨ nvironments

☝️Just like most code-based developers use IDEs to develop software today, we're going to start seeing multiple new VDEs emerge that enable a primarily-visual way of designing and shipping software.

— Vlad Magdalin (@callmevlad) November 16, 2018

Vlad works with Webflow, so you can see where he's coming from with that.

Jye SR chimed in with his post, "5 Ways DevTools Made My Life Easier":

... it’s possible to use Chrome DevTools to investigate competitors, see what’s not working with add-ons, change your viewport, understand page load timings and edit the web; all of which can help digital marketers, product managers or anyone working with a website to do their job more efficiently. It’s a tool which I use every day and I hope that you will too!

Hard to look at all that and not see this is where tooling is headed.

The post DevTools for Designers appeared first on CSS-Tricks.

Preventing Content Reflow From Lazy-Loaded Images

Css Tricks - Thu, 11/29/2018 - 5:03am

You know the concept of lazy loading images. It prevents the browser from loading images until those images are in (or nearly in) the browser's viewport.

There are a plethora of JavaScript-based lazy loading solutions. GitHub has over 3,400 different lazy load repos, and those are just the ones with "lazy load" in a searchable string! Most of them rely on the same trick: Instead of putting an image's URL in the src attribute, you put it in data-src — which is the same pattern for responsive images:

  • JavaScript watches the user scroll down the page
  • When the use encounters an image, JavaScript moves the data-src value into src where it belongs
  • The browser requests the image and it loads into view

The result is the browser loading fewer images up front so that the page loads faster. Additionally, if the user never scrolls far enough to see an image, that image is never loaded. That equals faster page loads and less data the user needs to spend.

"This is amazing!" you may be thinking. And, you’re right... it is amazing!

That said, it does indeed introduce a noticeable problem: images not containing the src attribute (including when it’s empty or invalid) have no height. This means that they're not the right size in the page layout until they're lazy-loaded.

When a user scrolls and images are lazy-loaded, those img elements go from a height of 0 pixels to whatever they need to be. This causes reflow, where the content below or around the image gets pushed to make room for the freshly loaded image. Reflow is a problem because it's a user-blocking operation. It slows down the browser by forcing it to recalculate the layout of any elements that are affected by that image's shape. The CSS scroll-behavior property may help here at some point, but its support needs to improve before it’s a viable option.

Lazy loading doesn't guarantee that the image will fully load before it enters the viewport. The result is a perceived janky experience, even if it’s a big performance win.

There are other issues with lazy loading images that are worth mentioning but are outside the scope of this post. For example, if JavaScript fails to run at all, then no images will load on the page. That’s a common concern for any JavaScript-based solution but this article only concerned with solving the problems introduced by reflow.

If we could force pre-loaded images to maintain their normal width and height (i.e. their aspect ratio), we could prevent reflow problems while still lazy loading them. This is something I recently had to solve building a progressive web app at DockYard where I work.

For future reference, there's an HTML attribute called intrinsicsize that's designed to preserve the aspect ratio, but right now, that's just experimental in Chrome.

Here’s how we did it.

Maintaining aspect ratio

There are many ways to go about the way we can maintain aspect ratios. Chris once rounded up an exhaustive list of options, but here’s what we’re looking at for image-specific options.

The image itself

The image src provides a natural aspect ratio. Even when an image is resized responsively, its natural dimensions still apply. Here's a pretty common bit of responsive image CSS:

img { max-width: 100%; height: auto; }

That CSS is telling images not to exceed the width of the element that contains them, but to scale the height properly so that there's no "stretching" or "squishing" as the image is resized. Even if the image has inline height and width attributes, this CSS will keep them behaving nicely on small viewports.

However, that "natural aspect ratio" behavior breaks down if there's no src yet. Browsers don't care about data-src and don't do anything with it, so it’s not really a viable solution for lazy loading reflow; but it is important to help understand the "normal" way images are laid out once they've loaded.

A pseudo-element

Many developers — including myself — have been frustrated trying to use pseudo-elements (e.g. ::before and ::after) to add decorations to img elements. Browsers don't render an image’s pseudo-elements because img is a replaced element, meaning its layout is controlled by an external resource.

However, there is an exception to that rule: If an image’s src attribute is invalid, browsers will render its pseudo-elements. So, if we store the src for an image in data-src and the src is empty, then we can use a pseudo-element to set an aspect ratio:

[data-src]::before { content: ''; display: block; padding-top: 56.25%; }

That'll set a 16:9 aspect ratio on ::before for any element with a data-src attribute. As soon as the data-src becomes the src, the browser stops rendering ::before and the image's natural aspect ratio takes over.

Here’s a demo:

See the Pen Image Aspect Ratio: ::before padding by James Steinbach (@jdsteinbach) on CodePen.

There are a couple drawbacks to this solution, however. First, it relies on CSS and HTML working together. Your stylesheet needs to have a declaration for each image aspect ratio you need to support. It would be much better if the template could insert an image without needing CSS edits.

Second, it doesn't work in Safari 12 and below, or Edge, at the time of writing. That's a pretty big traffic swatch to send poor layouts. To be fair, maintaining the aspect ratio is a bit of a progressive enhancement — there's nothing "broken" about the final rendered page. Still, it’s much more ideal to solve the reflow problem and for images to render as expected.

Data URI (Base64) PNGs

Another way we attempted to preserve the aspect ratio was to inline data URI for the src. as PNG. Using png-pixel.com will help with the lift of all that base64-encoding with any dimensions and colors. This can go straight into the image's src attribute in the HTML:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAQAAAA3fa6RAAAADklEQVR42mNkAANGCAUAACMAA2w/AMgAAAAASUVORK5CYII=" data-src="//picsum.photos/900/600" alt="Lazy loading test image" />

The inline PNG there has a 3:2 aspect ratio (the same aspect ratio as the final image). When src is replaced with the data-src value, the image will maintain its aspect ratio exactly like we want!

Here’s a demo:

See the Pen Image Aspect Ratio: inline base64 PNG by James Steinbach (@jdsteinbach) on CodePen.

And, yes, this approach also comes with some drawbacks. Although the browser support is much better, it's complicated to maintain. We need to generate a base64 string for each new image size, then make that object of strings available to whatever templating tool that’s being used. It's also not the most efficient way to represent this data.

I kept exploring and found a smaller way.

Combine SVG with base64

After exploring the inline PNG option, I wondered if SVG might be a smaller format for inline images and here's what I found: An SVG with a viewBox declaration is a placeholder image with an easily editable native aspect ratio.

First, I tried base64-encoding an SVG. Here's an example of what that looked like in my HTML:

<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzIDInPjwvc3ZnPg==" data-src="//picsum.photos/900/600" alt="Lazy loading test image">

On small, simple aspect ratios, this is roughly equivalent in size to the base64 PNGs. A 1:1 ratio would be 114 bytes with base64 PNG and 106 bytes with base64 SVG. A 2:3 ratio is 118 bytes with base64 PNG and 106 bytes with base64 SVG.

However, using base64 SVG for larger, more complex ratios stay small, which is a real winner in file size. A 16:9 ratio is 122 bytes in base64 PNG and 110 bytes in base64 SVG. A 923:742 ratio is 3,100 bytes in base64 PNG but only 114b in base64 SVG! (That's not a common aspect ratio, but I needed to test with custom dimensions with my client's use case.)

Here’s a table to see those comparisons more clearly:

Aspect Ratio base64 PNG base64 SVG 1:1 114 bytes 106 bytes 2:3 118 bytes 106 bytes 16:9 122 bytes 110 bytes 923:742 3,100 bytes 114 bytes

The differences are negligible with simple ratios, but you can see how extremely well SVG scales as ratios become complex.

We've got much better browser support now. This technique is supported by all the big players, including Chrome, Firefox, Safari, Opera, IE11, and Edge, but also has great support in mobile browsers, including Safari iOS, Chrome for Android, and Samsung for Android (from 4.4 up).

Here's a demo:

See the Pen Image Aspect Ratio: inline base64 SVG by James Steinbach (@jdsteinbach) on CodePen.

&#x1f3c6; We have a winner!

Yes, we do, but stick with me as we improve this approach even more! I remembered Chris suggesting that we should not use base64 encoding with SVG inlined in CSS background-images and thought that advice might apply here, too.

In this case, instead of base64-encoding the SVGs, I used the "Optimized URL-encoded" technique from that post. Here's the markup:

<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3C/svg%3E" data-src="//picsum.photos/900/600" alt="Lazy loading test image" />

This is just a tad smaller than base64 SVG. The 1:1 is 106 bytes in base64 and 92 bytes when URL-encoding. 16:9 outputs 110 bytes in base64 and 97 bytes when URL-encoded.

If you're interested in more data size by file and encoding format, this demo compares different byte sizes between all of these techniques.

However, the real benefits that make the URL-encoded SVG a clear winner are that its format is human-readable, easily template-able, and infinitely customizable!

You don't need to create a CSS block or generate a base64 string to get a perfect placeholder for images where the dimensions are unknown! For example, here's a little React component that uses this technique:

const placeholderSrc = (width, height) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E` const lazyImage = ({url, width, height, alt}) => { return ( <img src={placeholderSrc(width, height)} data-src={url} alt={alt} /> ) }

See the Pen React LazyLoad Image with Stable Aspect Ratio by James Steinbach (@jdsteinbach) on CodePen.

Or, if you prefer Vue:

See the Pen Vue LazyLoad Image with Stable Aspect Ratio by James Steinbach (@jdsteinbach) on CodePen.

I'm happy to report that browser support hasn't changed with this improvement — we've still got the full support as base64 SVG!

Conclusion

We've explored several techniques to prevent content reflow by preserving the aspect ratio of a lazy-loaded image before the swap happens. The best technique I was able to find is inlined and optimized URL-encoded SVG with image dimensions defined in the viewBox attribute. That can be scripted with a function like this:

const placeholderSrc = (width, height) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`

There are several benefits to this technique:

  • Solid browser support across desktop and mobile
  • Smallest byte size
  • Human-readable format
  • Easily templated without run-time encoding calls
  • Infinitely extensible

What do you think of this approach? Have you used something similar or have a completely different way of handling reflow? Let me know!

The post Preventing Content Reflow From Lazy-Loaded Images appeared first on CSS-Tricks.

What If?

Css Tricks - Wed, 11/28/2018 - 11:08am

Harry Roberts writes about working on a project with a rather nasty design flaw. The website was entirely dependent on images loading before rendering any of the content. He digs into why this bad for accessibility and performance but goes further to describe how this can ripple into other problems:

While ever you build under the assumption that things will always work smoothly, you’re leaving yourself completely ill-equipped to handle the scenario that they don’t. Remember the fallacies; think about resilience.

Harry then suggests that we should always ask ourselves a key question when developing a website: what if this image doesn’t load? For example, if the user is on a low-end device, using a flakey network, using an obscure browser, looking at the site without a crucial API or feature available... you get the idea.

While we're on this note, we asked what makes a good front-end developer a little while back and I think this is the best answer to that question after reading Harry's post: a good front-end developer is constantly asking themselves, "What if?"

Direct Link to ArticlePermalink

The post What If? appeared first on CSS-Tricks.

Front-End Developers Have to Manage the Loading Experience

Css Tricks - Wed, 11/28/2018 - 4:49am

Web performance is a huge complicated topic. There are metrics like total requests, page weight, time to glass, time to interactive, first input delay, etc. There are things to think about like asynchronous requests, render blocking, and priority downloading. We often talk about performance budgets and performance culture.

How that first document comes down from the server is a hot topic. That is where most back-end related performance talk enters the picture. It gives rise to architectures like the JAMstack, where gosh, at least we don't have to worry about index.html being slow.

Images have a performance story all to themselves (formats! responsive images!). Fonts also (FOUT'n'friends!). CSS also (talk about render blocking!). Service workers can be involved at every level. And, of course, JavaScript is perhaps the most talked about villain of performance. All of this is balanced with perhaps the most important general performance concept: perceived performance. Front-end developers already have a ton of stuff we're responsible for regarding performance. 80% is the generally quoted number and that sounds about right to me.

For a moment, let's assume we're going to build a site and we're not going to server-side render it. Instead, we're going to load an empty document and kick off data API calls as quickly as we can, then render the site with that data. Not a terribly rare scenario these days. As you might imagine, >we now have another major concern: handling the loading experience.

I mused about this the other day. Here's an example:

Client side rendering is so interesting. Look at this janky loading experience. The page itself isn't particularly slow, but it loads in very awkwardly. A whole thing front-end devs are going to have to get good at. pic.twitter.com/sMcD4nsL98

— Chris Coyier (@chriscoyier) October 30, 2018

I'd say that loading experience is pretty janky, and I'm on about the best hardware and internet connection money can buy. It's not a disaster and surely many, many thousands of people use this particular site successfully every day. That said, it doesn't feel fast, smooth, or particularly nice like you'd think a high-budget website would in these Future Times.

Part of the reason is probably because that page isn't server-side rendered. For whatever reason (we can't really know from the outside), that's not the way they went. Could be developer efficiency, security, a temporary state during a re-write... who knows! (It probably isn't ignorance.)

What are we to do? Well, I think this is a somewhat new problem in front-end development. We've told the browser: "Hey, we got this. We're gonna load things all out of order depending on when our APIs cough stuff up to us and our front-end framework decides it's time to do so." I can see the perspective here where this isn't ideal and we've given up something that browsers are incredibly good at only to do it less well ourselves. But hey, like I've laid out a bit here, the world is complicated.

What is actually happening is that these front-end frameworks are aware of this issue and are doing things to help manage it. Back in April of this year, Dan Abramov introduced React Suspense. It seems like a tool for helping front-end devs like us manage the idea that we now need to deal with more loading state stuff than we ever have before:

At about 14 minutes, he gets into fetching data with placeholder components, caching and such. This issue isn't isolated to React, of course, but keeping in that theme, here's a conference talk by Andrew Clark that hit home with me even more quickly (but ultimately uses the same demo and such):

Just the idea of waiting to show spinners for a little bit can go a long way in de-jankifying loading.

Mikael Ainalem puts a point on this in a recent article, A Brief History of Flickering Spinners. He explains more clearly what I was trying to say:

One reason behind this development is the change we’ve seen in asynchronous programming. Asynchronous programming is a lot easier than it used to be. Most modern languages have good support for loading data on the fly. Modern JavaScript has incorporated Promises and with ES7 comes the async and await keywords. With the async/await keywords one can easily fetch data and process it when needed. This means that we need to think a step further about how we show users that data is loading.

Plus, he offers some solutions!

See the Pen Flickering spinners by Mikael Ainalem (@ainalem) on CodePen.

We've got to get better at this.

The post Front-End Developers Have to Manage the Loading Experience appeared first on CSS-Tricks.

Syndicate content
©2003 - Present Akamai Design & Development.