Tag: practical

A Practical Tip For Using Sass Default Parameters

Sass offers functions and mixins that accept parameters. You can use Sass default parameters, that is, parameters that have a value even if you don’t provide them when the function or mixin is called.

Let’s focus on mixins here. Here’s the syntax of a mixin:

@mixin foo($  a, $  b, $  c) {   // I can use $  a, $  b, and $  c in here, but there is a risk they are null }  .el {   @include foo(1, 2, 3);    // if I tried to do `@include foo;`   // ... which is valid syntax...    // I'd get `Error: Missing argument $  a.` from Sass }

It’s safer and more useful to set up default parameters in this Sass mixin:

@mixin foo($  a: 1, $  b: 2, $  c: 3) { }  .el {   // Now this is fine   @include foo;    // AND I can send in params as well   @include foo("three", "little", "pigs"); }

But what if I wanted to send in $ b and $ c, but leave $ a as the Sass default parameter? The trick is that you send in named parameters:

@mixin foo($  a: 1, $  b: 2, $  c: 3) { }  .el {   // Only sending in the second two params, $  a will be the default.   @include foo($  b: 2, $  c: 3); }

A real-life example using Sass default parameters

Here’s a quick-y mixin that outputs what you need for very basic styled scrollbars (Kitty has one as well):

@mixin scrollbars(   $  size: 10px,   $  foreground-color: #eee,   $  background-color: #333 ) {   // For Google Chrome   &::-webkit-scrollbar {     width: $  size;     height: $  size;   }   &::-webkit-scrollbar-thumb {     background: $  foreground-color;   }   &::-webkit-scrollbar-track {     background: $  background-color;   }    // Standard version (Firefox only for now)   scrollbar-color: $  foreground-color $  background-color; }

Now I can call it like this:

.scrollable {   @include scrollbars; }  .thick-but-otherwise-default-scrollable {   // I can skip $  b and $  c because they are second and third   @include scrollbars(30px); }  .custom-colors-scrollable {   // I can skip the first param if all the others are named.   @include scrollbars($  foreground-color: orange, $  background-color: black); }  .totally-custom-scrollable {   @include scrollbars(20px, red, black); }

I’m just noting this as I had to search around a bit to figure this out. I was trying stuff like sending empty strings or null as the first parameter in order to “skip” it, but that doesn’t work. Gotta do the named parameter approach.


A Practical Tip For Using Sass Default Parameters originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , ,

7 Practical Uses for the ::before and ::after Pseudo-Elements in CSS

CSS ::before and ::after pseudo-elements allow you to insert “content” before and after any non-replaced element (e.g. they work on a <div> but not an <input>). This effectively allows you to show something on a web page that might not be present in the HTML content. You shouldn’t use it for actual content because it’s not very accessible in that you can’t even select and copy text inserted on the page this way —  it’s just decorative content.

In this article, I’ll walk you through seven different examples that showcase how ::before and ::after can be used to create interesting effects.

Note that for most examples, I am only explaining the parts of the code that deal specifically with CSS pseudo-elements. That said, all of the CSS is available in the embedded demos if you want to see the code for additional styling.

Styling Broken images

When a user visits your website, their internet connection (or a factor beyond your control) might prevent your images from downloading and, as a result, the browser shows a broken image icon and and the image’s alt text (if it’s actually there).

A broken image icon with alt text that says Red and white Chevrolet.

How about showing a custom placeholder instead? You can pull this off using ::before and ::after with a bit of CSS positioning.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text centered in a gray placeholder area that says Red and white Chevrolet.

First, we need to use relative positioning on the image element. We are going to use absolute positioning on one of the pseudo-elements in a bit, so this relative position makes sure make sure the pseudo-element is positioned within the content of the image element, rather than falling completely out of the document flow.

img {   display: block; /* Avoid the space under the image caused by line height */   position: relative;   width: 100% }

Next, let’s create the region of the broken image effect using the image’s ::before pseudo-element. We’re going to style this with a light gray background and slightly darker border to start.

img::before {   background-color: hsl(0, 0%, 93.3%);   border: 1px dashed hsl(0, 0%, 66.7%);   /* ... */ }

<img> is a replaced element. Why are you using ::before pseudo-element on it? It wont work!. Correct. In this scenario the pseudo-element will show in Chrome and Firefox when the image fails to load, which is exactly what you want. Meanwhile, Safari only shows the styling applied to the alt text.

The styling is applied to the top-left corner of the broken image.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text that says Red and white Chevrolet.

So far, so good. Now we can make it a block-level element (display: block) and give it a height that fills the entire available space.

img::before {   /* ... */   display: block;   height: 100%; }
Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text that says Red and white Chevrolet inside of a gray placeholder area. That area is highlighted with a red border.
Things are already starting to look better!

We can refine the style a little more. For example, let’s round the corners. We should also give the alt text a little breathing room by giving the pseudo-element full width and absolute positioning for better control placing things where we want.

img::before {   /* ... */   border-radius: 4px;   content: "";   position: absolute;   width: 100%; }

If you stopped here and checked your work, you might be scratching your head because the alt text is suddenly gone.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows a gray placeholder area.

That’s because we set content to an empty string (which we need to display our generated content and styles) and cover the entire space, including the actual alt text. It’s there, we just can’t see it.

We can see it if we display the alt text in an alternate (get it?) way, this time with help form the ::after pseudo-element. The content property is actually capable of displaying the image’s alt attribute text using the attr() function:

img::after {   content: attr(alt);    /* Some light styling */   font-weight: bold;   position: absolute;   height: 100%;   left: 0px;   text-align: center;   top: 1px;   width: 100%; }

This is awesome! In Chrome, at least.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text inside of a gray placeholder area.

But, in Firefox, not so much.

Two card elements, both with a large image, a title, and a description. The card on the left has a red Chevrolet Camaro image. The card on the right shows alt text on top of alt text inside of a gray placeholder area.
The generated content is colliding with the actual alt text in Firefox.

A quick fix is to target the alt attribute directly using an attribute selector (in this case, img[alt]), and target similar styles there so things match up with Chrome.

img[alt] {   text-align: center;   font-weight: bold;   color: hsl(0, 0%, 60%); }

Now we have a great placeholder that’s consistent in Chrome and Firefox.

Custom blockquote

Blockquotes are quotes or an excerpts from a cited work. They’re also provide a really great opportunity to break up a wall of text with something that’s visually interesting.

There are all kinds of ways to style blockquotes. Chris has a set of five styles that go all the way back to 2007.

I want to look at another technique, one that incorporates ::before and ::after. Like we saw with the last example, we can use the content property to display generated content, and apply other properties to dress it up. Let’s put large quotation marks at the start and end of a blockquote.

Firefox 91

The HTML is straightforward:

<blockquote>   <!-- Your text here --> </blockquote>

A few cosmetics in the CSS:

blockquote {   font-style: italic;   line-height: 1.618;   font-size: 1.2em;   width: 30em;   position: relative;   padding: 40px 80px; }

Note the position: relative in there because, as you’ll learn, it’s essential for positioning the blockquotes.

As you’ve probably guessed, we’re going to use ::before for the first quotation mark, and ::after for the closing one. Now, we could simply call the content property on both and generate the marks in there. But, CSS has us covered with open-quote and close-quote values.

blockquote::before {   content: open-quote;   /* Place it at the top-left */   top: 0;   left: 0; }  blockquote::after {   content: close-quote;   /* Place it at thee bottom-right */   bottom: 0;   right: 0; }

This gets us the quotation marks we want, but allow me to button up the styles a bit:

blockquote::before, blockquote::after {   background-color: #cccccc;   display: block;   width: 60px;   height: 60px;   line-height: 1.618;   font-size: 3em;   border-radius: 100%;   text-align: center;   position: absolute; }

Icon Bullet List

We have ordered (<ol>) and unordered (<ul>) lists in HTML. Both have default styling dictated by the browser’s User Agent stylesheet. But with ::before pseudo-element, we can override those “default” styles with something of our own. And guess what? We can use emojis (😊) on the content property!

.name-list li::before {   content: "😊";   margin-right: 15px;   font-size: 20px; }

While this is great and all, it’s worth noting that we could actually reach for the ::marker pseudo-element, which is designed specifically for styling list markers. Eric Meyer shows us how that works and it’s probably a better way to go in the long run.

Animated toggle switch

One of the neatest tricks for styling forms is creating a toggle switch out of a standard HTML checkbox. In fact, Preethi Sam recently shared one approach for it when showing off a handful of other checkbox styling tricks using CSS masks.

True to its name, a toggle switch is used to toggle or switch between the checked and unchecked states of a checkbox element.

<form class="container">   <label class="switch">     <input type="checkbox" />   </label> </form>

The customization is all thanks to modifications added to the <input> element via the ::before and ::after pseudo-elements. But first, here is some baseline CSS for the <form> element:

.container {   background: #212221;   background: linear-gradient(to right, #1560bd, #e90);   border-radius: 50px;   height: 40px;   position: relative;   width: 75px;     }
We’re not quite there, but see how the checkbox element is displayed.

We’re going to “hide” the checkbox’s default appearance while making it take up the full amount of space. Weird, right? It’s invisible but still technically there. We do that by:

  • changing its position to absolute,
  • setting the appearance to none, and
  • setting its width and height to 100%.
input {   -webkit-appearance: none; /* Safari */   cursor: pointer; /* Show it's an interactive element */   height: 100%;   position: absolute;   width: 100%; }

Now, let’s style the <input> element with its ::before pseudo-element. This styling will change the appearance of the input, bringing us closer to the final result.

input::before {   background: #fff;   border-radius: 50px;   content: "";   height: 70%;   position: absolute;   top: 50%;   transform: translate(7px, -50%); /* Move styling to the center of the element */   width: 85%; }

What, wait? You’d think that ::before wouldn’t work with a replaced element, like <input>. And that’s true, but only when the input type is image which is equivalent to an <img> element. Other form controls, like a checkbox, are defined as non-replaced elements in the HTML spec.

Next, we need to create the “toggle” button and it just so happens we still have the ::after pseudo-element available to make it. But, there are two things worth mentioning:

  1. The background is a linear-gradient.
  2. The “toggle” button is moved to the center of the <input> with the transform property.
input::after {   background: linear-gradient(to right, orange, #8e2de2);   border-radius: 50px;   content: "";   height: 25px;   opacity: 0.6;   position: absolute;   top: 50%;   transform: translate(7px, -50%);   transition: all .4s;   width: 25px; }

Try clicking on the toggle button. Nothing happens. That’s because we’re not actually changing the checked state of the <input>. And even if we were, the result is… unpleasant.

The fix is to apply the :checked attribute to the ::after pseudo-element of the <input>. By specifically targeting the checked state of the checkbox and chaining it to the ::after pseudo-element, we can move the toggle back into place.

input:checked::after {   opacity: 1;   transform: translate(170%, -50%); }

Gradient border

We can decorate images with borders to make them stand out or fit more seamlessly within a design. Did you know we can use a gradient on a border? Well, we can with ::before (there are other ways, too, of course).

The core idea is to create a gradient over the image and use the CSS z-index property with a negative value. The negative value pulls the gradient below the image in the stacking order. This means the image always appears on top as long as the gradient has a negative z-index.

.gradient-border::before {   /* Renders the styles */   content: "";   /* Fills the entire space */   position: absolute;   top: 0;   left: 0;   bottom: 0;   right: 0;   /* Creates the gradient */   background-image: linear-gradient(#1a1a1a, #1560bd);   /* Stacks the gradient behind the image */   z-index: -1; }  figure {   /* Removes the default margin */   margin: 0;   /* Squeezes the image, revealing the gradient behind it */   padding: 10px; }

Gradient overlays

This is similar to what we did in the previous example, but here, we’re applying the gradient on top of the image. Why would we do that? It can be a nice way to add a little texture and depth to the image. Or perhaps it can be used to either lighten or darken an image if there’s text on top it that needs extra contrast for legibility.

While this is similar to what we just did, you’ll notice a few glaring differences:

figure::before {   background-image: linear-gradient(to top right, #1a1a1a, transparent);   content: "";   height: 100%;   position: absolute;   width: 100%; }

See that? There’s no z-index because it’s OK for the gradient to stack on top of the image. We’re also introducing transparency in the background gradient, which lets the image bleed through the gradient. You know, like an overlay.

Custom radio buttons

Most, if not all, of us try to customize the default styles of HTML radio buttons, and that’s usually accomplished with ::before and ::after, like we did with the checkbox earlier.

Firefox 91

We’re going to set a few base styles first, just to set the stage:

/* Centers everything */ .flex-center {   align-items: center;   display: flex;   justify-content: center; }  /* Styles the form element */ .form {   background: #ccc;   height: 100vh;   width: 100%; }  /* Styles the inputs */ .form-row {   background: #fff;   border-radius: 50px;   height: 40px;   margin: 10px;   overflow: hidden;   position: relative;   width: 150px; }

Now let’s remove the default styling of the radio buttons, again, with appearance: none;

.form-input {   -webkit-appearance: none; /* Safari */   appearance: none; }

::before should be positioned at the top-left corner of the radio button, and when it’s checked, we change its background color.

.form-input::before {   /* Renders the styles */   content: '';   /* Shows that it's interactive */   cursor: pointer;   /* Positions it to the top-left corner of the input */   position: absolute;   top: 0;   left: 0;   /* Takes up the entire space */   height: 100%;   width: 100%; }  /* When the input is in a checked state... */ .form-input:checked::before {   /* Change the background color */   background: #21209c; }

We still need to iron a few things out using ::after. Specifically, when the radio button is checked, we want to change the color of the circular ring to white because, in its current state, the rings are blue.

.form-input::after {   /* Renders the styles */   content: '';   /* Shows that it's interactive */   cursor: pointer;   /* A little border styling */   border-radius: 50px;   border: 4px solid #21209c;   /* Positions the ring */   position: absolute;   left: 10%;   top: 50%;   transform: translate(0, -50%);   /* Sets the dimensions */   height: 15px;   width: 15px; }  /* When the input is in a checked state... */ .form-input:checked::after {   /* Change ::after's border to white */   border: 4px solid #ffffff; }

The form label is still unusable here. We need to target the form label directly to add color, and when the form input is checked, we change that color to something that’s visible.

.form-label {   color: #21209c;   font-size: 1.1rem;   margin-left: 10px; }

Click the buttons, and still nothing happens. Here what’s going on. The position: absolute on ::before and ::after is covering things up when the radio buttons are checked, as anything that occurs in the HTML document hierarchy is covered up unless they are moved to a new location in the HTML document, or their position is altered with CSS. So, every time the radio button is checked, its label gets covered.

You probably already know how to fix this since we solved the same sort of thing earlier in another example? We apply z-index: 1 (or position: absolute) to the form label.

.form-label {   color: #21209c;   font-size: 1.1rem;   margin-left: 10px;   z-index: 1; /* Makes sure the label is stacked on top */   /* position: absolute; This is an alternative option */ }

Wrapping up

We covered seven different ways we can use the ::before and ::after pseudo-elements to create interesting effects, customize default styles, make useful placeholders, and add borders to images.

By no means did we cover all of the possibilities that we can unlock when we take advantage of these additional elements that can be selected with CSS. Lynn Fisher, however, has made a hobby out of it, making amazing designs with a single element. And let’s not forget Diana Smith’s CSS art that uses pseudo-elements in several places to get realistic, painting-like effects.


The post 7 Practical Uses for the ::before and ::after Pseudo-Elements in CSS appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Practical Use Cases for Scroll-Linked Animations in CSS with Scroll Timelines

The Scroll-Linked Animations specification is an upcoming and experimental addition to CSS. Using the @scroll-timeline at-rule and animation-timeline property this specification provides you can control the time position of regular CSS Animations by scrolling.

In this post, we take a look at some practical use cases where scroll-linked animations come in handy, replacing a typical JavaScript approach.

👨‍🔬 The CSS features described in this post are still experimental and not finalized at all. The are not supported by any browser at the time of writing, except for Chromium ≥ 89 with the #experimental-web-platform-features flag enabled.

CSS Scroll-Linked Animations, a quick primer

With CSS Scroll-Linked Animations, you can drive a CSS animation by scroll: as you scroll up or down inside a scroll container, the linked CSS animation will advance or rewind. Best of all is that this is all running off main thread, on the compositor.

You need three things to implement a basic scroll-linked animation:

  1. a CSS animation
  2. a scroll timeline
  3. a link between both

CSS animation

This is a regular CSS Animation like we already know:

@keyframes adjust-progressbar {   from {     transform: scaleX(0);   }   to {     transform: scaleX(1);   } }

As you normally do, attach it to an element using the animation property:

#progressbar {   animation: 1s linear forwards adjust-progressbar; }

Scroll timeline

The scroll timeline allows us to map the scroll distance to the animation progress. In CSS, we describe this with the CSS @scroll-timeline at-rule.

@scroll-timeline scroll-in-document-timeline {   source: auto;   orientation: vertical;   scroll-offsets: 0%, 100%; }

This at-rule consists of descriptors, including:

  1. The source describes the scrollable element whose scrolling triggers the activation and drives the progress of the timeline. By default, this is the entire document.
  2. The orientation determines the scrolling direction that should trigger the animation. By default, this is vertical.
  3. The scroll-offsets property is an array of key points that describe the range in which the animation should be active. It can be absolute values (e.g. percentages and lengths) or element-based.

A previous version of the specification required you to also set a time-range descriptor. This descriptor has been removed and will automatically take over the animation-duration from the linked animation. You may still see traces of it in the demos, but you can safely ignore it.

To associate our @scroll-timeline with our CSS animation, we use the new animation-timeline CSS property, and have it refer to the timeline’s name.

#progressbar {   animation: 1s linear forwards adjust-progressbar;   animation-timeline: scroll-in-document-timeline; /* 👈 THIS! */ }

With that set up the adjust-progressbar animation won’t run automatically on page load, but will only advance as we scroll down the page.

For a more in-depth introduction to @scroll-timeline please refer to Part 1 and Part 2 of my series on the future of scroll-linked animations.

The first post looks at each descriptor/property in more detail, explaining them with an example to go along with them, before covering many more interesting demos.

The second post digs even deeper, looking into Element-Based Offsets, which allow us to drive an animation as an element appears into and disappears from the scrollport as we scroll.

An example of what you can achieve with CSS Scroll-Linked Animations using Element-Based Offsets.

Practical use cases

Apart from the progress par demo above, there are a few more use cases or scenarios where scroll-linked animations can replace a solution typically implemented using JavaScript.

  1. parallax header
  2. image reveal/hide
  3. typing animation
  4. carousel indicators
  5. scrollspy

Parallax header

A typical use case for Scroll-Linked Animations is a parallax effect, where several sections of a page seem to have a different scrolling speed. There’s a way to create these type of effects using only CSS, but that requires mind-bending transform hacks involving translate-z() and scale().

Inspired upon the Firewatch Header—which uses the mentioned transform hack—I created this version that uses a CSS scroll timeline:

Compared to the original demo:

  • The markup was kept, except for that extra .parallax__cover that’s no longer needed.
  • The <body> was given a min-height to create some scroll-estate.
  • The positioning of the .parallax element and its .parallax_layer child elements was tweaked.
  • The transform/perspective-hack was replaced with a scroll timeline.

Each different layer uses the same scroll timeline: scroll over a distance of 100vh.

@scroll-timeline scroll-for-100vh {   time-range: 1s;   scroll-offsets: 0, 100vh; }  .parallax__layer {   animation: 1s parallax linear;   animation-timeline: scroll-for-100vh; }

What’s different between layers is the distance that they move as we scroll down:

  • The foremost layer should stay in place, eg. move for 0vh.
  • The last layer should should move the fastest, e.g. 100vh.
  • All layers in between are interpolated.
@keyframes parallax {   to {     transform: translateY(var(--offset));   } }  .parallax__layer__0 {   --offset: 100vh; }  .parallax__layer__1 {   --offset: 83vh; }  .parallax__layer__2 {   --offset: 67vh; }  .parallax__layer__3 {   --offset: 50vh; }  .parallax__layer__4 {   --offset: 34vh; }  .parallax__layer__5 {   --offset: 17vh; }  .parallax__layer__6 {   --offset: 0vh; }

As the foremost layers move over a greater distance, they appear to move faster than the lower layers, achieving the parallax effect.

Image reveal/hide

Another great use-case for scroll linked animations is an image reveal: an image slides into view as it appears.

By default, the image is given an opacity of 0 and is masked using a clip-path:

#revealing-image {   opacity: 0;   clip-path: inset(45% 20% 45% 20%); }

In the end-state we want the image to be fully visible, so we sent the end-frame of our animation to reflect that:

@keyframes reveal {   to {     clip-path: inset(0% 0% 0% 0%);     opacity: 1;   } }

By using element-based offsets as our scroll timeline offsets, we can have it so thar the image begins to appear only when the image itself slides into view.

@scroll-timeline revealing-image-timeline {   scroll-offsets:     selector(#revealing-image) end 0.5,     selector(#revealing-image) end 1   ; }  #revealing-image {   animation: reveal 1s linear forwards;   animation-timeline: revealing-image-timeline; }

😵 Can’t follow with those element-based offsets? This visualization/tool has got you covered.

Typing animation

As CSS scroll timelines can be linked to any existing CSS animation, you can basically take any CSS Animation demo and transform it. Take this typing animation for example:

With the addition of a scroll timeline and the animation-timeline property, it can be adjusted to “type on scroll”:

Note that to create some scroll-estate the <body>was also given a height of 300vh.

Using a different animation, the code above can easily be adjusted to create a zoom on scroll effect:

I can see these two working great for article intros.

One of the components of a carousel (aka slider) is an indicator that exposes how many slides it contains, as well as which slide is currently active. This is typically done using bullets.

This again is something we will be able to achieve using a CSS scroll timeline, as shown in this demo created by Fabrizio Calderan:

The active state bullet is injected via .slider nav::before and has an animation set that moves it over the other bullets

/* Styling of the dots */ .slider nav::before, .slider a {   inline-size: 1rem;   aspect-ratio: 1;   border-radius: 50%;   background: #9bc; }  /* Positioning of the active dot */ .slider nav::before {   content: "";   position: absolute;   z-index: 1;   display: block;   cursor: not-allowed;   transform: translateX(0);   animation: dot 1s steps(1, end) 0s forwards; }  /* Position over time of the active dot */ @keyframes dot {   0%      { transform: translateX(0); }   33%      { transform: translateX(calc((100% + var(--gap)) * 1)); }   66%      { transform: translateX(calc((100% + var(--gap)) * 2)); }    100%      { transform: translateX(calc((100% + var(--gap)) * 3)); } }

By attaching a @scroll-timeline onto the slider, the dot that indicates the active state can move as you scroll:

@scroll-timeline slide {   source: selector(#s);   orientation: inline;  }  .slider nav::before {   /* etc. */   animation-timeline: slide; }

The dot only moves after the slide has snapped to its position thanks to the inclusion of a steps() function in the animation. When removing it, it becomes more clear how the dot moves as you scroll

💡 This feels like the final missing piece to Christian Shaefer’s CSS-only carousel.

ScrollSpy

Back in early 2020, I created a sticky table of contents with scrolling active states. The final part to creating the demo was to use IntersectionObserver to set the active states in the table of contents (ToC) as you scroll up/down the document.

Unlike the carousel indicators demo from above we can’t simply get there by moving a single dot around, as it’s the texts in the ToC that get adjusted. To approach this situation, we need to attach two animations onto each element in the ToC:

  1. The first animation is to visually activate the ToC item when the proper section comes into view at the bottom edge of the document.
  2. The second animation is to visually deactivate the ToC item when the proper section slides out of view at the top edge of the document.
.section-nav li > a {   animation:     1s activate-on-enter linear forwards,     1s deactivate-on-leave linear forwards; }

As we have two animations, we also need to create two scroll timelines, and this for each section of the content. Take the #introduction section for example:

@scroll-timeline section-introduction-enter {   source: auto;   scroll-offsets:     selector(#introduction) end 0,     selector(#introduction) end 1; }  @scroll-timeline section-introduction-leave {   source: auto;   scroll-offsets:     selector(#introduction) start 1,     selector(#introduction) start 0; }

Once both of these timelines are linked to both animations, everything will work as expected:

.section-nav li > a[href"#introduction"] {   animation-timeline:     section-introduction-enter,     section-introduction-leave; }

In closing

I hope I have convinced you of the potential offered by the CSS Scroll-linked Animations specification. Unfortunately, it’s only supported in Chromium-based browsers right now, hidden behind a flag.

Given this potential, I personally hope that—once the specification settles onto a certain syntax—other browser vendors will follow suit. If you too would like to see Scroll-Linked Animations land in other browsers, you can actively star/follow the relevant browser issues.

By actively starring issues, us developers can signal our interest into these features to browser vendors.


The post Practical Use Cases for Scroll-Linked Animations in CSS with Scroll Timelines appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Very Extremely Practical CSS Art

I’ve always enjoyed the CSS art people create, but I’ve never ventured into it much myself. I’m familiar with many of the tricks involved, but still find it surprising every time: the way people are able to make such fluid and beautiful images out of little boxes. I always end up digging around in dev tools to see how things are done, but I had never seen the process unfold.

Any time CSS art starts getting attention, there is always someone around to say “that’s not practical” or “just use SVG” or something similarly dismissive and boring. It’s a terrible argument, even if it was true — no one is required to be Practical At All Times. What a terrible world that would be.

In October, I took the time to watch Lynn Fisher (Twitter, CodePen), one of my favorite CSS artists, live-stream her single-div process. Somewhere in the back of my mind, I assumed single-div artwork relied on highly complicated box-shadows—almost a pixel-art approach. I’m not sure where that idea came from, I probably saw someone do it years ago. But her process is much more “normal” and “practical” than I even realized: a number of reasonably layered, sized, and positioned background gradients.

Examples of Lynn Fischer's single div projects: repeated polar bears, plants on a shelf, a blinking light, and a tiny electronic piano.

Wait. I know how to do that. It’s not the technique that’s magical—it’s the audacity of turning a few gradients into a block of cheese with cake inside!

I’ve used all these properties before on client projects. I’ve created gradients, layered images, sized them, and positioned them for various effects. None of that is new, or complicated, or radical. I really didn’t learn anything at all about the CSS itself. But it had a huge impact on my perception of what I could accomplish with those simple tools. 

Within a few weeks, I was using that in production. Again, it’s nothing fancy or complicated—the perfect low-hanging fruit where a custom SVG feels just slightly too bulky. Here’s the effect I created, for a personal project, with a few custom properties to make adjustment easier:

Last week we used a similar trick as part of a Very Practical & Official client component library. It was Stacy Kvernmo’s idea, and it worked great.

Thanks Lynn, and all you other fabulous CSS Artists! Thanks for showing us all how much farther we can push this language that we love so much, and the Very Serious tools we use every day.


The post Very Extremely Practical CSS Art appeared first on CSS-Tricks.

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

CSS-Tricks

, ,
[Top]

Practical Use Cases for JavaScript’s closest() Method

Have you ever had the problem of finding the parent of a DOM node in JavaScript, but aren’t sure how many levels you have to traverse up to get to it? Let’s look at this HTML for instance:

<div data-id="123">   <button>Click me</button> </div>

That’s pretty straightforward, right? Say you want to get the value of data-id after a user clicks the button:

var button = document.querySelector("button"); 
 button.addEventListener("click", (evt) => {   console.log(evt.target.parentNode.dataset.id);   // prints "123" });

In this very case, the Node.parentNode API is sufficient. What it does is return the parent node of a given element. In the above example, evt.targetis the button clicked; its parent node is the div with the data attribute.

But what if the HTML structure is nested deeper than that? It could even be dynamic, depending on its content.

<div data-id="123">   <article>     <header>       <h1>Some title</h1>       <button>Click me</button>     </header>      <!-- ... -->   </article> </div>

Our job just got considerably more difficult by adding a few more HTML elements. Sure, we could do something like element.parentNode.parentNode.parentNode.dataset.id, but come on… that isn’t elegant, reusable or scalable.

The old way: Using a while-loop

One solution would be to make use of a while loop that runs until the parent node has been found.

function getParentNode(el, tagName) {   while (el && el.parentNode) {     el = el.parentNode;          if (el && el.tagName == tagName.toUpperCase()) {       return el;     }   }      return null; }

Using the same HTML example from above again, it would look like this:

var button = document.querySelector("button"); 
 console.log(getParentNode(button, 'div').dataset.id); // prints "123"

This solution is far from perfect. Imagine if you want to use IDs or classes or any other type of selector, instead of the tag name. At least it allows for a variable number of child nodes between the parent and our source.

There’s also jQuery

Back in the day, if you didn’t wanted to deal with writing the sort of function we did above for each application (and let’s be real, who wants that?), then a library like jQuery came in handy (and it still does). It offers a .closest() method for exactly that:

$  ("button").closest("[data-id='123']")

The new way: Using Element.closest()

Even though jQuery is still a valid approach (hey, some of us are beholden to it), adding it to a project only for this one method is overkill, especially if you can have the same with native JavaScript.

And that’s where Element.closest comes into action:

var button = document.querySelector("button"); 
 console.log(button.closest("div")); // prints the HTMLDivElement

There we go! That’s how easy it can be, and without any libraries or extra code.

Element.closest() allows us to traverse up the DOM until we get an element that matches the given selector. The awesomeness is that we can pass any selector we would also give to Element.querySelector or Element.querySelectorAll. It can be an ID, class, data attribute, tag, or whatever.

element.closest("#my-id"); // yep element.closest(".some-class"); // yep element.closest("[data-id]:not(article)") // hell yeah

If Element.closest finds the parent node based on the given selector, it returns it the same way as  document.querySelector. Otherwise, if it doesn’t find a parent, it returns null instead, making it easy to use with if conditions:

var button = document.querySelector("button"); 
 console.log(button.closest(".i-am-in-the-dom")); // prints HTMLElement 
 console.log(button.closest(".i-am-not-here")); // prints null 
 if (button.closest(".i-am-in-the-dom")) {   console.log("Hello there!"); } else {   console.log(":("); }

Ready for a few real-life examples? Let’s go!

Use Case 1: Dropdowns

Our first demo is a basic (and far from perfect) implementation of a dropdown menu that opens after clicking one of the top-level menu items. Notice how the menu stays open even when clicking anywhere inside the dropdown or selecting text? But click somewhere on the outside, and it closes.

The Element.closest API is what detects that outside click. The dropdown itself is a <ul> element with a .menu-dropdown class, so clicking anywhere outside the menu will close it. That’s because the value for evt.target.closest(".menu-dropdown") is going to be null since there is no parent node with this class.

function handleClick(evt) {   // ...      // if a click happens somewhere outside the dropdown, close it.   if (!evt.target.closest(".menu-dropdown")) {     menu.classList.add("is-hidden");     navigation.classList.remove("is-expanded");   } }

Inside the handleClick callback function, a condition decides what to do: close the dropdown. If somewhere else inside the unordered list is clicked, Element.closest will find and return it, causing the dropdown to stay open.

Use Case 2: Tables

This second example renders a table that displays user information, let’s say as a component in a dashboard. Each user has an ID, but instead of showing it, we save it as a data attribute for each <tr> element.

<table>   <!-- ... -->   <tr data-userid="1">     <td>       <input type="checkbox" data-action="select">     </td>     <td>John Doe</td>     <td>john.doe@gmail.com</td>     <td>       <button type="button" data-action="edit">Edit</button>       <button type="button" data-action="delete">Delete</button>     </td>   </tr> </table>

The last column contains two buttons for editing and deleting a user from the table. The first button has a data-action attribute of edit, and the second button is delete. When we click on either of them, we want to trigger some action (like sending a request to a server), but for that, the user ID is needed.

A click event listener is attached to the global window object, so whenever the user clicks somewhere on the page, the callback function handleClick is called.

function handleClick(evt) {   var { action } = evt.target.dataset;      if (action) {     // `action` only exists on buttons and checkboxes in the table.     let userId = getUserId(evt.target);          if (action == "edit") {       alert(`Edit user with ID of $  {userId}`);     } else if (action == "delete") {       alert(`Delete user with ID of $  {userId}`);     } else if (action == "select") {       alert(`Selected user with ID of $  {userId}`);     }   } }

If a click happens somewhere else other than one of these buttons, no data-action attribute exists, hence nothing happens. However, when clicking on either button, the action will be determined (that’s called event delegation by the way), and as the next step, the user ID will be retrieved by calling getUserId:

function getUserId(target) {   // `target` is always a button or checkbox.   return target.closest("[data-userid]").dataset.userid; }

This function expects a DOM node as the only parameter and, when called, uses Element.closest to find the table row that contains the pressed button. It then returns the data-userid value, which can now be used to send a request to a server.

Use Case 3: Tables in React

Let’s stick with the table example and see how we’d handle it on a React project. Here’s the code for a component that returns a table:

function TableView({ users }) {   function handleClick(evt) {     var userId = evt.currentTarget     .closest("[data-userid]")     .getAttribute("data-userid"); 
     // do something with `userId`   } 
   return (     <table>       {users.map((user) => (         <tr key={user.id} data-userid={user.id}>           <td>{user.name}</td>           <td>{user.email}</td>           <td>             <button onClick={handleClick}>Edit</button>           </td>         </tr>       ))}     </table>   ); }

I find that this use case comes up frequently — it’s fairly common to map over a set of data and display it in a list or table, then allow the user to do something with it. Many people use inline arrow-functions, like so:

<button onClick={() => handleClick(user.id)}>Edit</button>

While this is also a valid way of solving the issue, I prefer to use the data-userid technique. One of the drawbacks of the inline arrow-function is that each time React re-renders the list, it needs to create the callback function again, resulting in a possible performance issue when dealing with large amounts of data.

In the callback function, we simply deal with the event by extracting the target (the button) and getting the parent <tr> element that contains the data-userid value.

function handleClick(evt) {   var userId = evt.target   .closest("[data-userid]")   .getAttribute("data-userid"); 
   // do something with `userId` }

Use Case 4: Modals

This last example is another component I’m sure you’ve all encountered at some point: a modal. Modals are often challenging to implement since they need to provide a lot of features while being accessible and (ideally) good looking.

We want to focus on how to close the modal. In this example, that’s possible by either pressing Esc on a keyboard, clicking on a button in the modal, or clicking anywhere outside the modal.

In our JavaScript, we want to listen for clicks somewhere in the modal:

var modal = document.querySelector(".modal-outer");  modal.addEventListener("click", handleModalClick);

The modal is hidden by default through a .is-hidden utility class. It’s only when a user clicks the big red button that the modal opens by removing this class. And once the modal is open, clicking anywhere inside it — with the exception of the close button — will not inadvertently close it. The event listener callback function is responsible for that:

function handleModalClick(evt) {   // `evt.target` is the DOM node the user clicked on.   if (!evt.target.closest(".modal-inner")) {     handleModalClose();   } }

evt.target is the DOM node that’s clicked which, in this example, is the entire backdrop behind the modal, <div class="modal-outer">. This DOM node is not within <div class="modal-inner">, hence Element.closest() can bubble up all it wants and won’t find it. The condition checks for that and triggers the handleModalClose function.

Clicking somewhere inside the nodal, say the heading, would make <div class="modal-inner"> the parent node. In that case, the condition isn’t truthy, leaving the modal in its open state.

Oh, and about browser support…

As with any cool “new” JavaScript API, browser support is something to consider. The good news is that Element.closest is not that new and is supported in all of the major browsers for quite some time, with a whopping 94% support coverage. I’d say this qualifies as safe to use in a production environment.

The only browser not offering any support whatsoever is Internet Explorer (all versions). If you have to support IE, then you might be better off with the jQuery approach.


As you can see, there are some pretty solid use cases for Element.closest. What libraries, like jQuery, made relatively easy for us in the past can now be used natively with vanilla JavaScript.

Thanks to the good browser support and easy-to-use API, I heavily depend on this little method in many applications and haven’t been disappointed, yet.

Do you have any other interesting use cases? Feel free to let me know.


The post Practical Use Cases for JavaScript’s closest() Method appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,
[Top]

Patterns for Practical CSS Custom Properties Use

I’ve been playing around with CSS Custom Properties to discover their power since browser support is finally at a place where we can use them in our production code. I’ve been using them in a number different ways and I’d love for you to get as excited about them as I am. They are so useful and powerful!

I find that CSS variables usage tends to fall into categories. Of course, you’re free to use CSS variables however you like, but thinking of them in these different categories might help you understand the different ways in which they can be used.

  • Variables. The basics, such as setting, a brand color to use wherever needed.
  • Default Values. For example, a default border-radius that can be overridden later.
  • Cascading Values. Using clues based on specificity, such as user preferences.
  • Scoped Rulesets. Intentional variations on individual elements, like links and buttons.
  • Mixins. Rulesets intended to bring their values to a new context.
  • Inline Properties. Values passed in from inline styles in our HTML.

The examples we’ll look at are simplified and condensed patterns from a CSS framework I created and maintain called Cutestrap.

A quick note on browser support

There are two common lines of questions I hear when Custom Properties come up. The first is about browser support. What browsers support them? Are there fallbacks we need to use where they aren’t supported?

The global market share that support the things we’re covering in this post is 85%. Still, it’s worth cross-referencing caniuse) with your user base to determine how much and where progressive enhancement makes sense for your project.

The second question is always about how to use Custom Properties. So let’s dive into usage!

Pattern 1: Variables

The first thing we’ll tackle is setting a variable for a brand color as a Custom Property and using it on an SVG element. We’ll also use a fallback to cover users on trailing browsers.

html {   --brand-color: hsl(230, 80%, 60%); }  .logo {   fill: pink; /* fallback */   fill: var(--brand-color); }

Here, we’ve declared a variable called --brand-color in our html ruleset. The variable is defined on an element that’s always present, so it will cascade to every element where it’s used. Long story short, we can use that variable in our .logo ruleset.

We declared a pink fallback value for trailing browsers. In the second fill declaration, we pass --brand-color into the var() function, which will return the value we set for that Custom Property.

That’s pretty much how the pattern goes: define the variable (--variable-name) and then use it on an element (var(--variable-name)).

See the Pen
Patterns for Practical Custom Properties: Example 1.0
by Tyler Childs (@tylerchilds)
on CodePen.

Pattern 2: Default values

The var() function we used in the first example can also provide default values in case the Custom Property it is trying to access is not set.

For example, say we give buttons a rounded border. We can create a variable — we’ll call it --roundness — but we won’t define it like we did before. Instead, we’ll assign a default value when putting the variable to use.

.button {   /* --roundness: 2px; */   border-radius: var(--roundness, 10px); }

A use case for default values without defining the Custom Property is when your project is still in design but your feature is due today. This make it a lot easier to update the value later if the design changes.

So, you give your button a nice default, meet your deadline and when --roundness is finally set as a global Custom Property, your button will get that update for free without needing to come back to it.

See the Pen
Patterns for Practical Custom Properties: Example 2.0
by Tyler Childs (@tylerchilds)
on CodePen.

You can edit on CodePen and uncomment the code above to see what the button will look like when --roundness is set!

Pattern 3: Cascading values

Now that we’ve got the basics under our belt, let’s start building the future we owe ourselves. I really miss the personality that AIM and MySpace had by letting users express themselves with custom text and background colors on profiles.

Let’s bring that back and build a school message board where each student can set their own font, background color and text color for the messages they post.

User-based themes

What we’re basically doing is letting students create a custom theme. We’ll set the theme configurations inside data-attribute rulesets so that any descendants — a .message element in this case — that consume the themes will have access to those Custom Properties.

.message {   background-color: var(--student-background, #fff);   color: var(--student-color, #000);   font-family: var(--student-font, "Times New Roman", serif);   margin-bottom: 10px;   padding: 10px; }  [data-student-theme="rachel"] {   --student-background: rgb(43, 25, 61);   --student-color: rgb(252, 249, 249);   --student-font: Arial, sans-serif; }  [data-student-theme="jen"] {   --student-background: #d55349;   --student-color: #000;   --student-font: Avenir, Helvetica, sans-serif; }  [data-student-theme="tyler"] {   --student-background: blue;   --student-color: yellow;   --student-font: "Comic Sans MS", "Comic Sans", cursive; }

Here’s the markup:

<section>   <div data-student-theme="chris">     <p class="message">Chris: I've spoken at events and given workshops all over the world at conferences.</p>   </div>   <div data-student-theme="rachel">     <p class="message">Rachel: I prefer email over other forms of communication.</p>   </div>   <div data-student-theme="jen">     <p class="message">Jen: This is why I immediately set up my new team with Slack for real-time chat.</p>   </div>   <div data-student-theme="tyler">     <p class="message">Tyler: I miss AIM and MySpace, but this message board is okay.</p>   </div> </section>

We have set all of our student themes using [data-student-theme] selectors for our student theme rulesets. The Custom Properties for background, color, and font will apply to our .message ruleset if they are set for that student because .message is a descendant of the div containing the data-attribute that, in turn, contains the Custom Property values to consume. Otherwise, the default values we provided will be used instead.

See the Pen
Patterns for Practical Custom Properties: Example 3.0
by Tyler Childs (@tylerchilds)
on CodePen.

Readable theme override

As fun and cool as it is for users to control custom styles, what users pick won’t always be accessible with considerations for contrast, color vision deficiency, or anyone that prefers their eyes to not bleed when reading. Remember the GeoCities days?

Let’s add a class that provides a cleaner look and feel and set it on the parent element (<section>) so it overrides any student theme when it’s present.

.readable-theme [data-student-theme] {   --student-background: hsl(50, 50%, 90%);   --student-color: hsl(200, 50%, 10%);   --student-font: Verdana, Geneva, sans-serif; }
<section class="readable-theme">   ... </section>

We’re utilizing the cascade to override the student themes by setting a higher specificity such that the background, color, and font will be in scope and will apply to every .message ruleset.

See the Pen
Patterns for Practical Custom Properties: Example 3.1
by Tyler Childs (@tylerchilds)
on CodePen.

Pattern 4: Scoped rulesets

Speaking of scope, we can scope Custom Properties and use them to streamline what is otherwise boilerplate CSS. For example, we can define variables for different link states.

a {   --link: hsl(230, 60%, 50%);   --link-visited: hsl(290, 60%, 50%);   --link-hover: hsl(230, 80%, 60%);   --link-active: hsl(350, 60%, 50%); }  a:link {   color: var(--link); }  a:visited {   color: var(--link-visited); }  a:hover {   color: var(--link-hover); }  a:active {   color: var(--link-active); }
<a href="#">Link Example</a>

Now that we’ve written out the Custom Properties globally on the <a> element and used them on our link states, we don’t need to write them again. These are scoped to our <a> element’s ruleset so they are only set on anchor tags and their children. This allows us to not pollute the global namespace.

Example: Grayscale link

Going forward, we can control the links we just created by changing the Custom Properties for our different use cases. For example, let’s create a gray-colored link.

.grayscale {   --link: LightSlateGrey;   --link-visited: Silver;   --link-hover: DimGray;   --link-active: LightSteelBlue; }
<a href="#" class="grayscale">Link Example</a>

We’ve declared a .grayscale ruleset that contains the colors for our different link states. Since the selector for this ruleset has a greater specificity then the default, these variable values are used and then applied to the pseudo-class rulesets for our link states instead of what was defined on the <a> element.

See the Pen
Patterns for Practical Custom Properties: Example 4.0
by Tyler Childs (@tylerchilds)
on CodePen.

Example: Custom links

If setting four Custom Properties feels like too much work, what if we set a single hue instead? That could make things a lot easier to manage.

.custom-link {   --hue: 30;   --link: hsl(var(--hue), 60%, 50%);   --link-visited: hsl(calc(var(--hue) + 60), 60%, 50%);   --link-hover: hsl(var(--hue), 80%, 60%);   --link-active: hsl(calc(var(--hue) + 120), 60%, 50%); }  .danger {   --hue: 350; }
<a href="#" class="custom-link">Link Example</a> <a href="#" class="custom-link danger">Link Example</a>

See the Pen
Patterns for Practical Custom Properties: Example 4.1
by Tyler Childs (@tylerchilds)
on CodePen.

By introducing a variable for a hue value and applying it to our HSL color values in the other variables, we merely have to change that one value to update all four link states.

Calculations are powerful in combination with Custom Properties since they let
your styles be more expressive with less effort. Check out this technique by Josh Bader where he uses a similar approach to enforce accessible color contrasts on buttons.

Pattern 5: Mixins

A mixin, in regards to Custom Properties, is a function declared as a Custom Property value. The arguments for the mixin are other Custom Properties that will recalculate the mixin when they’re changed which, in turn, will update styles.

The custom link example we just looked at is actually a mixin. We can set the value for --hue and then each of the four link states will recalculate accordingly.

Example: Baseline grid foundation

Let’s learn more about mixins by creating a baseline grid to help with vertical rhythm. This way, our content has a pleasant cadence by utilizing consistent spacing.

.baseline, .baseline * {   --rhythm: 2rem;   --line-height: var(--sub-rhythm, var(--rhythm));   --line-height-ratio: 1.4;   --font-size: calc(var(--line-height) / var(--line-height-ratio)); }  .baseline {   font-size: var(--font-size);   line-height: var(--line-height); }

We’ve applied the ruleset for our baseline grid to a .baseline class and any of its descendants.

  • --rhythm: This is the foundation of our baseline. Updating it will impact all the other properties.
  • --line-height: This is set to --rhythm by default, since --sub-rhythm is not set here.
  • --sub-rhythm: This allows us to override the --line-height — and subsequently, the --font-size — while maintaining the overall baseline grid.
  • --line-height-ratio: This helps enforce a nice amount of spacing between lines of text.
  • --font-size: This is calculated by dividing our --line-height by our --line-height-ratio.

We also set our font-size and line-height in our .baseline ruleset to use the --font-size and --line-height from our baseline grid. In short, whenever the rhythm changes, the line height and font size change accordingly while maintaining a legible experience.

OK, let’s put the baseline to use.

Let’s create a tiny webpage. We’ll use our --rhythm Custom Property for all of the spacing between elements.

.baseline h2, .baseline p, .baseline ul {   padding: 0 var(--rhythm);   margin: 0 0 var(--rhythm); }  .baseline p {   --line-height-ratio: 1.2; }  .baseline h2 {   --sub-rhythm: calc(3 * var(--rhythm));   --line-height-ratio: 1; }  .baseline p, .baseline h2 {   font-size: var(--font-size);   line-height: var(--line-height); }  .baseline ul {   margin-left: var(--rhythm); }
<section class="baseline">   <h2>A Tiny Webpage</h2>   <p>This is the tiniest webpage. It has three noteworthy features:</p>   <ul>     <li>Tiny</li>     <li>Exemplary</li>     <li>Identifies as Hufflepuff</li>   </ul> </section>

We’re essentially using two mixins here: --line-height and --font-size. We need to set the properties font-size and line-height to their Custom Property counterparts in order to set the heading and paragraph. The mixins have been recalculated in those rulesets, but they need to be set before the updated styling will be applied to them.

See the Pen
Patterns for Practical Custom Properties: Example 5.0
by Tyler Childs (@tylerchilds)
on CodePen.

Something to keep in mind: You probably do not want to use the Custom Property values in the ruleset itself when applying mixins using a wildcard selector. It gives those styles a higher specificity than any other inheritance that comes along with the cascade, making them hard to override without using !important.

Pattern 6: Inline properties

We can also declare Custom Properties inline. Let’s build a lightweight grid system demonstrate.

.grid {   --columns: auto-fit;    display: grid;   gap: 10px;   grid-template-columns: repeat(var(--columns), minmax(0, 1fr)); }
<div class="grid">   <img src="https://www.fillmurray.com/900/600" alt="Bill Murray" />   <img src="https://www.placecage.com/900/600" alt="Nic Cage" />   <img src="https://www.placecage.com/g/900/600" alt="Nic Cage gray" />   <img src="https://www.fillmurray.com/g/900/600" alt="Bill Murray gray" />   <img src="https://www.placecage.com/c/900/600" alt="Nic Cage crazy" />   <img src="https://www.placecage.com/gif/900/600" alt="Nic Cage gif" /> </div>

By default, the grid has equally sized columns that will automatically lay themselves into a single row.

See the Pen
Patterns for Practical Custom Properties: Example 6.0
by Tyler Childs (@tylerchilds)
on CodePen.

To control the number of columns we can set our --columns Custom Property
inline on our grid element.

<div class="grid" style="--columns: 3;">   ... </div>

See the Pen
Patterns for Practical Custom Properties: Example 6.1
by Tyler Childs (@tylerchilds)
on CodePen.


We just looked at six different use cases for Custom Properties — at least ones that I commonly use. Even if you were already aware of and have been using Custom Properties, hopefully seeing them used these ways gives you a better idea of when and where to use them effectively.

Are there different types of patterns you use with Custom Properties? Share them in the comments and link up some demos — I’d love to see them!

If you’re new to Custom Properties are are looking to level up, try playing around with the examples we covered here, but add media queries to the mix. You’ll see how adaptive these can be and how many interesting opportunities open up when you have the power to change values on the fly.

Plus, there are a ton of other great resources right here on CSS-Tricks to up your Custom Properties game in the Custom Properties Guide.

See the Pen
Thank you for Reading!
by Tyler Childs (@tylerchilds)
on CodePen.

The post Patterns for Practical CSS Custom Properties Use appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

A Practical Use Case for Vue Render Functions: Building a Design System Typography Grid

This post covers how I built a typography grid for a design system using Vue render functions. Here’s the demo and the code. I used render functions because they allow you to create HTML with a greater level of control than regular Vue templates, yet surprisingly I couldn’t find very much when I web searched around for real-life, non-tutorial applications of them. I’m hoping this post will fill that void and provide a helpful and practical use case on using Vue render functions.

I’ve always found render functions to be a little out-of-character for Vue. While the rest of the framework emphasizes simplicity and separation of concerns, render functions are a strange and often difficult-to-read mix of HTML and JavaScript.

For example, to display:

<div class="container">   <p class="my-awesome-class">Some cool text</p> </div>

…you need:

render(createElement) {   return createElement("div", { class: "container" }, [     createElement("p", { class: "my-awesome-class" }, "Some cool text")   ]) }

I suspect that this syntax turns some people off, since ease-of-use is a key reason to reach for Vue in the first place. This is a shame because render functions and functional components are capable of some pretty cool, powerful stuff. In the spirit of demonstrating their value, here’s how they solved an actual business problem for me.

Quick disclaimer: It will be super helpful to have the demo open in another tab to reference throughout this post.

Defining criteria for a design system

My team wanted to include a page in our VuePress-powered design system showcasing different typography options. This is part of a mockup that I got from our designer.

A screenshot of the typographic design system. There are four columns where the first shows the style name with the rendered style, second is the element or class, third shows the properties that make the styles, and fourth is the defined usage.

And here’s a sample of some of the corresponding CSS:

h1, h2, h3, h4, h5, h6 {   font-family: "balboa", sans-serif;   font-weight: 300;   margin: 0; }  h4 {   font-size: calc(1rem - 2px); }  .body-text {   font-family: "proxima-nova", sans-serif; }  .body-text--lg {   font-size: calc(1rem + 4px); }  .body-text--md {   font-size: 1rem; }  .body-text--bold {   font-weight: 700; }  .body-text--semibold {   font-weight: 600; }

Headings are targeted with tag names. Other items use class names, and there are separate classes for weight and size.

Before writing any code, I created some ground rules:

  • Since this is really a data visualization, the data should be stored in a separate file.
  • Headings should use semantic heading tags (e.g. <h1>, <h2>, etc.) instead of having to rely on a class.
  • Body content should use paragraph (<p>) tags with the class name (e.g. <p class="body-text--lg">).
  • Content types that have variations should be grouped together by wrapping them in the root paragraph tag, or corresponding root element, without a styling class. Children should be wrapped with <span> and the class name.
<p>   <span class="body-text--lg">Thing 1</span>   <span class="body-text--lg">Thing 2</span> </p>
  • Any content that’s not demonstrating special styling should use a paragraph tag with the correct class name and <span> for any child nodes.
<p class="body-text--semibold">   <span>Thing 1</span>   <span>Thing 2</span> </p>
  • Class names should only need to be written once for each cell that’s demonstrating styling.

Why render functions make sense

I considered a few options before starting:

Hardcoding

I like hardcoding when appropriate, but writing my HTML by hand would have meant typing out different combinations of the markup, which seemed unpleasant and repetitive. It also meant that data couldn’t be kept in a separate file, so I ruled out this approach.

Here’s what I mean:

<div class="row">   <h1>Heading 1</h1>   <p class="body-text body-text--md body-text--semibold">h1</p>   <p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p>   <p class="group body-text body-text--md body-text--semibold">     <span>Product title (once on a page)</span>     <span>Illustration headline</span>   </p> </div>

Using a traditional Vue template

This would normally be the go-to option. However, consider the following:

See the Pen
Different Styles Example
by Salomone Baquis (@soluhmin)
on CodePen.

In the first column, we have:

– An <h1>> tag rendered as-is.
– A <p> tag that groups some <span> children with text, each with a class (but no special class on the <p> tag).
– A <p> tag with a class and no children.

The result would have meant many instances of v-if and v-if-else, which I knew would get confusing fast. I also disliked all of that conditional logic inside the markup.

Because of these reasons, I chose render functions. Render functions use JavaScript to conditionally create child nodes based on all of the criteria that’s been defined, which seemed perfect for this situation.

Data model

As I mentioned earlier, I’d like to keep typography data in a separate JSON file so I can easily make changes later without touching markup. Here’s the raw data.

Each object in the file represents a different row.

{   "text": "Heading 1",   "element": "h1", // Root wrapping element.   "properties": "Balboa Light, 30px", // Third column text.   "usage": ["Product title (once on a page)", "Illustration headline"] // Fourth column text. Each item is a child node.  }

The object above renders the following HTML:

<div class="row">   <h1>Heading 1</h1>   <p class="body-text body-text--md body-text--semibold">h1</p>   <p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p>   <p class="group body-text body-text--md body-text--semibold">     <span>Product title (once on a page)</span>     <span>Illustration headline</span>   </p> </div>

Let’s look at a more involved example. Arrays represent groups of children. A classes object can store classes. The base property contains classes that are common to every node in the cell grouping. Each class in variants is applied to a different item in the grouping.

{   "text": "Body Text - Large",   "element": "p",   "classes": {     "base": "body-text body-text--lg", // Applied to every child node     "variants": ["body-text--bold", "body-text--regular"] // Looped through, one class applied to each example. Each item in the array is its own node.    },   "properties": "Proxima Nova Bold and Regular, 20px",   "usage": ["Large button title", "Form label", "Large modal text"] }

Here’s how that renders:

<div class="row">   <!-- Column 1 -->   <p class="group">     <span class="body-text body-text--lg body-text--bold">Body Text - Large</span>     <span class="body-text body-text--lg body-text--regular">Body Text - Large</span>   </p>   <!-- Column 2 -->   <p class="group body-text body-text--md body-text--semibold">     <span>body-text body-text--lg body-text--bold</span>     <span>body-text body-text--lg body-text--regular</span>   </p>   <!-- Column 3 -->   <p class="body-text body-text--md body-text--semibold">Proxima Nova Bold and Regular, 20px</p>   <!-- Column 4 -->   <p class="group body-text body-text--md body-text--semibold">     <span>Large button title</span>     <span>Form label</span>     <span>Large modal text</span>   </p> </div>

The basic setup

We have a parent component, TypographyTable.vue, which contains the markup for the wrapper table element, and a child component, TypographyRow.vue, which creates a row and contains our render function.

I loop through the row component, passing the row data as props.

<template>   <section>     <!-- Headers hardcoded for simplicity -->     <div class="row">       <p class="body-text body-text--lg-bold heading">Hierarchy</p>       <p class="body-text body-text--lg-bold heading">Element/Class</p>       <p class="body-text body-text--lg-bold heading">Properties</p>       <p class="body-text body-text--lg-bold heading">Usage</p>     </div>       <!-- Loop and pass our data as props to each row -->     <typography-row       v-for="(rowData, index) in $ options.typographyData"       :key="index"       :row-data="rowData"     />   </section> </template> <script> import TypographyData from "@/data/typography.json"; import TypographyRow from "./TypographyRow"; export default {   // Our data is static so we don't need to make it reactive   typographyData: TypographyData,   name: "TypographyTable",   components: {     TypographyRow   } }; </script>

One neat thing to point out: the typography data can be a property on the Vue instance and be accessed using $ options.typographyData since it doesn’t change and doesn’t need to be reactive. (Hat tip to Anton Kosykh.)

Making a functional component

The TypographyRow component that passes data is a functional component. Functional components are stateless and instanceless, which means that they have no this and don’t have access to any Vue lifecycle methods.

The empty starting component looks like this:

// No <template> <script> export default {   name: "TypographyRow",   functional: true, // This property makes the component functional   props: {     rowData: { // A prop with row data       type: Object     }   },   render(createElement, { props }) {     // Markup gets rendered here   } } </script>

The render method takes a context argument, which has a props property that’s de-structured and used as the second argument.

The first argument is createElement, which is a function that tells Vue what nodes to create. For brevity and convention, I’ll be abbreviating createElement as h. You can read about why I do that in Sarah’s post.

h takes three arguments:

  1. An HTML tag (e.g. div)
  2. A data object with template attributes (e.g. { class: 'something'})
  3. Text strings (if we’re just adding text) or child nodes built using h
render(h, { props }) {   return h("div", { class: "example-class }, "Here's my example text") }

OK, so to recap where we are at this point, we’ve covered creating:

  • a file with the data that’s going to be used in my visualization;
  • a regular Vue component where I’m importing the full data file; and
  • the beginning of a functional component that will display each row.

To create each row, the data from the JSON file needs to be passed into arguments for h. This could be done all at once, but that involves a lot of conditional logic and is confusing.

Instead, I decided to do it in two parts:

  1. Transform the data into a predictable format.
  2. Render the transformed data.

Transforming the common data

I wanted my data in a format that would match the arguments for h, but before doing this, I wrote out how I wanted things structured:

// One cell {   tag: "", // HTML tag of current level   cellClass: "", // Class of current level, null if no class exists for that level   text: "", // Text to be displayed    children: [] // Children each follow this data model, empty array if no child nodes }

Each object represents one cell, with four cells making up each row (an array).

// One row [ { cell1 }, { cell2 }, { cell3 }, { cell4 } ]

The entry point would be a function like:

function createRow(data) { // Pass in the full row data and construct each cell   let { text, element, classes = null, properties, usage } = data;   let row = [];   row[0] = createCellData(data) // Transform our data using some shared function   row[1] = createCellData(data)   row[2] = createCellData(data)   row[3] = createCellData(data)    return row; }

Let’s take another look at our mockup.

The first column has styling variations, but the rest seem to follow the same pattern, so let’s start with those.

Again, the desired model for each cell is:

{   tag: "",   cellClass: "",    text: "",    children: [] }

This gives us a tree-like structure for each cell since some cells have groups of children. Let’s use two functions to create the cells.

  • createNode takes each of our desired properties as arguments.
  • createCell wraps around createNode so that we can check if the text that we’re passing in is an array. If it is, we build up an array of child nodes.
// Model for each cell function createCellData(tag, text) {   let children;   // Base classes that get applied to every root cell tag   const nodeClass = "body-text body-text--md body-text--semibold";   // If the text that we're passing in as an array, create child elements that are wrapped in spans.    if (Array.isArray(text)) {     children = text.map(child => createNode("span", null, child, children));   }   return createNode(tag, nodeClass, text, children); } // Model for each node function createNode(tag, nodeClass, text, children = []) {   return {     tag: tag,     cellClass: nodeClass,     text: children.length ? null : text,     children: children   }; }

Now, we can do something like:

function createRow(data) {   let { text, element, classes = null, properties, usage } = data;   let row = [];   row[0] = ""   row[1] = createCellData("p", ?????) // Need to pass in class names as text    row[2] = createCellData("p", properties) // Third column   row[3] = createCellData("p", usage) // Fourth column    return row; }

We pass properties and usage to the third and fourth columns as text arguments. However, the second column is a little different; there, we’re displaying the class names, which are stored in the data file like:

"classes": {   "base": "body-text body-text--lg",   "variants": ["body-text--bold", "body-text--regular"] },

Additionally, remember that headings don’t have classes, so we want to show the heading tag names for those rows (e.g. h1, h2, etc.).

Let’s create some helper functions to parse this data into a format that we can use for our text argument.

// Pass in the base tag and class names as arguments function displayClasses(element, classes) {   // If there are no classes, return the base tag (appropriate for headings)   return getClasses(classes) ? getClasses(classes) : element; }  // Return the node class as a string (if there's one class), an array (if there are multiple classes), or null (if there are none.)  // Ex. "body-text body-text--sm" or ["body-text body-text--sm body-text--bold", "body-text body-text--sm body-text--italic"] function getClasses(classes) {   if (classes) {     const { base, variants = null } = classes;     if (variants) {       // Concatenate each variant with the base classes       return variants.map(variant => base.concat(`$ {variant}`));     }     return base;   }   return classes; }

Now we can do this:

function createRow(data) {   let { text, element, classes = null, properties, usage } = data;   let row = [];   row[0] = ""   row[1] = createCellData("p", displayClasses(element, classes)) // Second column   row[2] = createCellData("p", properties) // Third column   row[3] = createCellData("p", usage) // Fourth column    return row; }

Transforming the demo data

This leaves the first column that demonstrates the styles. This column is different because we’re applying new tags and classes to each cell instead of using the class combination used by the rest of the columns:

<p class="body-text body-text--md body-text--semibold">

Rather than try to do this in createCellData or createNodeData, let’s make another function to sit on top of these base transformation functions and handle some of the new logic.

function createDemoCellData(data) {   let children;   const classes = getClasses(data.classes);   // In cases where we're showing off multiple classes, we need to create children and apply each class to each child.   if (Array.isArray(classes)) {     children = classes.map(child =>       // We can use "data.text" since each node in a cell grouping has the same text       createNode("span", child, data.text, children)     );   }   // Handle cases where we only have one class   if (typeof classes === "string") {     return createNode("p", classes, data.text, children);   }   // Handle cases where we have no classes (ie. headings)   return createNode(data.element, null, data.text, children); }

Now we have the row data in a normalized format that we can pass to our render function:

function createRow(data) {   let { text, element, classes = null, properties, usage } = data   let row = []   row[0] = createDemoCellData(data)   row[1] = createCellData("p", displayClasses(element, classes))   row[2] = createCellData("p", properties)   row[3] = createCellData("p", usage)    return row }

Rendering the data

Here’s how we actually render the data to display:

// Access our data in the "props" object const rowData = props.rowData;  // Pass it into our entry transformation function const row = createRow(rowData);  // Create a root "div" node and handle each cell return h("div", { class: "row" }, row.map(cell => renderCells(cell)));  // Traverse cell values function renderCells(data) {    // Handle cells with multiple child nodes   if (data.children.length) {     return renderCell(       data.tag, // Use the base cell tag       { // Attributes in here         class: {           group: true, // Add a class of "group" since there are multiple nodes           [data.cellClass]: data.cellClass // If the cell class isn't null, apply it to the node         }       },       // The node content       data.children.map(child => {         return renderCell(           child.tag,           { class: child.cellClass },           child.text         );       })     );   }    // If there are no children, render the base cell   return renderCell(data.tag, { class: data.cellClass }, data.text); }  // A wrapper function around "h" to improve readability function renderCell(tag, classArgs, text) {   return h(tag, classArgs, text); }

And we get our final product! Here’s the source code again.

Wrapping up

It’s worth pointing out that this approach represents an experimental way of addressing a relatively trivial problem. I’m sure many people will argue that this solution is needlessly complicated and over-engineered. I’d probably agree.

Despite the up-front cost, however, the data is now fully separated from the presentation. Now, if my design team adds or removes rows, I don’t have to dig into messy HTML — I just update a couple of properties in the JSON file.

Is it worth it? Like everything else in programming, I guess it depends. I will say that this comic strip was in the back of my mind as I worked on this:

A three-panel comic strip. First panel is a stick figure at a dinner table asking to pass the salt. Second panel is the same figure with no dialogue. Third panel is another figure saying he's building a system to pass the condiments and that it will save time in the long run. First figure says it's already been 20 minutes.
Source: https://xkcd.com/974

Maybe that’s an answer. I’d love to hear all of your (constructive) thoughts and suggestions, or if you’ve tried other ways of going about a similar task.

The post A Practical Use Case for Vue Render Functions: Building a Design System Typography Grid appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]

The practical value of semantic HTML

I love how Bruce steps up to the plate here:

If the importance of good HTML isn’t well-understood by the newer breed of JavaScript developers, then it’s my job as a DOWF (Dull Old Web Fart) to explain it.

Then he points out some very practical situations in which good HTML brings meaningful benefits. Maybe benefits isn’t the right word, as much as requirement since most of it is centered around accessibility.

I hope I’ve shown you that choosing the correct HTML isn’t purely an academic exercise…

Semantic HTML will give usability benefits to many users, help to future-proof your work, potentially boost your search engine results, and help people with disabilities use your site.

I think it’s fair to call HTML easy. Compared to many other things you’ll learn in your journey building websites, perhaps it is. All the more reason to get it right.

Estelle Weyl has some similar thoughts:

… take the radio button. All you have to do is give all the radio buttons in your button group the same name, preferably with differed values. Associate a label to each radio button to define what each one means. Simply using allows for selecting only one value with completely accessible, fast keyboard navigation. Each radio button is keyboard focusable. Users can select a different radio button by using the arrow keys, or clicking anywhere on the label or button. The arrows cycle thru the radio buttons, going from the last in the group to the first with a click of the down or right arrow. Developers don’t have to listen for keyboard, mouse, or touch interactions with JavaScript. These native interactions are robust and accessible. There is rarely a reason to rewrite them, especially since they’ll always work, even if the JavaScript doesn’t.

Direct Link to ArticlePermalink

The post The practical value of semantic HTML appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]