Tag: Look

A Look at What’s New in Chrome DevTools in 2020

I’m excited to share some of the newer features in Chrome DevTools with you. There’s a brief introduction below, and then we’ll cover many of the new DevTools features. We’ll also look at what’s happening in some other browsers. I keep up with this stuff, as I create Dev Tips, the largest collection of DevTools tips you’ll find online! 

It’s a good idea to find out what’s changed in DevTools because it’s constantly evolving and new features are specifically designed to help and improve our development and debugging experience.

Let’s jump into the latest and greatest. While the public stable version of Chrome does have most of these features, I’m using Chrome Canary as I like to stay on the bleeding edge.

Lighthouse

Lighthouse is an open source tool for auditing web pages, typically around performance, SEO, accessibility and such. For a while now, Lighthouse has been bundled as part of DevTools meaning you can find it in a panel named… Lighthouse!

Screenshot of DevTools open on a CSS-Tricks page. The Lighthouse panel is open showing a best practices score of 100 out of 100.
Well done, Mr. Coyier. 🏆

I really like Lighthouse because it’s one of easiest parts of DevTools to use. Click “Generate report” and you immediately get human-readable notes for your webpage, such as:

Document uses legible font sizes 100% legible text

Or:

Avoid an excessive DOM size (1,189 elements)

Almost every single audit links to developer documentation that explains how the audit may fail, and what you can do to improve it.

The best way to get started with Lighthouse is to run audits on your own websites:

  1. Open up DevTools and navigate to the Lighthouse panel when you are on one of your sites
  2. Select the items you want to audit (Best practices is a good starting point)
  3. Click Generate report
  4. Click on any passed/failed audits to investigate the findings

Even though Lighthouse has been part of DevTools for a while now (since 2017!), it still deserves a significant mention because of the user-facing features it continues to ship, such as:

  • An audit that checks that anchor elements resolve to their URLs (Fun fact: I worked on this!)
  • An audit that checks whether the Largest Contentful Paint metic is fast enough
  • An audit to warn you of unused JavaScript

A better “Inspect Element”

This is a subtle and, in some ways, very small feature, but it can have profound effects on how we treat web accessibility.

Here’s how it works. When you use Inspect Element — what is arguably the most common use of DevTools — you now get a tooltip with additional information on accessibility.

Screenshot showing DevTools open on a CSS-Tricks page. An element is highlighted on the page and a tooltip with a white background is above it providing information on the element's color, font, contrast, name, role, and whether it is keyboard-focusable.
Accessibility is baked right in!

The reason I say this can have a profound impact is because DevTools has had accessibility features for quite some time now, but how many of us actually use them? Including this information on a commonly used feature like Inspect Element will gives it a lot more visibility and makes it a lot more accessible.

The tooltip includes:

  • the contrast ratio of the text (how well, or how poorly, does the foreground text contrast with the background color)
  • the text representation
  • the ARIA role
  • whether or not the inspected element is keyboard-focusable

To try this out, right-click (or Cmd + Shift + C) on an element and select Inspect to view it in DevTools.

I made a 14-minute video on Accessibility debugging with Chrome DevTools which covers some of this in more detail.

Emulate vision deficiencies

Exactly as it says on the tin, you can use Chrome DevTools to emulate vision impairments. For example, we can view a site through the lens of blurred vision.

Screenshot of DevTools open on a CSS-Tricks page. The Rendering panel is open and the blurred vision option is selected. The CSS-Tricks page is blurry and difficult to read.
That’s a challenge to read!

How can you do this in DevTools? Like this:

  1. Open DevTools (right click and “Inspect” or Cmd + Shift + C).
  2. Open the DevTools Command menu (Cmd + Shift + P on Mac, Ctrl + Shift + P on Windows).
  3. Select Show Rendering in the Command menu.
  4. Select a deficiency in the Rendering pane.

We used blurred vision as an example, but DevTools has other options, including: protanopia, deuteranopia, tritanopia, and achromatopsia.

Like with any tool of this nature, it’s designed to be a complement to our (hopefully) existing accessibility skills. In other words, it’s not instructional, but rather, influential on the designs and user experiences we create.

Here are a couple of extra resources on low vision accessibility and emulation:

Get timing on performance

The Performance Panel in DevTools can sometimes look like a confusing mish-mash of shapes and colors.

This update to it is great because it does a better job surfacing meaningful performance metrics.

Screenshot of DevTools with the Performance panel open. A chart showing the timeline of page rendering is above a row of Timings, including DCL, FP, FCP, L, and LCP. Below that is a summary that provides a time range for the selected timing.

What we want to look at are those extra timing rectangles shown in the “Timings” in the Performance Panel recording. This highlights:

  • DOMContentLoaded: The event which triggers when the initial HTML loads
  • First Paint: When the browser first paints pixels to the screen
  • First Contentful Paint: The point at which the browser draws content from the DOM which indicates to the user that content is loading
  • Onload: When the page and all of its resources have finished loading
  • Largest Contentful Paint: The largest image or text element, which is rendered in the viewport

As a bonus, if you find the Largest Contentful Paint event in a Performance Panel recording, you can click on it to get additional information.

Nice work, CSS-Tricks! The Largest Contentful Paint happens early on in the page load.

While there is a lot of golden information here, the “Related Node” is potentially the most useful item because it specifies exactly which element contributed to the LCP event.

To try this feature out:

  1. Open up DevTools and navigate to the Performance panel
  2. Click “Start profiling and reload page”
  3. Observe the timing metrics in the Timings section of a recording
  4. Click the individual metrics to see what additional information you get

Monitor performance

If you want to quickly get started using DevTools to analyze performance and you’ve already tried Lighthouse, then I recommend the Performance Monitor feature. This is sort of like having WebPageTest.org right at your fingertips with things like CPU usage.

Screenshot of DevTools with the Performance Monitor pane open. Four timeline charts are stacked vertically, starting with CPU Usage,followed by JavaScript Heap Size, DOM Nodes, and JavaScript Event Listeners.

Here’s how to access it:

  1. Open DevTools
  2. Open up the Command menu (Cmd + Shift + P on Mac, Ctrl + Shift + P on Windows)
  3. Select “Show performance monitor” from the Command menu
  4. Interact and navigate around the website
  5. Observe the results

The Performance Monitor can give you interesting metrics, however, unlike Lighthouse, it’s for you to figure out how to interpret them and take action. No suggestions are provided. It’s up to you to study that CPU usage chart and ask whether something like 90% is an acceptable level for your site (it probably isn’t).

The Performance Monitor has an interactive legend, where you can toggle metrics on and off, such as:

  • CPU usage
  • JS heap size
  • DOM Nodes
  • JS event listeners
  • Documents
  • Document Frames
  • Layouts / sec
  • Style recalcs / sec 

CSS overview and local overrides

CSS-Tricks has already covered these features, so go and check them out!

  • CSS Overview: A handy DevTools panel that gives a bunch of interesting stats on the CSS your page is using
  • Local Overrides:  A powerful feature that lets you override production websites with your local resources, so you can easily preview changes 

So, what about DevTool in other browsers?

I’m sure you noticed that I’ve been using Chrome throughout this article. It’s the browser I use personally. That said, it’s worth considering that:

  • Firefox DevTools is looking pretty great right now
  • With Microsoft Edge extending from Chromium, it too will benefit from these DevTools features
  • As evident on the Safari Technology Preview Release Notes (search for Web Inspector on that page), Safari DevTools has come a long way 

In other words, keep an eye out because this is a quickly evolving space!

Conclusion

We covered a lot in a short amount of space!

  • Lighthouse: A panel that provides  tips and suggestions for performance, accessibility, SEO and best practices.
  • Inspect Element: An enhancement to the Inspect Element feature that provides accessibility information to the Inspect Element tooltip
  • Emulate vision deficiencies: A feature in the Rendering Pane to view a page through the lens of low vision.
  • Performance Panel Timings: Additional metrics in the Performance panel recording, showing user-orientated stats, like Largest Contentful Paint
  • Performance Monitor – A real-time visualization of performance metrics for the current website, such as CPU usage and DOM size

Please check out my mailing list, Dev Tips, if you want to stay keep up with the latest updates and get over 200 web development tips! I also have a premium video course over at ModernDevTools.com. And, I tend to post loads of bonus web development resources on Twitter.


The post A Look at What’s New in Chrome DevTools in 2020 appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,

A First Look at `aspect-ratio`

Oh hey! A brand new property that affects how a box is sized! That’s a big deal. There are lots of ways already to make an aspect-ratio sized box (and I’d say this custom properties based solution is the best), but none of them are particularly intuitive and certainly not as straightforward as declaring a single property.

So, with the impending arrival of aspect-ratio (MDN, and not to be confused with the media query version), I thought I’d take a look at how it works and try to wrap my mind around it.

Shout out to Una where I first saw this. Boy howdy did it strike interest in folks:

https://twitter.com/Una/status/1260980901934137345

Just dropping aspect-ratio on an element alone will calculate a height based on the auto width.

Without setting a width, an element will still have a natural auto width. So the height can be calculated from the aspect ratio and the rendered width.

.el {   aspect-ratio: 16 / 9; }

If the content breaks out of the aspect ratio, the element will still expand.

The aspect ratio becomes ignored in that situation, which is actually nice. Better to avoid potential data loss. If you prefer it doesn’t do this, you can always use the padding hack style.

If the element has either a height or width, the other is calculated from the aspect ratio.

So aspect-ratio is basically a way of seeing the other direction when you only have one (demo).

If the element has both a height and width, aspect-ratio is ignored.

The combination of an explicit height and width is “stronger” than the aspect ratio.

Factoring in min-* and max-*

There is always a little tension between width, min-width, and max-width (or the height versions). One of them always “wins.” It’s generally pretty intuitive.

If you set width: 100px; and min-width: 200px; then min-width will win. So, min-width is either ignored because you’re already over it, or wins. Same deal with max-width: if you set width: 100px; and max-width: 50px; then max-width will win. So, max-width is either ignored because you’re already under it, or wins.

It looks like that general intuitiveness carries on here: the min-* and max-* properties will either win or are irrelevant. And if they win, they break the aspect-ratio.

.el {   aspect-ratio: 1 / 4;   height: 500px;    /* Ignored, because width is calculated to be 125px */   /* min-width: 100px; */    /* Wins, making the aspect ratio 1 / 2 */   /* min-width: 250px; */ } 

With value functions

Aspect ratios are always most useful in fluid situations, or anytime you essentially don’t know one of the dimensions ahead of time. But even when you don’t know, you’re often putting constraints on things. Say 50% wide is cool, but you only want it to shrink as far as 200px. You might do width: max(50%, 200px);. Or constrain on both sides with clamp(200px, 50%, 400px);.

This seems to work inutitively:

.el {   aspect-ratio: 4 / 3;   width: clamp(200px, 50%, 400px); }

But say you run into that minimum 200px, and then apply a min-width of 300px? The min-width wins. It’s still intuitive, but it gets brain-bending because of how many properties, functions, and values can be involved.

Maybe it’s helpful to think of aspect-ratio as the weakest way to size an element?

It will never beat any other sizing information out, but it will always do its sizing if there is no other information available for that dimension.

The post A First Look at `aspect-ratio` appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

An Early Look at the Vue 3 Composition API in the Wild

I recently had an opportunity to try the new Vue Composition API in a real project to check where it might be useful and how we could use it in the future.

Until now, when we were creating a new component we were using Options API. That API forced us to separate the component’s code by options, meaning that we needed to have all reactive data in one place (data), all computed properties in one place (computed), all methods in one place (methods), and so on.

As it is handy and readable for smaller components, it becomes painful when the component gets more complicated and deals with multiple functionalities. Usually, logic related to one specific functionality contains some reactive data, computed property, a method or a few of them; sometimes it also involves using component lifecycle hooks. That makes you constantly jump between different options in the code when working on a single logical concern.

The other issue that you may have encountered when working with Vue is how to extract a common logic that can be reused by multiple components. Vue already has few options to do that, but all of them have their own drawbacks (e.g. mixins, and scoped-slots).

The Composition API brings a new way of creating component, separating code and extracting reusable pieces of code.

Let’s start with code composition within a component.

Code composition

Imagine you have a main component that sets up few things for your whole Vue app (like layout in Nuxt). It deals with the following things:

  • setting locale
  • checking if the user is still authenticated and redirects them if not
  • preventing the user from reloading the app too many times
  • tracking user activity and reacting when the user is inactive for specific period of time
  • listening on an event using EventBus (or window object event)

Those are just a few things the component can do. You can probably imagine a more complex component, but this will serve the purpose of this example. For the sake of readability, I am just using names of the props without the actual implementation.

This is how the component would look like using Options API:

<template>   <div id="app">     ...   </div> </template>  <script> export default {   name: 'App',    data() {     return {       userActivityTimeout: null,       lastUserActivityAt: null,       reloadCount: 0     }   },    computed: {     isAuthenticated() {...}     locale() {...}   },    watch: {     locale(value) {...},     isAuthenticated(value) {...}   },    async created() {     const initialLocale = localStorage.getItem('locale')     await this.loadLocaleAsync(initialLocale)   },    mounted() {     EventBus.$  on(MY_EVENT, this.handleMyEvent)      this.setReloadCount()     this.blockReload()      this.activateActivityTracker()     this.resetActivityTimeout()   },    beforeDestroy() {     this.deactivateActivityTracker()     clearTimeout(this.userActivityTimeout)     EventBus.$  off(MY_EVENT, this.handleMyEvent)   },    methods: {     activateActivityTracker() {...},     blockReload() {...},     deactivateActivityTracker() {...},     handleMyEvent() {...},     async loadLocaleAsync(selectedLocale) {...}     redirectUser() {...}     resetActivityTimeout() {...},     setI18nLocale(locale) {...},     setReloadCount() {...},     userActivityThrottler() {...},   } } </script>

As you can see, each option contains parts from all functionalities. There is no clear separation between them and that makes the code hard to read, especially if you are not the person who wrote it and you are looking at it for the first time. It is very hard to find which method is used by which functionality.

Let’s look at it again but identify the logical concerns as comments. Those would be:

  • Activity tracker
  • Reload blocker
  • Authentication check
  • Locale
  • Event Bus registration
<template>   <div id="app">     ...   </div> </template>  <script> export default {   name: 'App',    data() {     return {       userActivityTimeout: null, // Activity tracker       lastUserActivityAt: null, // Activity tracker       reloadCount: 0 // Reload blocker     }   },    computed: {     isAuthenticated() {...} // Authentication check     locale() {...} // Locale   },    watch: {     locale(value) {...},     isAuthenticated(value) {...} // Authentication check   },    async created() {     const initialLocale = localStorage.getItem('locale') // Locale     await this.loadLocaleAsync(initialLocale) // Locale   },    mounted() {     EventBus.$  on(MY_EVENT, this.handleMyEvent) // Event Bus registration      this.setReloadCount() // Reload blocker     this.blockReload() // Reload blocker      this.activateActivityTracker() // Activity tracker     this.resetActivityTimeout() // Activity tracker   },    beforeDestroy() {     this.deactivateActivityTracker() // Activity tracker     clearTimeout(this.userActivityTimeout) // Activity tracker     EventBus.$  off(MY_EVENT, this.handleMyEvent) // Event Bus registration   },    methods: {     activateActivityTracker() {...}, // Activity tracker     blockReload() {...}, // Reload blocker     deactivateActivityTracker() {...}, // Activity tracker     handleMyEvent() {...}, // Event Bus registration     async loadLocaleAsync(selectedLocale) {...} // Locale     redirectUser() {...} // Authentication check     resetActivityTimeout() {...}, // Activity tracker     setI18nLocale(locale) {...}, // Locale     setReloadCount() {...}, // Reload blocker     userActivityThrottler() {...}, // Activity tracker   } } </script>

See how hard it is to untangle all of those? 🙂

Now imagine you need to make a change in one functionality (e.g. activity tracking logic). Not only do you need to know which elements are related to that logic, but even when you know, you still need to jump up and down between different component options.

Let’s use the Composition API to separate the code by logical concerns. To do that we create a single function for each logic related to a specific functionality. This is what we call a composition function.

// Activity tracking logic function useActivityTracker() {   const userActivityTimeout = ref(null)   const lastUserActivityAt = ref(null)    function activateActivityTracker() {...}   function deactivateActivityTracker() {...}   function resetActivityTimeout() {...}   function userActivityThrottler() {...}    onBeforeMount(() => {     activateActivityTracker()     resetActivityTimeout()   })    onUnmounted(() => {     deactivateActivityTracker()     clearTimeout(userActivityTimeout.value)   }) }
// Reload blocking logic function useReloadBlocker(context) {   const reloadCount = ref(null)    function blockReload() {...}   function setReloadCount() {...}    onMounted(() => {     setReloadCount()     blockReload()   }) }
// Locale logic function useLocale(context) {   async function loadLocaleAsync(selectedLocale) {...}   function setI18nLocale(locale) {...}    watch(() => {     const locale = ...     loadLocaleAsync(locale)   })    // No need for a 'created' hook, all logic that runs in setup function is placed between beforeCreate and created hooks   const initialLocale = localStorage.getItem('locale')   loadLocaleAsync(initialLocale) }
// Event bus listener registration import EventBus from '@/event-bus'  function useEventBusListener(eventName, handler) {   onMounted(() => EventBus.$  on(eventName, handler))   onUnmounted(() => EventBus.$  off(eventName, handler)) }

As you can see, we can declare reactive data (ref / reactive), computed props, methods (plain functions), watchers (watch) and lifecycle hooks (onMounted / onUnmounted). Basically everything you normally use in a component.

We have two options when it comes to where to keep the code. We can leave it inside the component or extract it into a separate file. Since the Composition API is not officially there yet, there are no best practices or rules on how to deal with it. The way I see it, if the logic is tightly coupled to a specific component (i.e. it won’t be reused anywhere else), and it can’t live without the component itself, I suggest leaving it within the component. On the flip side, if it is general functionality that will likely be reused, I suggest extracting it to a separate file. However, if we want to keep it in a separate file, we need to remember to export the function from the file and import it in our component.

This is how our component will look like using newly created composition functions:

<template>   <div id="app">          </div> </template>  <script> export default {   name: 'App',    setup(props, context) {     useEventBusListener(MY_EVENT, handleMyEvent)     useActivityTracker()     useReloadBlocker(context)     useLocale(context)      const isAuthenticated = computed(() => ...)      watch(() => {       if (!isAuthenticated) {...}     })      function handleMyEvent() {...},      function useLocale() {...}     function useActivityTracker() {...}     function useEventBusListener() {...}     function useReloadBlocker() {...}   } } </script>

This gives us a single function for each logical concern. If we want to use any specific concern, we need to call the related composition function in the new setup function.

Imagine again that you need to make some change in activity tracking logic. Everything related to that functionality lives in the useActivityTracker function. Now you instantly know where to look and jump to the right place to see all the related pieces of code. Beautiful!

Extracting reusable pieces of code

In our case, the Event Bus listener registration looks like a piece of code we can use in any component that listens to events on Event Bus.

As mentioned before, we can keep the logic related to a specific functionality in a separate file. Let’s move our Event Bus listener setup into a separate file.

// composables/useEventBusListener.js import EventBus from '@/event-bus'  export function useEventBusListener(eventName, handler) {   onMounted(() => EventBus.$  on(eventName, handler))   onUnmounted(() => EventBus.$  off(eventName, handler)) }

To use it in a component, we need to make sure we export our function (named or default) and import it in a component.

<template>   <div id="app">     ...   </div> </template>  <script> import { useEventBusListener } from '@/composables/useEventBusListener'  export default {   name: 'MyComponent',    setup(props, context) {     useEventBusListener(MY_EVENT, myEventHandled)     useEventBusListener(ANOTHER_EVENT, myAnotherHandled)   } } </script>

That’s it! We can now use that in any component we need.

Wrapping up

There is an ongoing discussion about the Composition API. This post has no intention to promote any side of the discussion. It is more about showing when it might be useful and in what cases it brings additional value.

I think it is always easier to understand the concept on a real life example like above. There are more use cases and, the more you use the new API, the more patterns you will see. This post is merely a few basic patterns to get your started.

Let’s go again through the presented use cases and see where the Composition API can be useful:

General features that can live on its own without tight coupling with any specific component

  • All logic related to a specific feature in one file
  • Keep it in @/composables/*.js and import it in components
  • Examples: Activity Tracker, Reload Blocker, and Locale

Reusable features that are used in multiple components

  • All logic related to a specific feature in one file
  • Keep it in @/composables/*.js and import in components
  • Examples: Event Bus listener registration, window event registration, common animation logic, common library usage

Code organization within component

  • All logic related to a specific feature in one function
  • Keep the code in a composition function within the component
  • The code related to the same logical concern is in the same place (i.e. there’s no need to jump between data, computed, methods, lifecycle hooks, etc.)

Remember: This is all a work-in-progress!

The Vue Composition API is currently at work in progress stage and is subject to future changes. Nothing mentioned in the examples above is sure, and both syntax and use cases may change. It is intended to be shipped with Vue version 3.0. In the meantime, you can check out view-use-web for a collection of composition functions that are expected to be included in Vue 3 but can be used with the Composition API in Vue 2.

If you want to experiment with the new API you can use the @vue/composition library.

The post An Early Look at the Vue 3 Composition API in the Wild appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

A Look at JAMstack’s Speed, By the Numbers

People say JAMstack sites are fast — let’s find out why by looking at real performance metrics! We’ll cover common metrics, like Time to First Byte (TTFB) among others, then compare data across a wide section of sites to see how different ways to slice those sites up compare.

First, I’d like to present a small analysis to provide some background. According to the HTTPArchive metrics report on page loading, users wait an average of 6.7 seconds to see primary content.

First Contentful Paint (FCP) – measures the point at which text or graphics are first rendered to the screen.

The FCP distribution for the 10th, 50th and 90th percentile values as reported on August 1, 2019.

If we are talking about engagement with a page (Time to Interactive), users wait even longer. The average time to interactive is 9.3 seconds.

Time to Interactive (TTI) – a time when user can interact with a page without delay.

TTI distribution for the 10th, 50th and 90th percentile values as reported on August 1, 2019.

State of the real user web performance

The data above is from lab monitoring and doesn’t fully represent real user experience. Real users data based taken from the Chrome User Experience Report (CrUX) shows an even wider picture.

​​I’ll use data aggregated from users who use mobile devices. Specifically, we will use metrics like:


Time To First Byte

TTFB represents the time browser waits to receive first bytes of the response from server. TTFB takes from 200ms to 1 second for users around the world. It’s a pretty long time to receive the first chunks of the page.

TTFB mobile speed distribution (CrUX, July 2019)

First Contentful Paint

FCP happens after 2.5 seconds for 23% of page views around the world.

FCP mobile speed distribution (CrUX, July 2019)

First Input Delay

FID metrics show how fast web pages respond to user input (e.g. click, scroll, etc.).

CrUX doesn’t have TTI data due to different restrictions, but has FID, which is even better can reflect page interactivity. Over 75% of mobile user experiences have input delay for 50ms and users didn’t experience any jank.

FID mobile speed distribution (CrUX, July 2019)

You can use the queries below and play with them on this site.

Data from July 2019
[     {       "date": "2019_07_01",       "timestamp": "1561939200000",       "client": "desktop",       "fastTTFB": "27.33",       "avgTTFB": "46.24",       "slowTTFB": "26.43",       "fastFCP": "48.99",       "avgFCP": "33.17",       "slowFCP": "17.84",       "fastFID": "95.78",       "avgFID": "2.79",       "slowFID": "1.43"     },     {       "date": "2019_07_01",       "timestamp": "1561939200000",       "client": "mobile",       "fastTTFB": "23.61",       "avgTTFB": "46.49",       "slowTTFB": "29.89",       "fastFCP": "38.58",       "avgFCP": "38.28",       "slowFCP": "23.14",       "fastFID": "75.13",       "avgFID": "17.95",       "slowFID": "6.92"     }   ]
BigQuery
#standardSQL   SELECT     REGEXP_REPLACE(yyyymm, '(d{4})(d{2})', '1_2_01') AS date,     UNIX_DATE(CAST(REGEXP_REPLACE(yyyymm, '(d{4})(d{2})', '1-2-01') AS DATE)) * 1000 * 60 * 60 * 24 AS timestamp,     IF(device = 'desktop', 'desktop', 'mobile') AS client,     ROUND(SUM(fast_fcp) * 100 / (SUM(fast_fcp) + SUM(avg_fcp) + SUM(slow_fcp)), 2) AS fastFCP,     ROUND(SUM(avg_fcp) * 100 / (SUM(fast_fcp) + SUM(avg_fcp) + SUM(slow_fcp)), 2) AS avgFCP,     ROUND(SUM(slow_fcp) * 100 / (SUM(fast_fcp) + SUM(avg_fcp) + SUM(slow_fcp)), 2) AS slowFCP,     ROUND(SUM(fast_fid) * 100 / (SUM(fast_fid) + SUM(avg_fid) + SUM(slow_fid)), 2) AS fastFID,     ROUND(SUM(avg_fid) * 100 / (SUM(fast_fid) + SUM(avg_fid) + SUM(slow_fid)), 2) AS avgFID,     ROUND(SUM(slow_fid) * 100 / (SUM(fast_fid) + SUM(avg_fid) + SUM(slow_fid)), 2) AS slowFID   FROM     `chrome-ux-report.materialized.device_summary`   WHERE     yyyymm = '201907'   GROUP BY     date,     timestamp,     client   ORDER BY     date DESC,     client

State of Content Management Systems (CMS) performance

CMSs should have become our saviors, helping us build faster sites. But looking at the data, that is not the case. The current state of CMS performance around the world is not so great.

TTFB mobile speed distribution comparison between all web and CMS (CrUX, July 2019)
Data from July 2019
[     {       "freq": "1548851",       "fast": "0.1951",       "avg": "0.4062",       "slow": "0.3987"     }   ]
BigQuery
#standardSQL   SELECT     COUNT(DISTINCT origin) AS freq,            ROUND(SUM(IF(ttfb.start < 200, ttfb.density, 0)) / SUM(ttfb.density), 4) AS fastTTFB,     ROUND(SUM(IF(ttfb.start >= 200 AND ttfb.start < 1000, ttfb.density, 0)) / SUM(ttfb.density), 4) AS avgTTFB,     ROUND(SUM(IF(ttfb.start >= 1000, ttfb.density, 0)) / SUM(ttfb.density), 4) AS slowTTFB      FROM     `chrome-ux-report.all.201907`,     UNNEST(experimental.time_to_first_byte.histogram.bin) AS ttfb   JOIN (     SELECT       url,       app     FROM       `httparchive.technologies.2019_07_01_mobile`     WHERE       category = 'CMS'     )   ON CONCAT(origin, '/') = url   ORDER BY     freq DESC

And here are the FCP results:

FCP mobile speed distribution comparison between all web and CMS (CrUX, July 2019)

At least the FID results are a bit better:

FID mobile speed distribution comparison between all web and CMS (CrUX, July 2019)
Data from July 2019
[     {       "freq": "546415",       "fastFCP": "0.2873",       "avgFCP": "0.4187",       "slowFCP": "0.2941",       "fastFID": "0.8275",       "avgFID": "0.1183",       "slowFID": "0.0543"     }   ]
BigQuery
#standardSQL   SELECT     COUNT(DISTINCT origin) AS freq,     ROUND(SUM(IF(fcp.start < 1000, fcp.density, 0)) / SUM(fcp.density), 4) AS fastFCP,     ROUND(SUM(IF(fcp.start >= 1000 AND fcp.start < 2500, fcp.density, 0)) / SUM(fcp.density), 4) AS avgFCP,     ROUND(SUM(IF(fcp.start >= 2500, fcp.density, 0)) / SUM(fcp.density), 4) AS slowFCP,     ROUND(SUM(IF(fid.start < 50, fid.density, 0)) / SUM(fid.density), 4) AS fastFID,     ROUND(SUM(IF(fid.start >= 50 AND fid.start < 250, fid.density, 0)) / SUM(fid.density), 4) AS avgFID,     ROUND(SUM(IF(fid.start >= 250, fid.density, 0)) / SUM(fid.density), 4) AS slowFID   FROM     `chrome-ux-report.all.201907`,     UNNEST(first_contentful_paint.histogram.bin) AS fcp,     UNNEST(experimental.first_input_delay.histogram.bin) AS fid   JOIN (     SELECT       url,       app     FROM       `httparchive.technologies.2019_07_01_mobile`     WHERE       category = 'CMS'     )   ON CONCAT(origin, '/') = url   ORDER BY     freq DESC

As you can see, sites built with a CMS perform not much better than the overall performance of sites on web.

You can find performance distribution across different CMSs on this HTTPArchive forum discussion.

E-Commerce websites, a good example of sites that are typically built on a CMS, have really bad stats for page views:

  • ~40% – 1second for TTFB
  • ~30% – more than 1.5 second for FCP
  • ~12% – lag for page interaction.

I faced clients who requested support of IE10-IE11 because the traffic from those users represented 1%, which equalled millions of dollars in revenue. Please, calculate your losses in case 1% of users leave immediately and never came back because of bad performance. If users aren’t happy, business will be unhappy, too.

To get more details about how web performance correlates with revenue, check out WPO Stats. It’s a list of case studies from real companies and their success after improving performance.

JAMstack helps improve web performance

Credit: Snipcart

With JAMstack, developers do as little rendering on the client as possible, instead using server infrastructure for most things. Not to mention, most JAMstack workflows are great at handling deployments, and helping with scalability, among other benefits. Content is stored statically on a static file hosts and provided to the users via CDN.

Read Mathieu Dionne’s “New to JAMstack? Everything You Need to Know to Get Started” for a great place to become more familiar with JAMstack.

I had two years of experience working with one of the popular CMSs for e-commerce and we had a lot of problems with deployments, performance, scalability. The team would spend days and fixing them. It’s not what customers want. These are the sorts of big issues JAMstack solves.

Looking at the CrUX data, JAMstack sites performance looks really solid. The following values are based on sites served by Netlify and GitHub. There is some discussion on the HTTPArchive forum where you can participate to make data more accurate.

Here are the results for TTFB:

TTFB mobile speed distribution comparison between all web, CMS and JAMstack sites (CrUX, July 2019)
Data from July 2019
[   {     "n": "7627",     "fastTTFB": "0.377",     "avgTTFB": "0.5032",     "slowTTFB": "0.1198"   } ]
BigQuery
#standardSQL SELECT   COUNT(DISTINCT origin) AS n,   ROUND(SUM(IF(ttfb.start < 200, ttfb.density, 0)) / SUM(ttfb.density), 4) AS fastTTFB,   ROUND(SUM(IF(ttfb.start >= 200 AND ttfb.start < 1000, ttfb.density, 0)) / SUM(ttfb.density), 4) AS avgTTFB,   ROUND(SUM(IF(ttfb.start >= 1000, ttfb.density, 0)) / SUM(ttfb.density), 4) AS slowTTFB FROM   `chrome-ux-report.all.201907`,   UNNEST(experimental.time_to_first_byte.histogram.bin) AS ttfb JOIN   (SELECT url, REGEXP_EXTRACT(LOWER(CONCAT(respOtherHeaders, resp_x_powered_by, resp_via, resp_server)),       '(netlify|x-github-request)')     AS platform   FROM `httparchive.summary_requests.2019_07_01_mobile`) ON   CONCAT(origin, '/') = url WHERE   platform IS NOT NULL ORDER BY   n DESC

Here’s how FCP shook out:

FCP mobile speed distribution comparison between all web, CMS and JAMstack sites (CrUX, July 2019)

Now let’s look at FID:

FID mobile speed distribution comparison between all web, CMS and JAMstack sites (CrUX, July 2019)
Data from July 2019
[     {       "n": "4136",       "fastFCP": "0.5552",       "avgFCP": "0.3126",       "slowFCP": "0.1323",       "fastFID": "0.9263",       "avgFID": "0.0497",       "slowFID": "0.024"     }   ]
BigQuery
#standardSQL   SELECT     COUNT(DISTINCT origin) AS n,     ROUND(SUM(IF(fcp.start < 1000, fcp.density, 0)) / SUM(fcp.density), 4) AS fastFCP,     ROUND(SUM(IF(fcp.start >= 1000 AND fcp.start < 2500, fcp.density, 0)) / SUM(fcp.density), 4) AS avgFCP,     ROUND(SUM(IF(fcp.start >= 2500, fcp.density, 0)) / SUM(fcp.density), 4) AS slowFCP,     ROUND(SUM(IF(fid.start < 50, fid.density, 0)) / SUM(fid.density), 4) AS fastFID,     ROUND(SUM(IF(fid.start >= 50 AND fid.start < 250, fid.density, 0)) / SUM(fid.density), 4) AS avgFID,     ROUND(SUM(IF(fid.start >= 250, fid.density, 0)) / SUM(fid.density), 4) AS slowFID   FROM     `chrome-ux-report.all.201907`,     UNNEST(first_contentful_paint.histogram.bin) AS fcp,     UNNEST(experimental.first_input_delay.histogram.bin) AS fid   JOIN     (SELECT url, REGEXP_EXTRACT(LOWER(CONCAT(respOtherHeaders, resp_x_powered_by, resp_via, resp_server)),         '(netlify|x-github-request)')       AS platform     FROM `httparchive.summary_requests.2019_07_01_mobile`)   ON     CONCAT(origin, '/') = url   WHERE     platform IS NOT NULL   ORDER BY     n DESC

The numbers show the performance of JAMstack sites is the best. The numbers are pretty much the same for mobile and desktop which is even more amazing!

Some highlights from engineering leaders

Let me show you a couple of examples from some prominent folks in the industry:

JAMstack sites are generally CDN-hosted and mitigate TTFB. Since the file hosting is handled by infrastructures like Amazon Web Services or similar, all sites performance can be improved in one fix.

One more real investigation says that it is better to deliver static HTML for better FCP.

Here’s a comparison for all results shown above together:

Mobile speed distribution comparison between all web, CMS and JAMstack sites (CrUX, July 2019)

JAMstack brings better performance to the web by statically serving pages with CDNs. This is important because a fast back-end that takes a long time to reach users will be slow, and likewise, a slow back-end that is quick to reach users will also be slow.

JAMstack hasn’t won the perf race yet, because the number of sites built with it not so huge as for example for CMS, but the intention to win it is really great.

Adding these metrics to a performance budget can be one way make sure you are building good performance into your workflow. Something like:

  • TTFB: 200ms
  • FCP: 1s
  • FID: 50ms

Spend it wisely 🙂


Editor’s note: Artem Denysov is from Stackbit, which is a service that helps tremendously with spinning up JAMstack sites and more upcoming tooling to smooth out some of the workflow edges with JAMstack sites and content. Artem told me he’d like to thank Rick Viscomi, Rob Austin, and Aleksey Kulikov for their help in reviewing the article.

The post A Look at JAMstack’s Speed, By the Numbers appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Awards That Look Beyond the Flashy

Dan Mall is judging the Communication Arts Interactive 2020 awards. These types of things are usually a celebration of flashy, short-lived, one-off designs. Those things are awesome, but Dan has more in mind:

I’d love to award work that demonstrates creative use of the highest level of color contrast ratios and works well on assistive devices. I’d love to award work that’s still useful when JavaScript fails. I’d love to award work that shows smart thinking and strategy in addition to flawless execution and art direction. I’d love to award work that serves business and user needs.

Direct Link to ArticlePermalink

The post Awards That Look Beyond the Flashy appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

A Quick Look at the First Public Working Draft for Color Adjust Module 1

We’ve been talking a lot about Dark Mode around here ever since Apple released it as a system setting in MacOS 10.14 and subsequently as part of Safari. It’s interesting because of both what it opens up as as far as design opportunities as well as tailoring user experience based on actual user preferences.

This week, we got an Editor’s Draft for the Color Adjust Module Level 1 specification and the First Public Working Draft of it. All of this is a work-in-progress, but the progression of it has been interesting to track. The spec introduces three new CSS properties that help inform how much control the user agent should have when determining the visual appearance of a rendered page based on user preferences.

color-scheme is the first property defined in the spec and perhaps the centerpiece of it. It accepts light and dark values which — as you may have guessed — correspond to Light Mode and Dark Mode preferences for operating systems that support them. And, for what it’s worth, we could be dealing with labels other than “Light” and “Dark” (e.g. “Day” and “Night”) but what we’re dealing with boils down to a light color scheme versus a dark one.

Source: developer.apple.com

This single property carries some important implications. For one, the idea is that it allows us to set styles based on a user’s system preferences which gives us fine-grained control over that experience.

Another possible implication is that declaring the property at all enables the user agent to take some responsibility for determining an element’s colors, where declaring light or dark informs the user agent that an element is “aware” of color schemes and should be styled according to a preference setting matching the value. On the other hand, we can give the browser full control to determine what color scheme to use based on the user’s system preferences by using the auto value. That tells the browser that an element is “unaware” of color schemes and that the browser can determine how to proceed using the user preferences and a systems’s default styling as a guide.

It’s worth noting at this point that we may also have a prefers-color-scheme media feature (currently in the Editor’s Draft for the Media Queries Level 5 specification) that also serves to let us detect a user’s preference and help gives us greater control of the user experience based on system preferences. Robin has a nice overview of it. The Color Adjust Module Level 1 Working Draft also makes mention of possibly using a color scheme value in a <meta> element to indicate color scheme support.

There’s more to the property, of course, including an only keyword, chaining values to indicate an order of preference, and even an open-ended custom ident keyword. So definitely dig in there because there’s a lot to take in.

Pretty interesting, right? Hopefully you’re starting to see how this draft could open up new possibilities and even impacts how we make design decisions. And that’s only the start because there are two more properties!

  • forced-color-adjust: This is used when we want to support color schemes but override the user agent’s default stylesheet with our own CSS. This includes a note about possibly merging this into color-adjust.
  • color-adjust: Unlike forcing CSS overrides onto the user agent, this property provides a hint to browsers that they can change color values based on the both the user’s preferences and other factors, such as screen quality, bandwidth, or whatever is “deem[ed] necessary and prudent for the output device.” Eric Bailey wrote up the possibilities this property could open up as far as use cases, enhanced accessibility, and general implementations.

The current draft is sure to expand but, hey, this is where we get to be aware of the awesome work that W3C authors are doing, gain context for the challenges they face, and even contribute to the work. (See Rachel Andrew’s advice on making contributions.)

The post A Quick Look at the First Public Working Draft for Color Adjust Module 1 appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]

A historical look at lowercase defaultstatus

Browsers, thank heavens, take backward compatibility seriously.

Ancient websites generally work just fine on modern browsers. There is a way higher chance that a website is broken because of problems with hosting, missing or altered assets, or server changes than there is with changes in how browsers deal with HTML, CSS, JavaScript, another other native web technologies.

In recent memory, #SmooshGate was all about a new JavaScript feature that conflicted with a once-popular JavaScript library. Short story, JavaScript has a proposal for Array.prototype.flatten, but in a twist of fate, it would have broken MooTools Elements.prototype.flatten if it shipped, so it had to be re-named for the health of the web.

That was the web dealing with a third-party, but sometimes the web has to deal with itself. Old APIs and names-of-things that need to continue to work even though they may feel like they are old and irrelevant. That work is, surprise surprise, done by caring humans.

Mike Taylor is one such human! The post I’m linking to here is just one example of this kind of bizarre history that needs to be tended to.

If Chrome were to remove defaultstatus the code using it as intended wouldn’t break—a new global would be set, but that’s not a huge deal. I guess the big risk is breaking UA sniffing and ended up in an unanticipated code-path, or worse, opting users into some kind of “your undetected browser isn’t supported, download Netscape 2” scenario.

If you’re into this kind of long term web API maintenance stuff, that’s the whole vibe of Mike’s blog, and something tells me it will stick around for a hot while.

Direct Link to ArticlePermalink

The post A historical look at lowercase defaultstatus appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Look Ma, No Media Queries! Responsive Layouts Using CSS Grid

Not only has CSS Grid reshaped the way we think and build layouts for the web, but it has also contributed to writing more resilient code, replacing “hacky” techniques we’ve used before, and in some cases, killing the need to rely on code for specific resolutions and viewports. What’s so cool about this era in web development is that we’re capable of doing more and more with fewer lines of code.

In this article, we’ll start dipping our toes into the power of CSS Grid by building a couple of common responsive navigation layouts. It’s easier than what you may think, and since CSS Grid was built with responsiveness in mind, it’ll take less code than writing media queries all over the place. Let’s do this!

Layout #1: Hero content and list of articles

See the Pen
Hero Content and List of Articles
by Juan Martín García (@imjuangarcia)
on CodePen.

We’ll kick off this set of examples by creating a common website layout: A full-width hero section, with a grid of cards below.

Both elements will respond to window resizing and adapt accordingly. Though this might seem like a lot of code at first glance, the responsive behavior is done with only six lines of CSS Grid code, and without writing a single @media rule. Let’s break down the code to see what’s going on:

The hero section

Let’s take a look at the code for the .hero element:

<section class="hero">   <h1>You thirsty?</h1>   <article>     <p>Explore local breweries with just one click and stirred by starlight across the centuries light years great turbulent clouds circumnavigated paroxysm of global death.</p>     <a href="#breweries">Browse Breweries</a>   </article> </section>
.hero {   /* Photo by mnm.all on Unsplash */   background: url('https://images.unsplash.com/photo-1518176258769-f227c798150e') center;   background-size: cover;   padding: 4rem 2rem;    /* Grid styles */   display: grid;   align-items: center;   grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

We have a bunch of background styles to enable the beer background, a bit of padding to separate the content from the edge of the screen, and then three lines of grid styles:

  1. The first line (display: grid;) is changing the behavior of the .hero element to be a grid container. That means the elements inside .hero are now grid items.
  2. The second line (align-items: center;) is going to vertically center the columns on our grid. But these two lines don’t do anything on their own until we set the columns of our grid.
  3. And that’s where the third line comes in. A lot of stuff is going on in that single property, so let’s go one step at a time.

The repeat() function

Generally speaking, what we usually do to define our columns and rows on a CSS Grid is to add the value for each track after defining the property, like this:

.element {   /* This will result on four columns, each one of 1fr */   grid-template-columns: 1fr 1fr 1fr 1fr;   /* This will result on two rows, each one of 300px */   grid-template-rows: 300px 300px; }

Now, that’s quite dull. We can use the repeat() function to make that less verbose and easier to follow. The function takes two parameters:

  1. The number of times to repeat the value.
  2. The value itself.

After refactoring our code to use repeat(), we should expect the same results from these lines of code:

.element {   /* this is the same as grid-template-columns: 1fr 1fr 1fr 1fr; */   grid-template-columns: repeat(4, 1fr);   /* this is the same as grid-template-rows: 300px 300px; */   grid-template-rows: repeat(2, 300px); }

Much cleaner, yeah?

The minmax() function

Now, the above examples are explicitly defining sizes for the tracks (1fr and 300px). That might work for some scenarios, but for our beer example here, we need to be able to automatically calculate the size of the track, based on the width of the viewport, and automatically adjust the number of columns shown. To be able to do that, we’ll define a range of values using the minmax() function. What will we be defining? You’ve probably guessed by now: The *minimum* and *maximum* values we want these columns to be able to resize to.

In the hero for our beer example above, we set our minmax() property to be 240px at its minimum size, and 1fr at its maximum size. fr units, if you’ve never heard of them, stand for fractional units. Nobody can explain them better than Jen Simmons on this video and Robin Rendle in this post.

Using the Firefox Grid Inspector to check the change on the track’s size when resizing

That results in our tracks being 1fr when there’s plenty of space on our viewport (aka desktop resolutions), and 240px when there’s not enough space for both columns (like on mobile devices). That’s why they nicely grow when we make our browser wider, since they’re taking the remaining space and equally dividing it across the existing columns. Now, moving to the last piece of the puzzle!

The auto-fit keyword

The auto-fit keyword allows us to wrap our columns into rows when there’s not enough space in our viewport to fit the 240px minimum value without overflowing the content. Sara Soueidan wrote an excellent article about auto-sizing columns using the auto-fill and auto-fit keywords, in case you want to dive a little deeper into what’s going on under the hood. Now, with that last bit of code in place, we should be able to achieve this result:

The column is automatically wrapping when there’s not enough space in the viewport

The article list

Now that we’ve thoroughly reviewed the behavior of the elements inside our hero element, it’s likely that the first two lines of CSS code for the breweries list below it might already seem familiar to you:

.breweries > ul {   display: grid;   grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));   grid-gap: 1rem; }

That’s right! We’re using the exact same approach: On the first line we define our grid, on the second one we size our tracks using the same magic one-liner, and on the third line we set a gap for these columns. Nothing new under the sun, and what’s really neat about this, is that our code is resilient enough to adjust the number of tracks and their sizes, according to the number of items we have inside our unordered list:

The grid responds to the change in the number of tracks, and adapts the layout

That’s all, folks! A fully responsive website layout, using just six lines of CSS code. Not bad, huh? Make sure you check the source code and play around with this example on CodePen.

Layout #2: Full-width image gallery

See the Pen
Full Width Image Gallery
by Juan Martín García (@imjuangarcia)
on CodePen.

On this next example, we’ll embrace the power of our newly learned combination of repeat(), auto-fit and minmax() to create this responsive image gallery. We’ll also be sizing our tracks using grid-column and grid-row, and learning about the handy property:value combination of grid-auto-flow: dense; that allows us to change the default behavior of the elements that can’t fit on our explicit tracks: Instead of wrapping themselves in new rows or columns, we’ll make them fit into the unused spots on our grid. Let’s get into the coding!

The grid setup

The grid is created using our familiar display: grid; property, where columns are defined using repeat(), auto-fit and minmax(). We also added a bunch rows with a repeat() function and defined a gap to our images, using grid-gap. But the new player here is the grid-auto-flow: dense;. We’ll get to it in a second.

.gallery > .gallery__list {   display: grid;   grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));   grid-template-rows: repeat(6, 200px);   grid-gap: 1rem;   grid-auto-flow: dense; }

We also created a repetition pattern using the nth-child() pseudo-selector to set different sizes for our tracks using grid-column and grid-row. Notice here that we’re using the span keyword to allow the selected item to occupy more than one column or row.

/* This will create 2x images every 4 elements */ .gallery > .gallery__list > li:nth-child(4n) {   grid-column: span 2; /* Spans two columns */   grid-row: span 2; /* Spans two rows */ }  /* This will create 3x images every 8 elements */ .gallery > .gallery__list > li:nth-child(8n) {   grid-column: span 3;   grid-row: span 3; }

And finally, we’ll make sure our images cover the entire area of its container, regardless if it’s 1x, 2x or 3x, using object-fit: cover;. If you have never heard of object-fit, it works fairly similar to how background-image does, but with HTML <img> tags:

.gallery > .gallery__list > li > figure > img {   width: 100%;   height: 100%;   object-fit: cover; }

Now, the real deal here is grid-auto-flow: dense;. Check what happens when we take that out from our code:

Removing grid-auto-flow: dense; leads to inconsistent placement of the elements on the grid

See those holes on our beautifully crafted grid? That’s because some of the elements on it are taking 2x or 3x spots, and when there isn’t enough space on our tracks to fit them, they’ll wrap into a new row, since that’s the default behavior. By changing it from row to dense, we’re telling the grid to fill any gaps we might have with elements that could fit them, regardless of their source order on the DOM.

That’s why this technique might come especially handy for things like image galleries, but might not be suitable for other use cases where you might need to preserve the order of the markup. Feel free to play around with the CodePen demo to check the differences between where items are placed.

Layout #3: Trello-style card layout

See the Pen
Trello-Style Card Layout
by Juan Martín García (@imjuangarcia)
on CodePen.

Now, on to the last demo, where we’ll take advantage of the ability to nest grids to recreate this Trello Board. We’ll be creating a grid to hold our four different columns, and inside of those, we’ll create a child grid for our cards. Even though this example won’t explore new properties or revolutionary methods, it’ll help us to get a grasp on how easy it is to build complex layouts with a few lines of CSS code. This demo has a lot of extra code to achieve the styling of the Trello layout, so we’ll focus solely on the grid styles.

The columns

To create the four columns, we’ll use display: grid; on the container and use our magical one-liner for our grid-template-columns. We’ll also be defining a gap between them, and use align-items: flex-start; to ensure that our columns don’t stretch to the bottom of the screen.

.column__list {   display: grid;   grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));   grid-gap: .5rem;   align-items: flex-start; }

Now, the original Trello is not responsive by default: If you resize your browser on a Trello Board, you’ll notice that you’ll end up having a horizontal scroll on your columns, rather than wrapping them on a new row. We’re not following that behavior here since we want to build responsive layouts, but in case you’re curious, and want to emulate Trello’s functionality, you can achieve that by adding two more lines of CSS code:

.column__list {   display: grid;   grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));   grid-gap: .5rem;   align-items: flex-start;   /* Uncomment these lines if you want to have the standard Trello behavior instead of the column wrapping */   grid-auto-flow: column;   grid-auto-columns: minmax(260px, 1fr); }

We learned about grid-auto-flow in our previous demo and discovered that it let us control how the auto-placement algorithm work, and how implicit elements should be added in the flow of the grid. The default behavior is row, meaning that any extra element that won’t fit on our grid will wrap into a new line. We changed that to be dense on our previous demo, and we’ll change it to be column on this one: That way, any new column added here will end up in an implicit column, and have a horizontal scroll. We’ll also define a width for those auto-generated columns with the grid-auto-columns property.

Modifying the grid-auto-flow property will make this demo behave like the real-world Trello

The cards

For the cards grid, we’ll use a similar approach. We’ll display: grid; on the container. We won’t define any columns here, since we don’t want to have any, and we’ll put grid-template-rows: auto; to use to avoid all cards having the same height — we want some of them to be bigger and some of them smaller, based on the type of content being added to them.

.card__list {   display: grid;   grid-template-rows: auto;   grid-gap: .5rem;   margin: .5rem 0; }

And, again, that’s all folks! Two more lines to set a gap and a margin to the cards, and we’re done! Everything else in the Pen is standard CSS to achieve the Trello look and feel.

So then… are media queries dead?

Back in the day, when we were building layouts using display: inline-block or floats, media queries made a lot of sense in order to change the size of our elements as the viewport got smaller. But now, with the incredibly powerful layouts that we’re able to create with a couple of CSS lines, you might feel tempted to think that media queries are doomed. I strongly disagree with that: I believe that we should change the way we think about them, and therefore use them differently.

As Rachel Andrew stated about a year ago, we should use media queries to fix our layout when it breaks, rather than targeting devices: There are so many out there! With the advent of Media Queries Level 4 and 5, we’re not only able to detect screen sizes now, but pointer types as well. As a result, we can dig into a user’s system preferences and adapt our code for those who prefer reduced motion or whether we should use inverted colors. That means media queries are not dead; on the flipside, I’d say it’s an exciting time for using media queries, but we need to learn to use them right. In the meantime, building robust layouts using modern techniques such as Flexbox or CSS Grid, will save you a bunch of time, code, and headaches.

The post Look Ma, No Media Queries! Responsive Layouts Using CSS Grid appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]