Social Cards as a Service

I love the idea of programmatically generated images. That power is close at hand these days for us front-end developers, thanks to the concept of headless browsers. Take Puppeteer, the library for controlling headless Chrome. Generating images from URLs is their default use case:

const puppeteer = require('puppeteer');  (async () => {   const browser = await puppeteer.launch();   const page = await browser.newPage();   await page.goto('https://example.com');   await page.screenshot({path: 'example.png'});    await browser.close(); })();

That ought to get the ol’ mind grape going. What if we had URLs on our site that — with the power of our HTML and CSS skills — created perfect little designs for sharing using dynamic data… then turned them into images and used them for our meta tags?

The first I saw of this idea was Drew McLellan’s Dynamic Social Sharing Images. Drew wrote a script to fire up Puppeteer and get the job done.

Since the design part is entirely an HTML/CSS adventure, I’m sure you could imagine a setup where the URL passed in parameters that did things like set copy and typography, colors, sizes, etc. Zeit built exactly that!

The URL is like this:

https://og-image.now.sh/I%20am%20Chris%20and%20I%20am%20**cool**%20la%20tee%20ding%20dong%20da..png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.zeit.co%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fhyper-color-logo.svg

Kind of amazing that you can spin up an entire browser in a cloud function! Netlify also offers cloud functions, and when I mentioned this to Phil Hawksworth, he told me he was already doing this for his blog!

So on a blog post like this one, an image like this is automatically generated:

Which is inserted as meta:

<meta property="og:image" content="https://www.hawksworx.com/card-image/-blog-find-that-at-card.png">

I dug through Phil’s repos, naturally, and found his little machine for doing it.

I’m madly envious of all this and need to get one set up for myself.

The post Social Cards as a Service appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

instant.page

instant.page is a pretty cool project from Alexandre Dieulot. Alexandre has been at this idea for half a decade now, as InstantClick is his and is essentially the same exact idea.

The idea is that there is a significant delay between hovering over a link and clicking that link. Say it takes you 300ms of delay. That 300ms could have been spent preloading the next page. And if you do use that time preloading, that page loads that much faster.

This new project makes use of newer tech to get it done. It’s hardly any code., the core of which is appending a <link rel="prefetch" href=""> to the document of the link you’re about to click/touch.

The page encourages you to hotlink the script, which means possible cache-hits in case you’ve already visited a page using this. It’s not risky in the way other third-party JavaScript can be because the integrity attribute means that if you trust the code as it is now, it can’t ever change unless you change that attribute along with it. It also cleverly uses the type="module" to prevent it from loading anything in browsers that don’t support prefetching anyway.

Still, you could self-host it if you wanted. I have no idea who’s ponying up the for the bandwidth here, so another risk is a hung script should it stop responding one day.

You could argue that it doesn’t do the prefetching as absolutely responsibly as it could. Google’s similar quick link library (which we covered here) does two interesting things in which to attempt to be more responsible with prefetching: 1) wait for requestIdleCallback and 2) respects info from navigator.connection, like a user enabling data-saver mode.

Direct Link to ArticlePermalink

The post instant.page appeared first on CSS-Tricks.

CSS-Tricks

[Top]

How @supports Works

CSS has a neat feature that allows us to test if the browser supports a particular property or property:value combination before applying a block of styles — like how a @media query matches when, say, the width of the browser window is narrower than some specified size and then the CSS within it takes effect. In the same spirit, the CSS inside this feature will take effect when the property:value pair being tested is supported in the current browser. That feature is called @supports and it looks like this:

@supports (display: grid) {   .main {     display: grid;   } }

Why? Well, that’s a bit tricky. Personally, I find don’t need it all that regularly. CSS has natural fallback mechanisms such that if the browser doesn’t understand a property:value combination, then it will ignore it and use something declared before it if there is anything, thanks to the cascade. Sometimes that can be used to deal with fallbacks and the end result is a bit less verbose. I’m certainly not a it’s-gotta-be-the-same-in-every-browser kinda guy, but I’m also not a write-elaborate-fallbacks-to-get-close kinda guy either. I generally prefer a situation where a natural failure of a property:value doesn’t do anything drastic to destroy functionality.

That said, @supports certainly has use cases! And as I found out while putting this post together, plenty of people use it for plenty of interesting situations.

A classic use case

The example I used in the intro is a classic one that you’ll see used in lots of writing about this topic. Here it is a bit more fleshed out:

/* We're gonna write a fallback up here in a minute */  @supports (display: grid) {   .photo-layout {     display: grid;     grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));     grid-gap: 2rem;   } }

Nice grid! Repeating and auto-filling columns is a sweet feature of CSS grid. But, of course, there are browsers that don’t support grid, or don’t support all the specific features of it that I’m using above there.

For example, iOS shipped support for CSS grid in version 10.2, but iOS has had flexbox support since version 7. That’s a decent gap of people with older iOS devices that do support flexbox but not grid. I’m sure there are more example gaps like that, but you probably get the idea.

It may be acceptable to let the fallback for this be nothing, depending on the requirements. For example, vertically stacked block-level elements rather than a multi-column grid layout. That’s often fine with me. But let’s say it’s not fine, like a photo gallery or something that absolutely needs to have some basic grid-like structure. In that case, starting with flexbox as the default and using @supports to apply grid features where they’re supported may work better…

.photo-layout {   display: flex;   flex-wrap: wrap;   > div {     flex: 200px;     margin: 1rem;   } }  @supports (display: grid) {   .photo-layout {     display: grid;     grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));     grid-gap: 2rem;     > div {       margin: 0;     }   } }

The “fallback” is the code outside of the @supports block (the properties above the block in the example above), and the grid code is either inside or after. The @supports block doesn’t change any specificity, so we need the source order to help make sure the overrides work.

Notice I had to reset the margin on the divs inside the @supports block. That’s the kind of thing I find a bit annoying. There is just enough crossover between the two scenarios that it requires being super aware of how they impact each other.

Doesn’t that make you wish it could be entirely logically separated…

There is “not” logic in @supports blocks, but that doesn’t mean it should always be used

Jen Simmons put this example in an article called Using Feature Queries in CSS a few years ago:

/* Considered a BAD PRACTICE, at least if you're supporting IE 11 and iOS 8 and older */ @supports not (display: grid) {    /* Isolated code for non-support of grid */ } @supports (display: grid) {    /* Isolated code for support of grid */ }

Notice the not operator in the first block. That’s checking for browsers that do not support grid in order to apply certain styles to those browsers. The reason this approach is considered bad practice is that the browser support for @supports itself has to be considered!. That’s what makes this so dang tricky.

It’s very appealing to write code in logically separated @supports blocks like that because then it’s starting from scratch each time and doesn’t need to be overriding previous values and dealing with those logical mind-benders. But let’s go back to the same iOS situation we considered before… @supports shipped in iOS in version 9 (right between when flexbox shipped in iOS 7 and grid shipped in iOS 10.2). That means any flexbox fallback code in a @supports block using the not operator to check for (display: grid) {} support wouldn’t work in either iOS 7 or 8, meaning the fallback now needs a fallback from working in browsers it otherwise would have. Phew!

The big reason to reach for @supports is to account for very different implementations of something depending on feature support where it becomes easier to reason and distinguish between those implementations if the blocks of code are separated.

We’ll probably get to a point where we can use mutually-exclusive blocks like that without worry. Speaking of which…

@supports is likely to be more useful over time.

Once @supports is supported in all browsers you need to support, then it’s good to start using it more aggressively and without having to factor in the complication of considering whether @supports itself is supported. Here’s the support grid on that:

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

Desktop

Chrome Opera Firefox IE Edge Safari
28 12.1 22 No 12 9

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
9.0-9.2 46 all 4.4 71 64

Basically, IE 11 and any iOS device stuck on iOS 8 are the pain points. If your requirements are already past those, then you’re good to use @supports more freely.

The irony is that there hasn’t been a ton of CSS features shipping that have big clear @supports use cases — but there are some! Apparently, it’s possible to test new fancy stuff like Houdini:

(I’m not sure entirely what you’d put in the @supports block to do that. Has anyone else done this?)

When @supports isn’t doing anything useful

I’ve seen a good amount of @supports uses in the wild where the end result is exactly as it would be without using it. For example…

@supports (transform: rotate(5deg) {   .avatar {     transform: rotate(5deg);   } }

On some level, that makes perfect logical sense. If transforms are supported, use them. But it’s unnecessary if nothing different is happening in a non-support scenario. In this case, the transform can fail without the @supports block and the result is the same.

Here’s another example of that shaking out.

There are browser extensions for playing with @supports

There are two of them!

They are both centered around the idea that we can write @supports blocks in CSS and then toggle them on and off as if we’re looking at a rendering of the code in a browser that does or doesn’t support that feature.

Here’s a video of Keith’s tool applied to the scenario using grid with a flexbox fallback:

This is fun to play with and is very neat tech. But in this exact scenario, if I was able to pull off the layout identically with flexbox, then I’d probably just do that and save that little bit of technical debt.

Ire’s tool, which she wrote about in the article Creating The Feature Queries Manager DevTools Extension, has a slightly different approach in that it shows the feature queries that you actually wrote in your CSS and provides toggles to flip them on and off. I don’t think it works through iframes though, so I popped open Debug Mode to use it on CodePen.

More real world use cases for @supports

Here’s one from Erik Vorhes. He’s styling some custom checkboxes and radio buttons here, but wraps all of it in a @supports block. None of the styling gets applied unless the block passes the support check.

@supports (transform: rotate(1turn)) and (opacity: 0) {   /* all the styling for Erik's custom checkboxes and radio buttons */ }

Here are several more I’ve come across:

  • Joe Wright and Tiago Nunes mentioned using it for position: sticky;. I’d love to see a demo here! As in, where you go for position: sticky;, but then have to do something different besides let it fail for a non-supporting browser.
  • Keith Grant and Matthias Ott mention using it for object-fit: contain;. Matthias has a demo where positioning trickery is used to make an image sort of fill a container, which is then made easier and better through that property when it’s available.
  • Ryan Filler mentions using it for mix-blend-mode. His example sets more opacity on an element, but if mix-blend-mode is supported, it uses a bit less and that property which can have the effect of seeing through an element on its own.
  • .thing {   opacity: 0.5; } @supports (mix-blend-mode: multiply) {   .thing {     mix-blend-mode: multiply;     opacity: 0.75;   } }
  • Rik Schennink mentioned the backdrop-filter property. He says, “when it’s supported the opacity of the background color often needs some fine tuning.”
  • Nour Saud mentioned it can be used to detect Edge through a specific vendor-prefixed property: @supports (-ms-ime-align:auto) { }.
  • Amber Weinberg mentioned using it for clip-path because adjusting the sizing or padding of an element will accommodate when clipping is unavailable.
  • Ralph Holzmann mentioned using it to test for that “notch” stuff (environment variables).
  • Stacy Kvernmo mentioned using it for the variety of properties needed for drop capping characters. Jen Simmons mentions this use case in her article as well. There is an initial-letter CSS property that’s pretty fantastic for drop caps, but is used in conjunction with other properties that you may not want to apply at all if initial-letter isn’t supported (or if there’s a totally different fallback scenario).

Here’s a bonus one from Nick Colley that’s not @supports, but @media instead! The spirit is the same. It can prevent that “stuck” hover state on touch devices like this:

@media (hover: hover) {   a:hover {     background: yellow;   } }

Logic in @supports

Basic:

@supports (initial-letter: 4) {  }

Not:

@supports not (initial-letter: 4) {  }

And:

@supports (initial-letter: 4) and (transform: scale(2)) {  }

Or:

@supports (initial-letter: 4) or (-webkit-initial-letter: 4) {  }

Combos:

@supports ((display: -webkit-flex) or           (display: -moz-flex) or           (display: flex)) and (-webkit-appearance: caret) {  }

JavaScript Variant

JavaScript has an API for this. To test if it exists…

if (window.CSS && window.CSS.supports) {   // Apparently old Opera had a weird implementation, so you could also do:   // !!((window.CSS && window.CSS.supports) || window.supportsCSS || false) }

To use it, either pass the property to it in one param and the value in another:

const supportsGrid = CSS.supports("display", "grid");

…or give it all in one string mirroring the CSS syntax:

const supportsGrid = CSS.supports("(display: grid)");

Selector testing

At the time of this writing, only Firefox supports this sort of testing (behind an experimental flag), but there is a way to test the support of selectors with @supports. MDN’s demo:

@supports selector(A > B) { }

You?

Of course, we’d love to see Pens of @supports use cases in the comments. So share ’em!

The post How @supports Works appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

IE10-Compatible Grid Auto-Placement with Flexbox

If you work on web applications that support older browsers, and have lusted after CSS Grid from the sidelines like I have, I have some good news: I’ve discovered a clever CSS-only way to use grid auto-placement in IE10+!

Now, it’s not actually CSS Grid, but without looking at the code itself, you wouldn’t be able to tell. The HTML structure looks like CSS Grid. It has a defined set of columns with an undefined amount of rows and it has gutters that support borders and shadows on the cells without hacks. But what’s actually happening behind the scenes is a combination of flexbox and margins.

In this article, I’ll walk through the approach. Here’s a demo of what we’re looking at:

See the Pen
IE10-compatible CSS-Grid-like column layout
by Brian Holt (@bholtbholt)
on CodePen.

Auto-flowing rows with flexbox wrap

Five orange rectangles in two rows with three on the first row and two on the second row.
Flexbox-created auto-placement grid

Getting the basic grid setup is very simple. If you’re at all familiar with flexbox, I’m certain you’ve already guessed flex-wrap: wrap is the trick here. And you’d be right.

Let’s get the HTML markup in place before we write any CSS. We want it to resemble the same structure as if we were using auto-placement — a .grid container and an undefined number of .grid__cells.

<div class="grid">   <div class="grid__cell">...</div>   ... </div>

We set three grid breakpoints. A single-column, two-column, and three-column layout for mobile-devices, small screens, and medium screens, respectively. I’m using the breakpoints used in Bootstrap for this article, though we’d want to define them at actual points where the layout breaks if we were working with real content.

$ screen-sm-min: 768px; $ screen-sm-max: 991px; $ screen-md-min: 992px;
Five orange rectangles stacked on top of one another.
Mobile-first grid collapses into a single column

A mobile-first approach means our single-column layout is already complete since each .grid__cell is already a block. We set .grid to become a flexbox container after the first breakpoint, and wrap cells.

@media (min-width: $ screen-sm-min) {   .grid {     display: flex;     flex-wrap: wrap;   } }

Our two- and three-column layouts need explicit widths and flex properties; otherwise they’ll cram onto a single line. While testing IE10, I experienced unexpected behavior with the flex-basis property, and found setting an explicit width with flex-basis: auto was more consistent. This didn’t seem to be a problem with IE11 though.

.grid__cell {   min-width: 0;   flex: 1 1 auto; }  // Two-column grid @media (min-width: $ screen-sm-min) and (max-width: $ screen-sm-max) {   $ width: 50%;    .grid__cell {     width: $ width;   } }  // Three-column grid @media (min-width: $ screen-md-min) {   $ width: 33.33%;    .grid__cell {     width: $ width;   } }

We don’t need to wrap .grid__cell in a media query since its flex properties won’t have the effect when the parent isn’t a flexbox container. We also define an upper-limit to the two-column media query so it doesn’t affect the three-column grid.

And that’s it! We now have a responsive, fluid, wrapping flexbox grid. The easy part is done… well, as long as we only ever have items that are multiples of two and three. With flex: 1 1 auto, the last item will always take up any remaining space in the last row.

Three rows of orange rectangles. The first two rows have two columns of boxes and the third row has one single box that spans both columns.
Two-column grid on smaller screens
Two rows of orange rectangles. The first row has three columns of rectangles and the second row has two rectnagles that span the full width.
Three-column grid on large screens

Aligning cells in the last row

The elusive last row is why we’re here, right? By default, each cell will stretch to the end of the row in a flexbox layout, but grid leaves a blank spot. How do we do that in flexbox? With pseudo-elements!

The trick is to add a pseudo-element to the .grid container and set it like a cell. We define the :after pseudo-element cell at each of our breakpoints with the same width as a real cell.

@media (min-width: $ screen-sm-min) {   .grid {     ...      &:after {       content: '';       display: block;       flex: 1 1 auto;     }   } }  @media (min-width: $ screen-sm-min) and (max-width: $ screen-sm-max) {   $ width: 50%;    .grid:after {     width: $ width;   } }  @media (min-width: $ screen-md-min) {   $ width: 33.33%;    .grid:after {     width: $ width;   } }

This creates a fake cell that will push against our real cells and align our two-column grid when the cells are odd. Leaving its height undefined allows it to collapse to nothing when the cells are even.

Three rows of orange rectangles. First two rows have two columns, each with a rectangle. Third row has a single rectangle and an empty column.
Two-column grid with odd cells, snapping into place

Our three-column grid is a bit more complex because we need to handle multiple states, like when there is one empty cell and when there are two empty cells.

Three column grid of orange rectangles with two rows. The second row only has one rectangle and an empty column.
Three-column grid with one empty cell

Our one empty cell state is already handled because it isn’t really any different from one empty cell in two columns. The :after cell has its width set and completes the row. The story changes when there are two empty cells though because flex: 1 1 auto rears its head again: the last cell now stretches across 50% of the width when pushed against the pseudo-element.

Three column grid of orange rectangles with two rows. The second row has one rectangle that spans half the grid width leaving an empty white space.
Three-column grid with two empty cells

Using CSS :nth-of-type selectors, we can target the first column in each row. Since our rows are multiples of three, we target them with 3n then count backwards by 2 to get the first element in each row.

@media (min-width: $ screen-md-min) {   .grid__cell {     ...      &:nth-of-type(3n-2) {       background-color: red;     }   } }
Three column grid of rectangles. The first column of rectangles is red indicating the rectangles that are selected with CSS. The other rectangles are orange.
Targeting the first cell in each three-column row

We’re broadly targeting all the cells in the first column, but we need to limit the selection to only the last row. Actually, we need to limit it to when it’s the last cell in the first column of the last row. Luckily, there’s a handy pseudo-selector for targeting the last item of its kind. We chain :last-of-type to create the logical statement.

@media (min-width: $ screen-md-min) {   .grid__cell {     ...     &:nth-of-type(3n-2):last-of-type {       background-color: red;     }   } }

Now that we have the last cell in the first column of the last row selected, we use a margin to push the :after cell to the last column and fill the middle cell.

@media (min-width: $ screen-md-min) {   .grid__cell {     ...      &:nth-of-type(3n-2):last-of-type {       margin-right: $ width;     }   } }

Here’s our flexbox-defined-auto-placement-grid-imitator in full. Look at its beautifully lined up rows. I bet you can’t even tell it’s not CSS Grid!

Three column grid with three rows of orange rectangles. The last row has a single rectangle in the first column and the other two columns are empty.
Our complete three-column grid.

Adding gutters with margins

CSS Grid’s spec has a column and row gap to provide space between each cell. Creating gutters in flexbox is much more challenging. It looks like it’s coming to flexbox, but we’re not there yet…and IE will never be.

In Daniel Tonon’s guide on CSS Grid in IE, he used an inner-cell div with negative margins, borders, a bit of padding, and overflow: hidden. While maybe a bit hacky, the effect works, but it breaks our desire to maintain CSS Grid-like HTML structure. The approach I prefer might feel a bit crude, but I also found it the easiest to read and understand. Further, it continues using :nth-of-type pseudo-selectors which makes the overall approach feel consistent.

We want gaps between the cells, but not around the outside. We also want our cells to sit flush with the container.

A three-by-two grid of orange rectangles. A block arrow is pointing at a gap between the rectangles.
Gaps between the cells, not on the outside.

Our mobile or single-column grid only needs a bottom margin on the cells. We add that and override the very last cell with margin-bottom: 0 so the cell fits flush against the container. Normally I’d use initial, but there’s no support in IE.

$ col-gap: 16px;  .grid__cell {   ...   margin-bottom: $ col-gap;    &:last-of-type {     margin-bottom: 0;   } }
A single column of orange rectangles in five rows.
Single-column grid with gaps between each row

Our two- and three-column grids need margins on the right of the cells, no right margins in the last column, and no bottom margins on any of the last row’s cells. Because of the margins, we’ll also need to recalculate our widths since the cells will wrap if they don’t fit.

In a two-column layout, getting the right (or second) column is fairly easy with :nth-of-type(2n) or :nth-of-type(even). I prefer an n-multiplier for consistency with our three-column grid and for calculating the last row.

Our last row is a bit more tricky. When we have odd cells our mobile-first CSS takes care of removing the bottom margins since the cell is the :last-of-type and our :after cell doesn’t have margins applied.

A two-by-three grid of orange rectangles. The last rectangle is a little taller than the others.
Two-columns with even cells

When we have even cells we need to target the second last cell, but only when it is in the first column position. If we didn’t qualify it, the second last cell will grow vertically with to match the height of the second last row. We can target it with :nth-of-type(2n-1):nth-last-of-type(2).

@media (min-width: $ screen-sm-min) and (max-width: $ screen-sm-max) {   $ width: calc(50% - #{$ col-gap});    .grid__cell {     ...     margin-right: $ col-gap;      // Remove margin in last column     &:nth-of-type(2n) {       margin-right: 0;     }      // For when the last row is complete     // . .     // * .     &:nth-of-type(2n-1):nth-last-of-type(2) {       margin-bottom: 0;     }   } }
The same two-by-three grid as before, but with the last rectangle at an even height with the rest.
Two-columns with even cells that sit flush against the container

Our three-column gutters take the same approach. We add margin-right to all of them, remove it from the third column, and remove bottom margins from the last row. Again our last cell is handled by our mobile-first approach, but now we need to cover when there are two cells in the last row and when when there are three cells. We can qualify our selectors with nth-of-type and nth-last-of-type.

@media (min-width: $ screen-md-min) {   $ width: calc(33% - #{$ col-gap});    .grid__cell {     ...     margin-right: $ col-gap;      // Remove margin in last column     &:nth-of-type(3n) {       margin-right: 0;     }      // For when there two items in the last row     // . . .     // * .     &:nth-of-type(3n-2):nth-last-of-type(2) {       margin-bottom: 0;     }      // For when the last row is complete     // . . .     // * * .     &:nth-of-type(3n-1):nth-last-of-type(2),     &:nth-of-type(3n-2):nth-last-of-type(3) {       margin-bottom: 0;     }   } }
A three-by-three grid of orange rectangles, with the last cell empty.
Three-column grid with gutters and an empty cell

We need to adjust the margin of last cell in the last row when it’s alone because of the columns. We use 33% plus a gutter on each side.

@media (min-width: $ screen-md-min) {   $ width: calc(33% - #{$ col-gap});    .grid__cell {     ...     // When there is only one item in the last rpw     // Fill the margin so it's like the last item is     // double the width     // . . .     // *->     &:nth-of-type(3n-2):last-of-type {       margin-right: calc(33% + #{$ col-gap * 2});     }   } }

Now our gutters are installed and the grid is complete! Fill them borders, shadows, or whatever your heart desires.

A three-by-two grid of orange rectangles with the last cell empty.
Complete three-column grid with gutters using flexbox.

Wrapping up

Here’s the final result one more time:

See the Pen
IE10-compatible CSS-Grid-like column layout
by Brian Holt (@bholtbholt)
on CodePen.

I believe this technique could also support IE9 with minor adjustments, like using inline-blocks instead of flexbox. We could also expand to a four-column grid by adding another breakpoint and using the same approach as the three-column grid. Feel free to use this approach and I hope it helps!

The post IE10-Compatible Grid Auto-Placement with Flexbox appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Design Deals for the Week

[Top]

The #StateOfCSS 2019 Survey

You know about the State of JavaScript survey, where thousands upon thousands of developers were surveyed about all-things-JS, from frameworks to testing and many other things in between? Well, Sacha Greif has launched one focused entirely on CSS.

This is super timely given a lot of the content we and other sites have been posting lately centered around learning, complexity, changing roles, and more. Sacha captures it nicely:

This is especially interesting since it comes at a time where many are talking about a “Great Divide” between the “front” of the front-end (HTML, CSS) and the “back” of the front-end (JavaScript and its many frameworks and libraries). […] [T]he survey will be a great chance to take a snapshot of the community as it currently exists, and see how this evolves over the next couple years.

Sounds like a good goal. Let’s help by putting some responses in there!

Take Survey

Direct Link to ArticlePermalink

The post The #StateOfCSS 2019 Survey appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

The Magic of React-Based Multi-Step Forms

One way to deal with long, complex forms is to break them up into multiple steps. You know, answer one set of questions, move on to another, then maybe another, and so on and so forth. We often refer to these as multi-step forms (for obvious reasons), but others also take to calling it a “wizard” form.

Multi-step forms can be a great idea! By only showing a few inputs on a screen at a time, the form may feel more digestible and prevent users from feeling overwhelmed by a sea of form fields. Although I haven’t looked it up, I’m willing to say no one enjoys completing a ginormous form — that’s where multiple steps can come in handy.

The problem is that multi-step forms — while reducing perceived complexity on the front end — can feel complex and overwhelming to develop. But, I’m here to tell you that it’s not only achievable, but relatively straightforward using React as the base. So, that’s what we’re going to build together today!

Here’s the final product:

See the Pen
React Simple Wizard Form
by Nathan Sebhastian (@nathansebhastian)
on CodePen.

Let’s build it!

The easiest way to create a multi-step form is to create a container form element that contains all the steps inside of it as components. Here’s a visual showing that container (<MasterForm/>), the components inside of it (<Step1/>, <Step2/>, <Step3/>) and the way states and props are passed between them.

A diagram showing the master form component above three rectangles representing the three steps of the form, from left to right. Between the master form and the steps is a constructor, setState, and render.
<MasterForm/> serves as the container while three child components inside of it act as each step of the form.

Although it seems to be more complex than a regular form, a multi-step form still uses the same principles as a React form:

  • State is used for storing data and user inputs.
  • Component is used for writing methods and the interface.
  • Props are used for passing data and function into elements.

Instead of having one form component, we will have one parent component and three child components. In the diagram above, <MasterForm/> will send data and functions to the child components via props, and in turn, the child components will trigger a handleChange() function to set values in the state of <MasterForm/>. It’s one big happy family over here!

We’ll need a function to move the form from one step to another as well, and we’ll get to that a little later.

The step child (get it?) components will receive props from the <MasterForm/> parent component for value and onChange props.

  • <Step1/> component will render an email address input
  • <Step2/> will render a username input
  • <Step3/> will render a password input and a submit button

<MasterForm/> will supply both data and function into child components, and child components will pass user inputs back to the parent using its props.

Creating the step (child) components

First, we’ll create the form’s child components. We’re keeping things pretty barebones for this example by only using one input per step, but each step could really be as complex as we’d like. Since the child components look almost similar between one another, I’m just gonna show one of them here. But be sure to take a look at the demo for the full code.

class Step1 extends React.Component {   render() {     if (this.props.currentStep !== 1) { // Prop: The current step       return null     }     // The markup for the Step 1 UI     return(       <div className="form-group">         <label htmlFor="email">Email address</label>         <input           className="form-control"           id="email"           name="email"           type="text"           placeholder="Enter email"           value={this.props.email} // Prop: The email input data           onChange={this.props.handleChange} // Prop: Puts data into state         />       </div>     )   } }

Now we can put this child component into the form’s render() function and pass in the necessary props. Just like in React’s form documentation, we can still use handleChange() to put the user’s submitted data into state with setState(). A handleSubmit() function will run on form submit.

Next up, the parent component

Let’s make the parent component — which we’re all aware by now, we’re calling <MasterForm/> — and initialize its state and methods.

We’re using a currentStep state that will be initialized with a default value of 1, indicating the first step (<Step1/>) of the form. We’ll update the state as the form progresses to indicate the current step.

class MasterForm extends Component {   constructor(props) {     super(props)     // Set the initial input values     this.state = {       currentStep: 1, // Default is Step 1       email: '',       username: '',       password: '',      }     // Bind the submission to handleChange()      this.handleChange = this.handleChange.bind(this)   }    // Use the submitted data to set the state   handleChange(event) {     const {name, value} = event.target     this.setState({       [name]: value     })       }      // Trigger an alert on form submission   handleSubmit = (event) => {     event.preventDefault()     const { email, username, password } = this.state     alert(`Your registration detail: n        Email: $ {email} n        Username: $ {username} n       Password: $ {password}`)   }      // Render UI will go here... }

OK, that’s the baseline functionality we’re looking for. Next, we want to create the shell UI for the actual form add call the child components in it, including the required state props that will be passed from <MasterForm/> via handleChange().

render() {     return (   <React.Fragment>   <h1>A Wizard Form!</h1>   <p>Step {this.state.currentStep} </p>         <form onSubmit={this.handleSubmit}>        // Render the form steps and pass in the required props     <Step1        currentStep={this.state.currentStep}        handleChange={this.handleChange}       email={this.state.email}     />     <Step2        currentStep={this.state.currentStep}        handleChange={this.handleChange}       username={this.state.username}     />     <Step3        currentStep={this.state.currentStep}        handleChange={this.handleChange}       password={this.state.password}     />           </form>   </React.Fragment> ) }

One step at a time

So far, we’ve allowed users to fill the form fields, but we’ve provided no actual way to proceed to the next step or head back to the previous one. That calls for next and previous functions that check if the current step has a previous or next step; and if it does, push the currentStep prop up or down accordingly.

class MasterForm extends Component {   constructor(props) {     super(props)     // Bind new functions for next and previous     this._next = this._next.bind(this)     this._prev = this._prev.bind(this)   }    // Test current step with ternary   // _next and _previous functions will be called on button click   _next() {     let currentStep = this.state.currentStep     // If the current step is 1 or 2, then add one on "next" button click     currentStep = currentStep >= 2? 3: currentStep + 1     this.setState({       currentStep: currentStep     })   }        _prev() {     let currentStep = this.state.currentStep     // If the current step is 2 or 3, then subtract one on "previous" button click     currentStep = currentStep <= 1? 1: currentStep - 1     this.setState({       currentStep: currentStep     })   } }

We’ll use a get function that will check whether the current step is 1 or 3. This is because we have three-step form. Of course, we can change these checks as more steps are added to the form. We also want to display the next and previous buttons only if there actually are next and previous steps to navigate to, respectively.

// The "next" and "previous" button functions get previousButton(){   let currentStep = this.state.currentStep;   // If the current step is not 1, then render the "previous" button   if(currentStep !==1){     return (       <button          className="btn btn-secondary"          type="button" onClick={this._prev}>       Previous       </button>     )   }   // ...else return nothing   return null; }  get nextButton(){   let currentStep = this.state.currentStep;   // If the current step is not 3, then render the "next" button   if(currentStep <3){     return (       <button          className="btn btn-primary float-right"          type="button" onClick={this._next}>       Next       </button>             )   }   // ...else render nothing   return null; }

All that’s left is to render those buttons:

// Render "next" and "previous" buttons render(){   return(     <form onSubmit={this.handleSubmit}>       {/*          ... other codes       */}              {this.previousButton}       {this.nextButton}            </form>   ) }

Congrats, you’re a form wizard! 🧙

That was the last step in this multi-step tutorial on multi-step forms. Whoa, how meta! While we didn’t go deep into styling, hopefully this gives you a solid overview of how to go about making complex forms less… complex!

Here’s that final demo again so you can see all the code in it’s full and glorious context:

See the Pen
React Simple Wizard Form
by Nathan Sebhastian (@nathansebhastian)
on CodePen.

React was made for this sort of thing considering it makes use of states, property changes, reusable components and such. I know that React may seem like a high barrier to entry for some folks, but I’ve written a book that makes it a much lower hurdle. I hope you check it out!

The post The Magic of React-Based Multi-Step Forms appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Vera Shimunia Embroiders Stunning Landscapes by Hand

[Top]

Use monday.com to manage and share projects all in one place

(This is a sponsored post.)

We’ve talked quite a bit about project management and workflows around here at CSS-Tricks, not because it’s the core of what we do as designers and developers, but because we all play a role in it as part of a team and because it impacts the quality of our work at the end of the day.

That’s why having a good system in place is such a benefit both to us and to teams as a whole. Where can you find a system like that? You might want to start by looking at monday.com. Yes, it’s a project management tool but it actually goes way beyond that. Where some other platforms out there stop at task lists, calendars, and milestones, monday.com does those plus team collaboration.

If you’ve ever felt out of the loop on a project, had a surprise change in scope, or even been curious what other folks on your team have been up to, that’s where monday.com really shines. It’s people-centric, giving you and others insight into activity across an entire project through news feeds, messaging, shared assets, clearly defined user roles, among any other things. It’s what a healthy, transparent, and collaborative team environment looks like.

We’ve only scratched the surface here, but lucky for you, there’s a free 14-day trial to check out everything that monday.com has to offer. Go for it!

Try it Now

Direct Link to ArticlePermalink

The post Use monday.com to manage and share projects all in one place appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]