⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies
Showing posts with label Layouts. Show all posts
Showing posts with label Layouts. Show all posts

Wednesday, June 8, 2022

A Guide To Hover And Pointer Media Queries

 The Internet is filled with a lot of interactivity, and more often than not the way we choose to show we can interact with an element is by using the hover pseudo-class. After all, changing an element a bit when you put the cursor on it ends up being a good indicator of whether the element is interactive or not.

The main issue with this approach is when we remember the vast amount of devices we can use to browse the web: you could be reading this article right now on a cellphone, a tablet, or even on a Smart TV!

This usually isn’t a problem, because the loss of interaction isn’t a big deal. But in some cases, not taking into account the variety of devices people use can create some usability and accessibility issues on your site.

Thanks to CSS, we can detect those nuances by using four media queries (or, to be more specific, media features): hover, pointer, any-hover, and any-pointer. In this article, I will talk in detail about each one of them and show some examples of how to use those media queries to adapt your sites to the different devices available today.

Media Query Hover: Detecting a Pointer

The hover media query allows us to detect the user’s primary input mechanism can hover over elements. It can have two values:

  1. none detects when the primary input mechanism can’t hover or can’t conveniently hover, like most cellphones and tablets.
  2. hover detects when the primary input mechanism can hover over elements (for example, desktop computers, laptops, and smartphones with a stylus).

Keep in mind that although a cell phone doesn’t have an input mechanism that can hover over elements, it can emulate this function with a long tap which can be inconvenient and create some usability issues.

With that said, let’s make a small example showing how to use this media query in action:

Let’s suppose, we have this button, and we want to change its color and its size when we hover over it, but we want this to happen only in devices that support hover. In that case, the only thing we’d have to do is to put the .button:hover rule inside the media query hover, as you can see here.

<style>
  .button {
    padding: 0.5em 1em;
    font-size: 1.125rem;
    border-radius: 0.6em;
    background-color: coral;
    font-weight: bold;
    border: 1px solid transparent;
    transition: background-color 200ms ease-in-out;
  }

  @media (hover: hover) {
    .button:hover {
      background-color: hotpink;
    }
  }
</style>

<button class="button">Hover over me</button>

This is a very basic example that doesn’t affect usability in non-hover devices. Honestly, it’d be one I’d even leave in that kind of device! Now let’s check a more complex one, where usability could be compromised at some level. Let’s take a look at this card.

And these are the animation requirements we need to add:

  • Initially, only the title (without the underline) will be visible.
  • When the user hovers the card, it’ll move up revealing the space of the rest of the content.
  • The card will also increase its size slightly, and the image in the background will zoom in as well.
  • After that, the underline will appear at the left and will expand until the end of the title.
  • When the underline animation ends, the text and the button will fade in.

With those requirements in mind, let’s add the markup and the stylesheet of this card without animations.

<article class="card">
  <img
    class="card__background"
    src="https://i.imgur.com/QYWAcXk.jpeg"
    alt="Photo of Cartagena's cathedral at the background and some colonial style houses"
    width="1920"
    height="2193"
  />
  <div class="card__content | flow">
    <div class="card__content--container | flow">
      <h2 class="card__title">Colombia</h2>
      <p class="card__description">
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Rerum in
        labore laudantium deserunt fugiat numquam.
      </p>
    </div>
    <button class="card__button">Read more</button>
  </div>
</article>
@import url("https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Montserrat:wght@700&display=swap");

:root {
  /* Colors */
  --brand-color: hsl(46, 100%, 50%);
  --black: hsl(0, 0%, 0%);
  --white: hsl(0, 0%, 100%);
  /* Fonts */
  --font-title: "Montserrat", sans-serif;
  --font-text: "Lato", sans-serif;
}

/* RESET */

/* Box sizing rules */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Remove default margin */
body,
h2,
p {
  margin: 0;
}

/* GLOBAL STYLES */
body {
  display: grid;
  place-items: center;
  height: 100vh;
}

h2 {
  font-size: 2.25rem;
  font-family: var(--font-title);
  color: var(--white);
  line-height: 1.1;
}

p {
  font-family: var(--font-text);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--white);
}

.flow > * + * {
  margin-top: var(--flow-space, 1em);
}

/* CARD COMPONENT */

.card {
  display: grid;
  place-items: center;
  width: 80vw;
  max-width: 21.875rem;
  height: 31.25rem;
  overflow: hidden;
  border-radius: 0.625rem;
  box-shadow: 0.25rem 0.25rem 0.5rem rgba(0, 0, 0, 0.25);
}

.card > * {
  grid-column: 1 / 2;
  grid-row: 1 / 2;
}

.card__background {
  object-fit: cover;
  max-width: 100%;
  height: 100%;
}

.card__content {
  --flow-space: 0.9375rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-self: flex-end;
  height: 55%;
  padding: 12% 1.25rem 1.875rem;
  background: linear-gradient(
    180deg,
    hsla(0, 0%, 0%, 0) 0%,
    hsla(0, 0%, 0%, 0.3) 10%,
    hsl(0, 0%, 0%) 100%
  );
}

.card__content--container {
  --flow-space: 1.25rem;
}

.card__title {
  position: relative;
  width: fit-content;
  width: -moz-fit-content; /* Prefijo necesario para Firefox  */
}

.card__title::after {
  content: "";
  position: absolute;
  height: 0.3125rem;
  width: calc(100% + 1.25rem);
  bottom: calc((1.25rem - 0.5rem) * -1);
  left: -1.25rem;
  background-color: var(--brand-color);
}

.card__button {
  padding: 0.75em 1.6em;
  width: fit-content;
  width: -moz-fit-content; /* Prefijo necesario para Firefox  */
  font-variant: small-caps;
  font-weight: bold;
  border-radius: 0.45em;
  border: none;
  background-color: var(--brand-color);
  font-family: var(--font-title);
  font-size: 1.125rem;
  color: var(--black);
}

.card__button:focus {
  outline: 2px solid black;
  outline-offset: -5px;
}

Now, if we add rules for the animation, as we normally do, that’d the card’s initial state on all devices:

It doesn’t seem to be a problem at all in devices with a hover, but with a non-hover device the user would have to tap to see the card’s information, and that could be awkward and non-intuitive for that kind of device. Our best way to solve this is putting all the animation’s related rules inside our media query hover as follows:

@media (hover: hover) {
  .card__content {
    transform: translateY(62%);
    transition: transform 500ms ease-out;
    transition-delay: 500ms;
  }

  .card__title::after {
    opacity: 0;
    transform: scaleX(0);
    transition: opacity 1000ms ease-in, transform 500ms ease-out;
    transition-delay: 500ms;
    transform-origin: right;
  }

  .card__background {
    transition: transform 500ms ease-in;
  }

  .card__content--container > :not(.card__title),
  .card__button {
    opacity: 0;
    transition: transform 500ms ease-out, opacity 500ms ease-out;
  }

  .card:hover,
  .card:focus-within {
    transform: scale(1.05);
    transition: transform 500ms ease-in;
  }

  .card:hover .card__content,
  .card:focus-within .card__content {
    transform: translateY(0);
    transition: transform 500ms ease-in;
  }

  .card:focus-within .card__content {
    transition-duration: 0ms;
  }

  .card:hover .card__background,
  .card:focus-within .card__background {
    transform: scale(1.3);
  }

  .card:hover .card__content--container > :not(.card__title),
  .card:hover .card__button,
  .card:focus-within .card__content--container > :not(.card__title),
  .card:focus-within .card__button {
    opacity: 1;
    transition: opacity 500ms ease-in;
    transition-delay: 1000ms;
  }

  .card:hover .card__title::after,
  .card:focus-within .card__title::after {
    opacity: 1;
    transform: scaleX(1);
    transform-origin: left;
    transition: opacity 500ms ease-in, transform 500ms ease-in;
    transition-delay: 500ms;
  }
}

This is how we can solve usability issues by using media queries, but that’s not all we can do with media queries. Detecting if a dispositive has or not a hover mechanism can help us to cover some scenarios, but sometimes you need to check how accurate a pointer is, which is a nuance the hover media query can’t detect. This is where our next media query enters and helps us solve quite a common accessibility issue.


Media Query Pointer: Detecting a Pointer’s Accuracy

Sometimes detecting if a dispositive has a pointer is not enough. In some cases, it’s important to detect how accurate the pointer is. This is where our next media query enters the scene. The pointer media query helps us to detect how accurate the primary pointer device is. This media query has three values:

  1. none detects when the main input mechanism doesn’t have a pointer device (for example cellphones);
  2. coarse detects when the main input mechanism has a pointer device with limited accuracy (like the remote control of a Smart TV or some video game consoles);
  3. fine detects when the primary input mechanism has an accurate pointer device (like a mouse, touchpads, or stylus).

Now, this media query can help us to solve some common accessibility issues that happen if we don’t take into consideration that some elements on our site can be hard to interact with if you don’t have an accurate pointer. Let’s check that with an example.

Let’s create and style a quick form to check what could happen for limited-accuracy pointer device users:

<form action="post">
  <fieldset>
    <legend>Which programming languages do you want to learn?</legend>
    <div class="form-grid">
      <label for="c"> <input type="checkbox" id="c" /> C </label>
      <label for="c+"> <input type="checkbox" id="c+" /> C+ </label>
      <label for="c++"> <input type="checkbox" id="c++" /> C++ </label>
      <label for="c-sharp"> <input type="checkbox" id="c-sharp" /> C# </label>
      <label for="kotlin"> <input type="checkbox" id="kotlin" /> Kotlin </label>
      <label for="java"> <input type="checkbox" id="java" /> Java </label>
      <label for="javascript">
        <input type="checkbox" id="javascript" /> JavaScript
      </label>
      <label for="go"> <input type="checkbox" id="go" /> Go </label>
      <label for="objective-c">
        <input type="checkbox" id="objective-c" /> Objective-C
      </label>
      <label for="php"> <input type="checkbox" id="php" /> PHP </label>
      <label for="python"> <input type="checkbox" id="python" /> Python </label>
      <label for="ruby"> <input type="checkbox" id="ruby" /> Ruby </label>
      <label for="rust"> <input type="checkbox" id="rust" /> Rust </label>
      <label for="scala"> <input type="checkbox" id="scala" /> Scala </label>
      <label for="swift"> <input type="checkbox" id="swift" /> Swift </label>
      <label for="other"> <input type="checkbox" id="other" /> Another </label>
    </div>
    <button type="button">Submit</button>
  </fieldset>
</form>
@import url("https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;700&display=swap");

body {
  font-family: "Fira Sans", sans-serif;
}

fieldset {
  padding: 0.6em 1em 2em;
  border-radius: 1em;
  border-color: #722f37;
  box-shadow: 0.25rem 0.25rem 0.2rem rgba(0, 0, 0, 0.25);
}

legend {
  font-size: 1.3rem;
  text-align: center;
  font-weight: bold;
}

form {
  max-width: 53.125rem;
  margin: 0 auto;
}

input[type="checkbox"] {
  margin-inline-end: 0.5em;
  accent-color: #722f37;
}

input[type="checkbox"]:focus {
  outline: 2px solid #722f37;
  outline-offset: 0.2em;
}

label {
  display: flex;
  align-items: center;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.3em;
  margin-bottom: 1.2em;
  align-items: center;
}

button[type="button"] {
  display: block;
  margin: 0 auto;
  padding: 0.3em 1em;
  color: white;
  font-weight: bold;
  background-color: #722f37;
  font-size: 1.25rem;
  border: none;
  border-radius: 0.5em;
}

button[type="button"]:focus {
  outline: 2px solid #722f37;
  outline-offset: 0.4em;
}

This form is totally fine for desktop computers or laptops, even for a stylus device, but if you need to use your fingers, you’ll find choosing an option can be bothersome — you could select an option you don’t want because of that.

This is something that happens quite often on some sites, and it could be solved if we used the pointer media query to make some minor changes for limited accuracy devices. Let’s look at some changes I made to this form to mitigate this issue:

@media screen and (pointer: coarse) {
  .form-grid {
    gap: 0.5em;
  }

  label {
    font-size: 1.05em;
  }

  input[type="checkbox"] {
    width: 1.625rem;
    height: 1.625rem;
  }

  button[type="button"] {
    min-height: 3rem;
  }
}

To summarize the changes:

  • I increased the gap between options (from 4.8px to 8px), so there is less room for choosing the wrong choice. 8px is the suggested space area between tappable elements.
  • Input elements’ size are bigger (from 16px to 26px), so they can be selected easier. I also increased the label size to match the input’s size increase without looking mismatched.
  • Submit button’s height is now 48px, which is the average size of a tap area, just to make it easier to select the button. The button was probably big enough before this change, but I decided to increase it as an extra layer of prevention.

As you can see, with some simple changes, our site can be more accessible for users that depend on a limited accuracy pointer to navigate our site. This is pretty useful, but the real magic of the mentioned media queries happens when we start combining them to check more specific scenarios.

Combining Media Queries

Media queries’ hover and pointer are pretty interesting tools, but when you start considering certain scenarios and adding more complex interactions to your site, you’ll start noticing that using only one or another can lead to incorrect conclusions and by extension harm your site’s usability. Just to make a point, let’s come back for a moment to the card in the first section of this article (which, by the way, is a modification of this animated card made by Kevin Powell). I showed it to a friend to test if it indeed worked, and the result was… It’s still required to be tapped to show its information.

Why? Well, my friend’s phone is a Samsung Galaxy Note 9. This cell phone has a stylus as its main navigation mechanism which acts as an accurate pointer and a hover mechanism. I put the animation rules inside the media query hover with the value hover, so it’ll still show in stylus-based devices.

Do we want that for our users? That’s up to you, I honestly don’t think so, even if a cellphone has a stylus, I have seen most people prefer to use their thumbs, so I’d prefer to adapt a site keeping that into consideration.

How can we solve that? Let’s remember we can create complex media queries combining two or more media queries, and in this case, we can combine media queries hover and pointer to check specific devices.

After everything I’ve learned by talking with my friend, I decided my card should only have this animation on desktop computers and laptops. Let’s think a bit about their properties: computers are devices that have a hover input mechanism, so we should use @media (hover: hover), and said input mechanism is accurate, so we should use @media (pointer: fine), so if we put all animation rules in the media query @media (hover: hover) and (pointer: fine), our animations will display only on computers.

With that said, what kind of devices can we detect with this technique? You can check it out in this table:

Media query hover’s valueMedia query pointer’s valueDevice
nonecoarseSmartphones, touchscreens
nonefineStylus-based screens
hovercoarseSmart TVs, video game consoles
hoverfineDesktop computers, laptops, touchpads

You might think that will cover all use cases of devices and you’d be mostly correct, but some specific scenarios are not possible to detect with what we have learned here but are worth taking a look at.

Media Queries Any-hover And Any-pointer 

The first one is a cellphone that has a keyboard and a mouse connected via Bluetooth, while the primary pointer device of this cell phone is a touchscreen, the addition of a mouse adds an accurate pointer to it.

The second one is a mini wireless keyboard that can be used for devices like Smart TVs, X-Box, or even mobile vehicle TVs. Smart TVs’ are not very accurate, but this control has a touchpad that adds an accurate pointer to this device.

What do those two elements have in common? Both of them won’t be detected by the previously mentioned media queries, because both of them detect the primary input mechanism. How do we detect those cases? This is where once again CSS brings us two more tools to help us: media queries any-hover and any-pointer.

Those media queries have the same values as the previous one: hover and none for any-hover, and none, coarse and fine for any-pointer. The difference is that instead of detecting the main input mechanism, it’ll detect if at least one of the device’s input mechanisms meets the condition.

For example, with (any-pointer: coarse) we would be able to detect touchscreen devices, but it’ll also detect it if said device would have a mouse as well. This is important because it’ll let us detect more scenarios, such as the ones described at the start of this section.

We can even combine all 4 media queries we have seen to detect some other scenarios. For example, we could use @media (pointer: fine) and (any-pointer: coarse) to check if the main input mechanism of the device has an accurate pointer and at the same time has a limited accuracy one. This would cover devices like stylus-based smartphones or desktop computers/laptops with a touchscreen. This could be useful for adapting interactive elements like inputs and buttons and adapting them to prioritize touch interaction, in the case the user does not want to use the accurate pointer and prefers to use the touchscreen (such as the form we made in the media query pointer section).

Even if combining those media queries can be useful, you need to be careful with something in particular: the combination of browsers and pointer devices can create some unexpected bugs. As you can see in this data compiled by Patrick H. Lauke some interactions between hardware and browsers have wrong results, which can be misleading in some specific cases. This data is a bit old, but it shows us a problem that needs to be pointed out with this approach. What can we do in that case to avoid as many problems as possible with those media queries?

In my opinion, the answer to that is in keeping in mind CSS’ philosophy itself. As Myriam Suzanne mentions in her video “Why is CSS so weird?” in two moments:

“In reality, we're designing for an unknown infinite canvas, and we don't have all the variables. Rather than designing with this control over exactly what we should ship, we're designing for change, and for movement, and for adaptation.”

“So as a designer I'm no longer controlling the output, I'm suggesting and that's why CSS is a declarative language. That means that rather than describing, as we might in JavaScript, the steps to take to recreate a specific outcome, I instead describe the intent of my outcome. What am I going for? What am I pushing towards? And I'm trying to give the browser as much meaningful information as possible, subtext and implications so that the browser can make smart decisions about what to do with those styles.”

Why do we need to take this into consideration? Because how a user decides to navigate our site is one of the many things that are not under our control. Sure, the user may have a laptop with a touchscreen and normally uses the touchpad/mouse, but maybe at a moment the user wants to use the touchscreen, and if your site is not adapted for that situation, it could create a usability problem.

How can we keep as many possible scenarios into consideration then? I think the answer is that rather than applying a set of specific rules for specific scenarios, we could use what we have learned to give some directives to our site to make our site take all into consideration based on a design philosophy that includes as many users as possible.

In my opinion, a good set of practices, we should take into consideration to adapt our sites to the multiple input mechanisms we can use to browse the web, should include:

  • Prioritize touch experience.
    Mobile browsing is the new default, and we should keep that in mind when we design our sites. Actions like making sure interactive elements like inputs or buttons have enough size to be used in those devices (which can be checked with the media query any-pointer: coarse) can help a lot in this task.
  • Don’t rely only on pointer and hover.
    The main input mechanism is not necessarily the pointer your users will rely on. However, you can use both of them to adapt your interface to specific devices, as we have seen in the previous section. That’s a good approach but use it sparingly because it can create some incorrect assumptions.
  • Don’t forget keyboard users.
    Sure, this article is about pointers, but let’s not forget, they’re not the only way to use a site. If you are going to make an element on your site react in a complex way with a hover, you should make sure it reacts with keyboard navigation as well! Take a look at the card of the first section, it reacts to hover, but the animation is triggered by keyboard navigation using the pseudo-class :focus-within, so keyboard users’ experience doesn’t seem affected. You can also tabindex=0 attribute on an element that is not naturally navigable with a keyboard if needed.
  • Test your site with different hardware and browsers.
    Remember, sometimes a combination of those two elements can create unexpected behaviors, so the more you test with different devices and browsers the more certainty you’ll have that your site usability works.

Use of JavaScript at Pointer Detection

It’s also worthy to note, there are some solutions with JavaScript to the same problem, as the one explained in this article made by Mezo Itsvan. It was a plausible solution where those media queries weren’t as widely supported as now (you can see browser support for those media queries in the next links: hover, pointer, any-hover, and any-pointer)! However, in this more recent web development environment, those hacky solutions are not necessary and can generate some problems:

  • Some users prefer to disable JavaScript for some reasons (like not having to deal with ads on a website), so using JavaScript to solve this problem is not always reliable.
  • It can detect an existing touch device now, but what about some months later when there is a new device in the market that JavaScript can’t detect? It’d create some usability and accessibility issues in those devices which can be inconvenient.
  • As Mezo himself mentions in his article, “When employing a component-based JS framework with encapsulated styles, this solution just throws a huge wrench in it. Any time hover effects are used, that component’s styles must reference this global class.” — which can be a bit inconvenient to maintain.

Even if it’s better to let this trouble to a more resilient layer of web development as CSS is, that doesn’t mean JavaScript doesn’t have a space in this matter! Something JavaScript can help us with is letting us create a choice where the user can select if our interface will be used for mouse or touch devices (in a similar vein as a dark/light mode toggle button), so you can add that extra layer of customization to your site.

Other Resources

The hover, pointer, any-hover, and any-pointer media queries have been available for a while, so many resources still apply to this day. And I didn’t cover all possible topics about pointer detection, such as using JavaScript for some cases or even the possibility to give users a choice to switch between mouse or touch layouts. You can find more information about it in these resources:

Wrapping Up

Browsing a site is a process that can be made in multiple ways and devices. Keeping this in mind is very important because some of those cases require our site to adapt to their specific needs. CSS has media queries that can help us with this task — to give our users the best experience possible. Do you know any use of those media queries? Let us know in the comments, so we all can learn about how to use those tools to give a better user experience!

Monday, November 18, 2013

Killer Responsive Layouts With CSS Regions

As Web designers, we are largely constrained by the layout features available to us. Content placed inside a container will often naturally extend the container vertically, wrapping the content. If a design requires elements to remain a certain height, then our options are limited. In these cases, we can only add a scroll bar or hide the overflow. The CSS Regions specification provides a new option.

Support

Regions are a new part of the CSS specification, so not all browsers have implemented them, and in some cases you might have to enable a flag to use them. They have recently gained support in iOS7 and Safari 7, as well as Safari 6.1+. Adobe maintains a list of supported browsers and instructions on enabling regions and other features. However, support for regions is constantly growing. For a robust list of which browsers have implemented regions and the various features available, see Adobe’s “CSS Regions Support” page.

Regions 101

CSS regions enable us to disperse content across multiple containing elements. They provide a flow, which consists of content that may appear within multiple elements, and a region chain, which is the collection of elements the flow is spread across. Once these elements have been defined, the flow dynamically fills the elements in the region chain. We can then size our containers vertically without worrying about the content getting cut off, because it simply overflows into next element in the chain. This creates new opportunities for layout with responsive design.
To use regions, start by creating a named flow; simply add the CSS property flow-into to your content element, with the value of your flow’s name. Then, for each region through which you want the content to flow, apply the CSS property flow-from with the same flow name value. The content element will then flow through the region elements. Current implementations in browsers require the property to be prefixed, but we are using the unprefixed version here.
#myContent {
 flow-into: myNamedFlow;
}

.myRegion {
 flow-from: myNamedFlow;
}
Your HTML would contain a content element and the scaffolding of all of the regions that this content will flow through. When you use regions, the content element will not be visible in its original location and any HTML already in your region elements will disappear, replaced by the content being flowed into them. Because of this, we can have placeholder or fallback content within our region elements.
<div class="myRegion"></div>
<div class="myRegion"></div>
<div class="myRegion"></div>

<div id="myContent">...</div>
When using regions, the content being flowed is not a child of the region elements. You are only changing where the content is displayed. According to the DOM, everything remains the same, so the content does not inherit styles from the region in which it lives. Instead, the specification defines a CSS pseudo-selector, ::region(), which allows you to style the content within a region. Apply the pseudo-element to the region’s selector and then pass a selector as an argument, specifying the elements that will be styled within a particular region.
.myRegion::region(p){
    /*styles for all the paragraphs flowing inside our regions*/
}

Responsive Design With Regions

Responsive design is the technique of creating malleable layouts that stretch and change according to the given context. Frequently, designers will make elements flexible with percentages and media queries to adapt a layout to different screen sizes. Responsive design adapts content to every screen without requiring the designer to completely overhaul the design or code.
Regions facilitate responsive design in several ways. First, you no longer have to rely on height: auto for every element to ensure content fits. Instead, you can allow the content to flow into different elements within the layout. This means that the content does not dictate the layout, but rather adapts to the intended design. You can still use height: auto on the last region in the chain to ensure that it extends to display all remaining content. You can see this technique in the CodePen example below.

Regions And Events

You can use JavaScript events with regions to manage your layout and to ensure that content is displayed properly. The regions specification defines events that you can use to respond to certain conditions. The regionoversetchange event is dispatched when the regionOverset property changes for any region. This can occur when a user resizes the page, stretching out the container element so that the content no longer flows into certain regions. The value of regionOverset is either fit, overset or empty. A value of empty specifies no content inside the region. The regionOverset property is set to overset when the last region in the chain is unable to display all of the remaining content, making some of the content unreadable.
The fit value sets content to fit within the region properly, either completely (if earlier in the chain) or partially (if it is the last region in the chain). How you respond to these events will depend on the design, content and other aspects of your layout. These events could be used to dynamically add or remove regions or to apply a class that changes the layout. You can see an example of the former technique in the CodePen below.
Note: Some implementations call the event regionlayoutupdate, instead of regionoversetchange, based on an earlier version of the specification.

Regions And Media Queries

Regions are defined entirely in CSS, making them easy to use in combination with media queries. In addition to resizing and positioning elements, you can completely change which elements are defined as regions. You can also set a region to display: none, which will cause it to be skipped entirely in the region chain. This capability makes it easy to remove particular regions from a layout without worrying about the continuity of the content. You can also use this technique to display whole new templates with completely different layouts, without ever changing the content.

Regions And Break Properties

Regions also extend break properties from the multi-column layout specification, which you can use to define how content breaks within your regions. You can apply these properties to elements within the flow either to always break or to avoid breaking a region relative to the element. Using the value region for break-before or break-after will always force a region to break before or after the element, respectively. The value avoid-region can be used for break-before, break-after or break-inside to prevent regions from breaking before, after or inside the element.
This technique is useful for keeping related elements grouped together and for preventing important elements from being split. The demo below shows images along the right column and long descriptive text flowing along the left. If you reduce the width of your browser, then media queries will change the layout, causing the images to redistribute over the narrower single-column structure. Applying break-after: region to the image containers ensures that a new region break will occur after each image in the image flow.
Note: Some implementations use non-standard regions-specific break properties with a region prefix; for example, region-break-before or, with a vendor prefix, -webkit-region-break-before.
Responsive Layout
The break-after property is applied to regions with media queries.

Regions And Viewport Units

Viewport units enable you to use the window (or viewport) as the basis for sizing elements, which creates a consistent aspect ratio and harmony in the layout. You can simulate pages or blocks that break up the content cohesively. A potential pitfall of this approach is that, if you use the aspect ratio of the device to size containers, defining both the width and the height, then your content might no longer fit inside the containers.
You could, however, use regions to break up the content while respecting the variable-sized elements across different screen sizes. You can see this technique being applied in the “Demo for National Geographic Orphan Elephants.” On this website, images and text are alternated to maintain the height of the viewport. We use regions to flow the content through all of the text sections, and we adjust them when the user shrinks the screen.
elephant-demo2
Regions being used with viewport units. Notice how the image fits the window exactly. (Large view)
The typical navigation paradigm for magazines and books on a tablet is pagination — i.e. enabling the user to swipe or tap to page through the content. As a designer, you want these pages to respond to a variety of screen sizes. Regions are particularly useful for this kind of layout, because you can size columns using viewport units and create a variety of different layouts that enable content to flow across the columns. An example of this done in HTML is shown in the video below:
The Kindle Cloud Reader website has a similar two-page spread but uses JavaScript to manage the layout. Implementing this kind of layout in JavaScript requires significant development overhead, and manipulating the DOM so heavily will usually incur a performance penalty. You can use regions to bring these capabilities natively to the browser, increasing the website’s performance while reducing development time.

Debugging

When working with regions, it’s helpful to have tools to easily manage and debug various features. In Chrome Developer Tools, you can enable debugging features specific to regions. Detailed instructions on enabling these tools can be found in Christian Cantrell’s post “Web Inspector Support for CSS Regions.” With these features, you can find all of the named flows in a document, find the content and region chain associated with each named flow, and get visual cues for whether content fits in a region based on the regionOverset property.
Webkit Nightly also has some helpful visual cues. When you open the Web Inspector and inspect a region’s container, you will see a region number and links between the region containers showing the flow of the content.
region_debugging
Webkit Nightly allows you to inspect region containers, showing their number and the flow chain.

Monday, June 17, 2013

Designing CSS Layouts With Flexbox Is As Easy As Pie

Flexible box layout (or flexbox) is a new box model optimized for UI layout. As one of the first CSS modules designed for actual layout (floats were really meant mostly for things such as wrapping text around images), it makes a lot of tasks much easier, or even possible at all. Flexbox’s repertoire includes the simple centering of elements (both horizontally and vertically), the expansion and contraction of elements to fill available space, and source-code independent layout, among others abilities.
Flexbox has lived a storied existence. It started as a feature of Mozilla’s XUL, where it was used to lay out application UI, such as the toolbars in Firefox, and it has since been rewritten multiple times. The specification has only recently reached stability, and we have fairly complete support across the latest versions of the leading browsers.
There are, however, some caveats. The specification changed between the implementation in Internet Explorer (IE) and the release of IE 10, so you will need to use a slightly different syntax. Chrome currently still requires the -webkit- prefix, and Firefox and Safari are still on the much older syntax. Firefox has updated to the latest specification, but that implementation is currently behind a runtime flag until it is considered stable and bug-free enough to be turned on by default. Until then, Firefox still requires the old syntax.
When you specify that an element will use the flexbox model, its children are laid out along either the horizontal or vertical axis, depending on the direction specified. The widths of these children expand or contract to fill the available space, based on the flexible length they are assigned.

Example: Horizontal And Vertical Centering (Or The Holy Grail Of Web Design)

Being able to center an element on the page is perhaps the number one wish among Web designers — yes, probably even higher than gaining the highly prized parent selector or putting IE 6 out of its misery (OK, maybe a close second then). With flexbox, this is trivially easy. Let’s start with a basic HTML template, with a heading that we want to center. Eventually, once we’ve added all the styling, it will end up looking like this vertically and horizontally centered demo.
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8"/>
   <title>Centering an Element on the Page</title>
</head>
<body>
   <h1>OMG, I’m centered</h1>
</body>
</html>
Nothing special here, not even a wrapper div. The magic all happens in the CSS:
html {
   height: 100%;
} 

body {
   display: -webkit-box;   /* OLD: Safari,  iOS, Android browser, older WebKit browsers.  */
   display: -moz-box;      /* OLD: Firefox (buggy) */ 
   display: -ms-flexbox;   /* MID: IE 10 */
   display: -webkit-flex;  /* NEW, Chrome 21+ */
   display: flex;          /* NEW: Opera 12.1, Firefox 22+ */

   -webkit-box-align: center; -moz-box-align: center; /* OLD… */
   -ms-flex-align: center; /* You know the drill now… */
   -webkit-align-items: center;
   align-items: center;

   -webkit-box-pack: center; -moz-box-pack: center; 
   -ms-flex-pack: center; 
   -webkit-justify-content: center;
   justify-content: center;

   margin: 0;
   height: 100%;
   width: 100% /* needed for Firefox */
} 

h1 {
   display: -webkit-box; display: -moz-box;
   display: -ms-flexbox;
   display: -webkit-flex;
   display: flex;
 
   -webkit-box-align: center; -moz-box-align: center;
   -ms-flex-align: center;
   -webkit-align-items: center;
   align-items: center;

   height: 10rem;
}
I’ve included all of the different prefixed versions in the CSS above, from the very oldest, which is still needed, to the modern and hopefully final syntax. This might look confusing, but the different syntaxes map fairly well to each other, and I’ve included tables at the end of this article to show the exact mappings.
This is not exactly all of the CSS needed for our example, because I’ve stripped out the extra styling that you probably already know how to use in order to save space.
Let’s look at the CSS that is needed to center the heading on the page. First, we set the html and body elements to have 100% height and remove any margins. This will make the container of our h1 take up the full height of the browser’s window. Firefox also needs a width specified on the body to force it to behave. Now, we just need to center everything.

Enabling Flexbox

Because the body element contains the heading that we want to center, we will set its display value to flex:
body {
   display: flex;
}
This switches the body element to use the flexbox layout, rather than the regular block layout. All of its children in the flow of the document (i.e. not absolutely positioned elements) will now become flex items.
The syntax used by IE 10 is display: -ms-flexbox, while older Firefox and WebKit browsers use display: -prefix-box (where prefix is either moz or webkit). You can see the tables at the end of this article to see the mappings of the various versions.
What do we gain now that our elements have been to yoga class and become all flexible? They gain untold powers: they can flex their size and position relative to the available space; they can be laid out either horizontally or vertically; and they can even achieve source-order independence. (Two holy grails in one specification? We’re doing well.)

Centering Horizontally

Next, we want to horizontally center our h1 element. No big deal, you might say; but it is somewhat easier than playing around with auto margins. We just need to tell the flexbox to center its flex items. By default, flex items are laid out horizontally, so setting the justify-content property will align the items along the main axis:
body {
   display: flex;
   justify-content: center;
}
For IE 10, the property is called flex-pack, while for older browsers it is box-pack (again, with the appropriate prefixes). The other possible values are flex-start, flex-end, space-between and space-around. These are start, end, justify and distribute, respectively, in IE 10 and the old specification (distribute is, however, not supported in the old specification). The flex-start value aligns to the left (or to the right with right-to-left text), flex-end aligns to the right, space-between evenly distributes the elements along the axis, and space-around evenly distributes along the axis, with half-sized spaces at the start and end of the line.
To explicitly set the axis that the element is aligned along, you can do this with the flex-flow property. The default is row, which will give us the same result that we’ve just achieved. To align along the vertical axis, we can use flex-flow: column. If we add this to our example, you will notice that the element is vertically centered but loses the horizontal centering. Reversing the order by appending -reverse to the row or column values is also possible (flex-flow: row-reverse or flex-flow: column-reverse), but that won’t do much in our example because we have only one item.
There are some differences here in the various versions of the specification, which are highlighted at the end of this article. Another caveat to bear in mind is that flex-flow directions are writing-mode sensitive. That is, when using writing-mode: vertical-rl to switch to vertical text layout (as used traditionally in China, Japan and Korea), flex-flow: row will align the items vertically, and column will align them horizontally.

Centering Vertically

Centering vertically is as easy as centering horizontally. We just need to use the appropriate property to align along the “cross-axis.” The what? The cross-axis is basically the axis perpendicular to the main one. So, if flex items are aligned horizontally, then the cross-axis would be vertical, and vice versa. We set this with the align-items property (flex-align in IE 10, and box-align for older browsers):
body {
   /* Remember to use the other versions for IE 10 and older browsers! */
   display: flex;
   justify-content: center;
   align-items: center;
}
This is all there is to centering elements with flexbox! We can also use the flex-start (start) and flex-end (end) values, as well as baseline and stretch. Let’s have another look at the finished example:
figure1.1_mini
Simple horizontal and vertical centering using flexbox. Larger view.
You might notice that the text is also center-aligned vertically inside the h1 element. This could have been done with margins or a line height, but we used flexbox again to show that it works with anonymous boxes (in this case, the line of text inside the h1 element). No matter how high the h1 element gets, the text will always be in the center:
h1 {
   /* Remember to use the other versions for IE 10 and older browsers! */
   display: flex;
   align-items: center;
   height: 10rem;
}

Flexible Sizes

If centering elements was all flexbox could do, it’d be pretty darn cool. But there is more. Let’s see how flex items can expand and contract to fit the available space within a flexbox element. Point your browser to this next example.
figure1.2_mini
An interactive slideshow built using flexbox. Larger view.
The HTML and CSS for this example are similar to the previous one’s. We’re enabling flexbox and centering the elements on the page in the same way. In addition, we want to make the title (inside the header element) remain consistent in size, while the five boxes (the section elements) adjust in size to fill the width of the window. To do this, we use the new flex property:
section {
   /* removed other styles to save space */
   -prefix-box-flex: 1; /* old spec webkit, moz */
   flex: 1;
   height: 250px;
}
What we’ve just done here is to make each section element take up 1 flex unit. Because we haven’t set any explicit width, each of the five boxes will be the same width. The header element will take up a set width (277 pixels) because it is not flexible. We divide the remaining width inside the body element by 5 to calculate the width of each of the section elements. Now, if we resize the browser window, the section elements will grow or shrink.
In this example, we’ve set a consistent height, but this could be set to be flexible, too, in exactly the same way. We probably wouldn’t always want all elements to be the same size, so let’s make one bigger. On hover, we’ve set the element to take up 2 flex units:
section:hover {
   -prefix-box-flex: 2;
   flex: 2;
   cursor: pointer;
}
Now the available space is divided by 6 rather than 5, and the hovered element gets twice the base amount. Note that an element with 2 flex units does not necessarily become twice as wide as one with 1 unit. It just gets twice the share of the available space added to its “preferred width.” In our examples, the “preferred width” is 0 (the default).

Source-Order Independence

For our last party trick, we’ll study how to achieve source-order independence in our layouts. When clicking on a box, we will tell that element to move to the left of all the other boxes, directly after the title. All we have to do is set the order with the order property. By default, all flex items are in the 0 position. Because they’re in the same position, they follow the source order. Click on your favorite person in the updated example to see their order change.
figure1.3_mini
An interactive slideshow with flex-order. Larger view.
To make our chosen element move to the first position, we just have to set a lower number. I chose -1. We also need to set the header to -1 so that the selected section element doesn’t get moved before it:
header {
   -prefix-box-ordinal-group: 1; /* old spec; must be positive */
   -ms-flex-order: -1; /* IE 10 syntax */
   order: -1; /* new syntax */
} 

section[aria-pressed="true"] {
   /* Set order lower than 0 so it moves before other section elements,
      except old spec, where it must be positive.
 */
   -prefix-box-ordinal-group: 1;
   -ms-flex-order: -1;
   order: -1;

   -prefix-box-flex: 3;
   flex: 3;
   max-width: 370px; /* Stops it from getting too wide. */
}
In the old specification, the property for setting the order (box-ordinal-group) accepts only a positive integer. Therefore, I’ve set the order to 2 for each section element (code not shown) and updated it to 1 for the active element. If you are wondering what aria-pressed="true" means in the example above, it is a WAI-ARIA attribute/value that I add via JavaScript when the user clicks on one of the sections.
This relays accessibility hints to the underlying system and to assistive technology to tell the user that that element is pressed and, thus, active. If you’d like more information on WAI-ARIA, check out “Introduction to WAI-ARIA” by Gez Lemon. Because I’m adding the attribute after the user clicks, this example requires a simple JavaScript file in order to work, but flexbox itself doesn’t require it; it’s just there to handle the user interaction.
Hopefully, this has given you some inspiration and enough introductory knowledge of flexbox to enable you to experiment with your own designs.

Syntax Changes

As you will have noticed throughout this article, the syntax has changed a number of times since it was first implemented. To aid backward- and forward-porting between the different versions, we’ve included tables below, which map the changes between the specifications.
Specification versions
Speci-
fication
IE Opera Firefox Chrome Safari
Standard 11? 12.10+ * Behind flag 21+ (-webkit-)
Mid 10 (-ms-)



Old

3+ (-moz-) <21 (-webkit-) 3+ (-webkit-)
* Opera will soon switch to WebKit. It will then require the -webkit- prefix if it has not been dropped by that time.
Enabling flexbox: setting an element to be a flex container
Speci-
fication
Property name Block-level flex Inline-level flex
Standard display flex inline-flex
Mid display flexbox inline-flexbox
Old display box inline-box
Axis alignment: specifying alignment of items along the main flexbox axis
Speci-
fication
Property name start center end justify distribute
Standard justify-content flex-start center flex-end space-between space-around
Mid flex-pack start center end justify distribute
Old box-pack start center end justify N/A
Cross-axis alignment: specifying alignment of items along the cross-axis
Speci-
fication
Property name start center end baseline stretch
Standard align-items flex-start center flex-end baseline stretch
Mid flex-align start center end baseline stretch
Old box-align start center end baseline stretch
Individual cross-axis alignment: override to align individual items along the cross-axis
Speci-
fication
Property name auto start center end baseline stretch
Standard align-self auto flex-start center flex-end baseline stretch
Mid flex-item-align auto start center end baseline stretch
Old N/A
Flex line alignment: specifying alignment of flex lines along the cross-axis
Speci-
fication
Property name start center end justify distribute stretch
Standard align-content flex-start center flex-end space-between space-around stretch
Mid flex-line-pack start center end justify distribute stretch
Old N/A
This takes effect only when there are multiple flex lines, which is the case when flex items are allowed to wrap using the flex-wrap property and there isn’t enough space for all flex items to display on one line. This will align each line, rather than each item.
Display order: specifying the order of flex items
Speci-
fication
Property name Value
Standard order
Mid flex-order <number>
Old box-ordinal-group <integer>
Flexibility: specifying how the size of items flex
Speci-
fication
Property name Value
Standard flex none | [ <flex-grow> <flex-shrink>? || <flex-basis>]
Mid flex none | [ [ <pos-flex> <neg-flex>? ] || <preferred-size> ]
Old box-flex <number>
The flex property is more or less unchanged between the new standard and the draft supported by Microsoft. The main difference is that it has been converted to a shorthand in the new version, with separate properties: flex-grow, flex-shrink and flex-basis. The values may be used in the same way in the shorthand. However, the default value for flex-shrink (previously called negative flex) is now 1. This means that items do not shrink by default. Previously, negative free space would be distributed using the flex-shrink ratio, but now it is distributed in proportion to flex-basis multiplied by the flex-shrink ratio.
Direction: specifying the direction of the main flexbox axis
Speci-
fication
Property name Horizontal Reversed horizontal Vertical Reversed vertical
Standard flex-direction row row-reverse column column-reverse
Mid flex-direction row row-reverse column column-reverse
Old box-orient

box-direction
horizontal

normal
horizontal

reverse
vertical

normal
vertical

reverse
In the old version of the specification, the box-direction property needs to be set to reverse to get the same behavior as row-reverse or column-reverse in the later version of the specification. This can be omitted if you want the same behavior as row or column because normal is the initial value.
When setting the direction to reverse, the main flexbox axis is flipped. This means that when using a left-to-right writing system, the items will display from right to left when row-reverse is specified. Similarly, column-reverse will lay out flex items from bottom to top, instead of top to bottom.
The old version of the specification also has writing mode-independent values for box-orient. When using a left-to-write writing system, horizontal may be substituted for inline-axis, and vertical may be substituted for block-axis. If you are using a top-to-bottom writing system, such as those traditional in East Asia, then these values would be flipped.
Wrapping: specifying whether and how flex items wrap along the cross-axis
Speci-
fication
Property name No wrapping Wrapping Reversed wrap
Standard flex-wrap nowrap wrap wrap-reverse
Mid flex-wrap nowrap wrap wrap-reverse
Old box-lines single multiple N/A
The wrap-reverse value flips the start and end of the cross-axis, so that if flex items are laid out horizontally, instead of items wrapping onto a new line below, they will wrap onto a new line above.
At the time of writing, Firefox does not support the flex-wrap or older box-lines property. It also doesn’t support the shorthand.
The current specification has a flex-flow shorthand, which controls both wrapping and direction. The behavior is the same as the one in the version of the specification implemented by IE 10. It is also currently not supported by Firefox, so I would recommend to avoid using it when specifying only the flex-direction value.

Conclusion

Well, that’s a (flex-)wrap. In this article, I’ve introduced some of the myriad of possibilities afforded by flexbox. Be it source-order independence, flexible sizing or just the humble centering of elements, I’m sure you can find ways to employ flexbox in your websites and applications. The syntax has settled down (finally!), and implementations are here. All major browsers now support flexbox in at least their latest versions.
While some browsers use an older syntax, Firefox looks like it is close to updating, and IE 11 uses the latest version in leaked Windows Blue builds. There is currently no word on Safari, but it is a no-brainer considering that Chrome had the latest syntax before the Blink-WebKit split. For the time being, use the tables above to map the various syntaxes, and get your flex on.
Layout in CSS is only getting more powerful, and flexbox is one of the first steps out of the quagmire we’ve found ourselves in over the years, first with table-based layouts, then float-based layouts. IE 10 already supports an early draft of the Grid layout specification, which is great for page layout, and Regions and Exclusions will revolutionize how we handle content flow and layout.
Flexbox can be used today if you only need to support relatively modern browsers or can provide a fallback, and in the not too distant future, all sorts of options will be available, so that we can use the best tool for the job. Flexbox is shaping up to be a mighty fine tool.

Further Reading