DEV Community

Cover image for Day 7: Making buttons look like "clouds" for embedded Google Maps
Masa Kudamatsu
Masa Kudamatsu

Posted on • Edited on • Originally published at Medium

Day 7: Making buttons look like "clouds" for embedded Google Maps

TL;DR

I've made a button look like a cloud for the web app I'm currently building.

The app features embedded Google Maps. It needs buttons for showing the user's current location and for saving a place of the user's interest.

What should buttons look like over a street map?

A cloud is my answer.

A map is like a view of streets from high up in the sky, and clouds would be seen part of this view. Cloud-shaped buttons will then fit into the view of Google Maps!


This article describes in detail how I’ve made web app buttons look like clouds, including the design process (Sections 1 and 2) and the HTML/CSS coding (Sections 4 to 6). It also documents how I’ve added a shadow to the cloud-shaped button (Section 7) and styled the button’s focus and active states (Sections 8 and 9).

Hopefully, this article will be useful for those who want to make a bit unusual buttons for a web app that they are building.

1. Why Cloud-shaped?

First, let me explain in more detail why I've shaped web app buttons like clouds.

The design concept of My Ideal Map App (the app I'm currently building) is "Dye Me In Your Hue From The Sky". By saving the places of his/her interest, the user "dyes" nobody's Google Maps in his/her hue. The dyeing process is undertaken from the sky, because a street map is the view of a city from the sky.

An aerial photo

View of a city from the sky (image source: Asahiair.com)

Now, what should buttons look like, to be appropriate for the UI design based on this concept?

A map is shown across the screen, and buttons are overlaid on it. That is, buttons are shown above the aerial view of a city. When we fly on an airplane and look down through the window, what do we see above the ground?

Yes, clouds!

An aerial photo of central London amid clouds Aerial view of central London amid clouds (image source: Wirestock via Freepik)

If buttons, overlaid on street maps, are shaped like clouds, we can reinforce the design concept of My Ideal Map that the user dyes a map in his/her hue from the sky.

I wouldn't come up with the idea of making buttons look like clouds if I hadn't developed the design concept representing the main feature of the app (see Day 2 of this blog series for more detail). I've just witnessed the power of the design concept to drive the UI design process.

2. Designing a cloud-shaped button

However, if we make a button to look like a very realistic cloud, the user won't notice it is a button to tap or click on the screen. I need to make a button look like a cloud while it still looks like a button.

With pen and paper, I start sketching a cloud-shaped button.

The starting point is what I have learned from Osamu Masuyama, an animation art director responsible for the blockbuster movie Spirited Away. In one of his books, he advises us to draw clouds as the circles of various sizes that overlap each other.

So I draw three circles of various sizes, overlapping each other. Three should be enough. Four or more will tilt the balance between "buttons" and "clouds" towards "clouds".

But I'm never successful of creating a cloud-like shape.

Then a question comes to my mind: what do those "cloud" icons look like? Those icons that are often used to indicate a cloud storage service? I look up Material Design's cloud icon.

A cloud-shaped icon with the caption that reads

The cloud icon of Material Icons (image source: Google Fonts)

Analyzing what this icon is made of, I learn that a cloud can be represented with a row of three circles, the middle one positioned higher than the other two, the right one positioned lower than the other two.

I start sketching again, but this time with Sketch app, so that circles are drawn accurately.

I first draw a circle of diameter 36px. I then duplicate this circle and move it to the left by 10px and down by 8px. That's the left part of the cloud. I go back to the first circle and duplicate it again. I move this third circle to the right by 10px and down by 12px. I get this;

A stylized cloud made of three overlapping white circles
Shape of a cloud-shaped button (screenshot of Sketch app by the author)

I experiment with three circles of different sizes. But then the unwelcome realism kicks in: it starts looking less and less like a button on the user interface.

I conclude that the three overlapping circles of the same size strike the right balance between "button" and "cloud".

3. Button Label

My Ideal Map App will need four buttons, but in this article, I focus on the menu button for the sake of simple exposition. For other buttons and how I position them all over the screen, see Day 8 of this blog series (not yet published).

For the menu button, I use the "hamburger" icon as its label:

Three horizontal stripes

Hamburger icon (image source: Material Icons via Google Fonts)

I download the SVG data of this icon from Google Fonts, import it into Sketch, overlay and center-align it on the cloud-shaped button, and export it as an SVG image:

The cloud-shaped button with three horizontal stripes as its label on it

Menu button created with Sketch app (image source: author)

4. HTML code with inline SVG

(This section is revised on Dec 17, 2021; see Day 16 of this blog series for why.)

To use an SVG image as the button label, one way to go is to wrap an <img> element with <button> and to set the src attribute to be the SVG image file name, like this:

<button type="button">
  <img src="button-label.svg" />
</button>
Enter fullscreen mode Exit fullscreen mode

However, I need to turn the SVG image itself into a button. Inline SVG (i.e., embedding SVG code into the HTML code) allows me to freely change the color and shadow of the button in response to user interactions and to switching between light and dark modes (see Coyier 2013 for more detail).

When I started learning web development, writing up the inline SVG code appeared to be too tricky. One day, however, I decided to crack it by taking time to go through an SVG tutorial by W3Schools and another tutorial by Copes (2018). Since then, a range of web design I can manage to implement with code has expanded.

Let's start writing up HTML for an inline SVG icon button. First, create a <button> element:

<button type="button">
</button>
Enter fullscreen mode Exit fullscreen mode

A button should be defined as the <button> element. It has many useful built-in features such as pressing the Space key to click the button (Coyier 2020). The type attribute should be "button" because this button won't submit any data to the server (in that case, use type="submit") or clear the user's inputs in a form (that's for type="reset").

Next, give an accessible name to the button with the aria-label attribute:

<button type="button" aria-label="Show menu"> <!-- Revised -->
</button>
Enter fullscreen mode Exit fullscreen mode

An accessible name is what screen readers will announce to the visually impaired user (see Watson 2017 for more detail). If a button has text as its label, its accessible name is the label text. If a button has only an icon image, we need to figure out how to give it a name. Otherwise, screen readers will just announce "button", leaving the user unsure of what will happen after they press the button.

Soueidan (2019) and O'Hara (2019) both recommend the use of aria-label to "label" an icon button. I initially gave an accessible name to the inline SVG image, hoping that browsers would then recognize it as the button's accessible name. I was wrong. See Day 16 of this blog series for detail.

Next, wrap the <svg> element inside the <button> element:

<button type="button" aria-label="Show menu">
  <!-- ADDED FROM HERE -->
  <svg viewBox="0 0 56 48"
       width="56px"
       height="48px"
       aria-hidden="true">
    <--! SVG image data to be inserted -->
  </svg>
  <!-- ADDED UNTIL HERE -->
</button>
Enter fullscreen mode Exit fullscreen mode

First of all, aria-hidden="true" is added to the <svg> element because screen readers do not need to “see” it: the button label is already given by aria-label.

Now let me give you a short SVG 101 tutorial. :-)

The viewBox is the most important attribute. Its value consists of 4 numbers, defining the coordinate system to draw an SVG image. The first two numbers define the coordinates of the top-left corner of the box; the last two numbers set the width and height of a box in which an SVG image is drawn. Consequently, in the above code (partly copied from the SVG file exported with Sketch), the top-left coordinates are (0,0) and the bottom-right (56,48). The SVG code uses this coordinate system to specify points through which the outlines of various shapes go through.

The width and height attributes shouldn't be necessary. But I've learned from my own experience that, without them, Safari fails to render the SVG icon inside the cloud-shaped button (I don't know why). It defeats the core idea of SVG images as “scalable” vector graphic. But I don't plan to change the size of these buttons by screen width. So I think it's fine in this context.

We don't need any other attributes for the <svg> element. When an SVG data is exported from graphic design apps such as Sketch, the <svg> element has many other attributes such as xmlns and version. When used as inline SVG code, none of them are necessary. When used as an image file (e.g., as the src attribute value for the <img> element), however, the xmlns attribute is required. See Longson (2013) for detail.

Now, for the SVG image data, I insert the following two <path> elements spitted out by Sketch app:

<path
  d="M45.4620546,13.6147645 C51.6790144,16.4506152 56,22.7206975 56,30 C56,39.9411255 47.9411255,48 38,48 C32.9385058,48 28.3649488,45.9108926 25.09456,42.5479089 C22.9175971,43.482463 20.5192372,44 18,44 C8.0588745,44 0,35.9411255 0,26 C0,17.9805361 5.24437759,11.1859622 12.4906291,8.85878199 C15.6225135,3.55654277 21.3959192,0 28,0 C36.428553,0 43.5040602,5.79307725 45.4620546,13.6147645 Z"
  id="cloud"
/>
<path
  d="M4.5,27 L31.5,27 L31.5,24 L4.5,24 L4.5,27 Z M4.5,19.5 L31.5,19.5 L31.5,16.5 L4.5,16.5 L4.5,19.5 Z M4.5,9 L4.5,12 L31.5,12 L31.5,9 L4.5,9 Z"
  id="material-icon-menu" 
  transform="translate(10.000000, 6.000000)"
/>

Enter fullscreen mode Exit fullscreen mode

The first <path> element defines the outline of the cloud-shaped button. I add the id attribute value of #cloud to be used to apply CSS declarations to it (see Sections 6 and 7 below).

The second <path> element defines the menu icon. I also add the id attribute, just to take note of which icon the element renders. The transform attribute moves the icon to the right by 10 units and down by 6 units, to center-align the icon relative to the SVG image box defined with the viewBox attribute value.

In the SVG code, an element is overlaid with another that comes after. So the <path> element for the button label should come after the one for the button itself.

The d attribute includes a series of two numbers separated with a comma. They represent a point on the coordinate system specified with the <svg> element's viewBox attribute. The alphabets in front of these pairs of numbers specify whether to move to the point (M), to draw a bezier curve (C), to draw a line (L), etc. See MDN Contributors (2021) for detail.

I'm done with HTML. Let's move on to CSS.

5. CSS code with Styled Components

Next up is CSS. I use Styled Components to write CSS code in JavaScript. In recent months, I found a couple of articles on the disadvantage of Styled Components (and CSS-in-JS in general) in terms of performance (Arvanitakis 2019, Dodds updated). As far as I understand, that's not the argument against CSS-in-JS itself. It's the argument against certain ways of using CSS-in-JS. As long as I avoid those ways, I believe the benefit of using JavaScript overwhelms the standard CSS code.

The way I use Styled Components is perhaps a bit unusual. I first define each set of CSS declarations to achieve one purpose as a JavaScript variable. Then I create a styled component by referring to these variables. Here's what I mean:

First, save as resetStyle the CSS declarations for removing the border and background-color of the <button> element:

import {css} from 'styled-components';

const resetStyle = css`
  background-color: rgba(255,255,255,0);
  border: none;
`;
Enter fullscreen mode Exit fullscreen mode

I remove the default border of the <button> element because I want the visual border of a cloud-shaped button to be drawn with the SVG image. And I set the background color to be transparent white. This pair of CSS declarations turns a button element into something transparent to the user's eye.

Next, I set the size of a clickable/tappable area of the button as setClickableArea:

const setClickableArea = css`
  height: 48px;
  width: 56px;
`;
Enter fullscreen mode Exit fullscreen mode

This way, the user can activate the button by tapping/clicking a rectangular area that is 48px high and 56 wide. This satisfies the WCAG 2.1's recommendation: the size of an interactive UI component such as a button must be at least 44px wide and 44px high (§2.5.5 of WCAG 2.1). It also satisfies Google's recommendation of 48x48px, which corresponds to a person's finger pad size (Gash et al. 2020).

Then, to center-align the cloud-shape SVG image within the tappable area, I define alignButtonLabel as follows:

const alignButtonLabel = css`
  align-items: center;
  display: flex;
  justify-content: center;
`;
Enter fullscreen mode Exit fullscreen mode

Without this, the SVG image gets left-aligned within the tappable area.

Finally, using these three variables, we style the <button> element to create <Button> component (which can be used just like a React component, thanks to Styled Components):

import styled from 'styled-components';

const Button = styled.button`
  ${resetStyle}
  ${setClickableArea}
  ${alignButtonLabel}
`;
Enter fullscreen mode Exit fullscreen mode

Writing the code this way, I can immediately tell how the <Button> component is styled, rather than deciphering a series of CSS declarations.

This coding style is inspired by Cube CSS, a CSS methodology that recently attracted attention from web developers. In Cube CSS, a class is created for one single purpose of styling (so each element gets quite a few classes). To achieve the same with Styled Components, I can replace a class with a JavaScript variable.

We're not done yet. We need more CSS declarations to set the color and shadow of these buttons.

6. Button color scheme

6.1 Fill color

I set the fill color of the cloud button to be rgba(255, 255, 255, 0.93), that is, pure white with 93% opacity (or 7% transparency).

Clouds are white. Making the button semi-transparent allows the map beneath to be partially visible, creating an impression that the cloud-shaped button is floating over the map. The opacity value of 0.93 is chosen to strike the balance between making it recognized as a button and creating the "floating" impression.

The cloud-shaped button image is defined in the <path id="cloud"> as a grand-child of the <button> element (see Section 4 above). So the following CSS code sets the fill color of the cloud-shaped button:

button #cloud {
  fill: rgba(255, 255, 255, 0.93);
}
Enter fullscreen mode Exit fullscreen mode

6.2 Label color

The button label color is set to be rgb(90, 90, 90). Its luminance contrast ratio is 3.04 to 1 against pure black. I choose this shade of gray because it allows a focus state to be distinct enough (see Section 8 below).

The following CSS declaration sets the button label color:

button svg {
  fill: rgb(90,90.90);
}
Enter fullscreen mode Exit fullscreen mode

The idea is to set the <svg> element's fill property, which will be cascaded to the <path> element for the button label. The cascading won't be applied to the other <path> element for the cloud shape, because its fill property is directly set already.

6.3 Outline color

The outline of the cloud-shaped button is colored in rgb(148, 148, 148). It's for making the white button perceptually distinct from the white streets in the map. (See Day 4 of this blog series for how I've set the color of various map elements in embedded Google Maps.)

The relative luminance in the map's color scheme ranges from 6 of the gray of city blocks (#898989) to 21 of the pure white of streets (where the value refers to the contrast ratio against pure black). The cloud-shaped buttons will be visually distinct from city blocks on the map, because the semi-transparent white satisfies the 3-to-1 contrast ratio requirement from the gray of city blocks:

Contrast-ratio.com user interface, showing the contrast ratio of #898989 to rgba(255,255,255,0.93) Luminance contrast ratio of #898989 to rgba(255,255,255,0.93) (image source: contrast-ratio.com)

If the cloud-shaped button is above streets on the map, however, it visually merges with them due to the lack of luminance contrast. A solution is to outline the cloud-shaped button with a shade of gray that's just enough to satisfy the 3-to-1 contrast ratio against pure white: rgb(148,148,148):Luminance contrast ratio between rgb(255,255,255) and rgb(148,148,148) is 3.03 to 1

Luminance contrast ratio of rgb(255,255,255) to rgb(148,148,148) (image source: contrast-ratio.com)

The following CSS declaration sets the outline color of the cloud-shaped button:

button #cloud {
  stroke: rgb(148, 148, 148);
}
Enter fullscreen mode Exit fullscreen mode

However, outlining the cloud-shaped button with this gray makes the button look, uh, ugly...

So I want to add a shadow to the button so the distinction between the outline and the shadow gets blurred, which will make the button look naturally floated above the map.

7. Shadows for cloud-shaped buttons

7.1 Why needed?

Another reason is that a shadow makes a button appear floated above the background, which allows the user to recognize it as a button (i.e., something to push down) more easily. Based on “36 interviews with fifteen low-vision participants”, Google (undated) reports:

“Using a shadow or stroke outline around a component improves one’s ability to determine whether or not it can be interacted with.”

7.2 CSS coding

For a standard rectangular button, its shadow can be added with the box-shadow CSS property. But I want to apply a shadow to a cloud-shaped SVG image. In this case, we can use the filter CSS property with the drop-shadow() function as its value (Olawanle 2021).

The drop-shadow() function allows us to specify four aspects of a shadow: where it is located, how blurry its edge is, and which color it has.

7.3 Location of a shadow

For the location of a shadow, we need to decide where the (imaginary) light source is located. Usually it's at the top-left or top-right of a screen. We human beings perceive depth by interpreting shadows as created by the light from above (Livingstone 2014, chap 11). I guess that's because we are used to the sunlight coming from above.

However, the design concept of My Ideal Map App dictates that the light comes from behind the user. This is because the user interface of a map is interpreted to be a view of streets from the sky. The sun is behind the user's head.

Consequently, buttons on the user interface of My Ideal Map App should have a shadow all around their edges, rather than on the bottom-right or bottom-left edges. This implies that the first two parameters for the drop-shadow() function is 0 0. The center of the shadow is directly beneath the center of the button.

7.4 Blurriness of shadow edges

Shadows are blurry on their edges, because, if I understand correctly, the farther away from the edge of an object, the more ambient light illuminates the shadowed surface. By ambient light, I mean light comes from all directions. For the outdoor, ambient light is the sunlight reflected by the water molecules in the air or by the surrounding objects. In the indoor, ambient light is the light reflected by the walls, the ceiling, and other objects inside the room.

To make the edge of a shadow blurry, UI designers need to set the parameter known as "blur radius". If it's set to be 1px, for example, then the shadow is rendered in the specified color at 1px inside the shadow edge. Then, the shadow color's opacity declines by half at the shadow edge and goes to zero at 1px outside the shadow edge. The rate of change in opacity is constant.

However, a shadow looks natural if the rate of reduction in darkness is decreasing the farther away from the object. That is, if the intensity of a shadow is 4 around the edge of an object, the first 1 unit of distance reduces it by 2, the second 1 unit of distance reduces it by 1, the third 1 unit of distance reduces it by 0.5, and so forth.

A single shadow cannot create such a non-linear change in the intensity of a shadow. A solution is to layer multiple shadows with increasing blur-radius (Ahlin 2019). So I apply shadows as follows:

button svg {
  filter: 
    drop-shadow(0 0 1px rgba(0,0,0,0.33)) 
    drop-shadow(0 0 2px rgba(0,0,0,0.33))  
    drop-shadow(0 0 4px rgba(0,0,0,0.33));
}
Enter fullscreen mode Exit fullscreen mode

In this code, I assume the color of shadows is rgba(0, 0, 0, 0.33), the reason for which will be described in Section 7.5 below.

With this CSS code, the opacity of a shadow declines rapidly for the first 1px from the edge, because all the three shadows are applied. But from 1px to 2px from the edge, only the second and third shadows apply, hence a slower reduction in opacity. From 2px to 4px from the edge, only the third shadow applies, with an even slower reduction in opacity.

7.5 Color of shadows

The color of a shadow should be semi-transparent black. A shadow is essentially a smaller amount of reflected light (which is colored as the hue of the surface) reaching the human eyes, compared to its surrounding area. On the screen, a shadow can be represented by overlaying a semi-transparent black so the surface color is retained while the amount of light is reduced.

The question is to what extent the shadow black should be semi-transparent.

I want the opacity of the shadow to be 42% at the edge of the button, because, when the background is pure white, rgba(0,0,0,0.42) becomes the same shade of gray as rgb(148,148,148), the color of the button outline as discussed in Section 6.3 above.

Semi-transparent black (rgba(0,0,0,0.42)) has the luminance contrast ratio of 1:3.03 against the background of pure white (#fff)

A shade of gray defined as rgb(148,148,148) has the luminance contrast ratio of 1:3.03 against pure white (#fff)

Luminance contrast ratio of rgba(0, 0, 0, 0.42) and rgb(148, 148, 148) against white background(image source: contrast-ratio.com

This way, the distinction between the button outline and the shadow gets blurry, and the outline's gray will be perceived as part of the shadow.

However, if I use rgba(0, 0, 0, 0.42) as the shadow color, the actual opacity of black will be much higher at the edge of the button. There are two reasons for this. First, as described above, the blur radius of 1px will use the specified shadow color at 1px inside its edge. At the edge (which coincides with the button's edge, due to the location of a shadow directly beneath the button), the opacity is halved (i.e., rgba(0, 0, 0, 0.21)). The second reason is that I draw three layers of shadows of different degrees of blur radius to make the shadow look natural, as described above. At the edge of the button, therefore, the shadow opacity will be three times higher than rgba(0, 0, 0, 0.21).

Given all these considerations, I've found out that using rgba(0, 0, 0, 0.33) as the color of a shadow will make the opacity of a shadow similar to rgba(0, 0, 0, 0.42). I omit the full detail of calculation, but the basic idea is as follows. If we overlay two shadows of 50% opacity, the resulting shadow will have an opacity level of 75%. This is because the level of transparency will be 0.5 x 0.5 = 0.25. I'm not sure if this calculation is correct, but it seems to be a good approximation.

Consequently, the following CSS code specifies the shadow of our cloud-shaped button.

button svg {
  filter: 
    drop-shadow(0 0 1px rgba(0,0,0,0.33)) 
    drop-shadow(0 0 2px rgba(0,0,0,0.33))  
    drop-shadow(0 0 4px rgba(0,0,0,0.33));
}
Enter fullscreen mode Exit fullscreen mode

And the cloud-shaped button is rendered as follows:
The cloud-shaped button rendered over embedded Google Maps at the top-left corner of an iPad screen
Cloud-shaped menu button, shown over embedded Google Maps (screenshot by the author)


But this is not the end of a story yet. In UI design, it is important to give visual feedback to the user's action. We also need to set the style for the focus/hover state and the active state of the button (Bailey 2018, Sticka 2018).

8. Styling focus/hover states

For keyboard users (including those visually-impaired who use screen readers), pressing a button with the Enter key requires an indication of which button is currently in focus. The button should therefore change its appearance when it's in focus.

In addition, for mouse users, a hover state is helpful for them to notice if a button is clickable. I usually design the focus and hover states in the same way, to keep the number of button styles to a minimum (which is also suggested by Maza 2019).

Deciding on how to style the focus state is always a headache to me. A big help for my decision process is the article by Maza (2019), who lists up the five major ways of styling the focus state: changing the background color, changing the text color, adding a shadow, increasing the size, and customizing the default outline. With this list, I can consider each option and decide which option is the most appropriate for the website / webapp I'm making.

For our cloud-shaped button, changing the background color is not effective. I don't want a cloud to be red, green, etc. Making it gray is not ideal, either, because it will look like a rainy cloud. Adding a shadow is not feasible, as it's already added (see above). Increasing the size is tacky and inconsistent with the design concept of My Ideal Map App.

Changing the text color (in our case the button label icon's color) is a feasible option. I can turn the gray of the button label into black: rgb(3, 3, 3):

button:focus svg,
button:hover svg {
  fill: rgb(3, 3, 3)
}
Enter fullscreen mode Exit fullscreen mode

I avoid rgb(0,0,0) for preventing the "black smear" problem on the OLED screens (see Edwards 2018). Also, as mentioned at the beginning of this subsection, I design the focus and hover states in the same way. So the CSS selector refers to both button:focus and button:hover.

But this change is not noticeable enough. So the last option is to customize the default outline for focus style.

For designing the outline on the focus state, I follow Coyier (2012), who replicates the focus state of Twitter's text field back in 2012. His idea is to draw a 1px-wide border plus a shadow with blur radius of 5px and zero offset, both in the same Twitter blue. This way, when focused, the text field gets lit up with blue glow around its edges.

Text field is indicated as being in the focus state, with the blue glow around its edges

Blue glow indicates that the text field is currently in focus (image source: Coyier 2012)

The question is which color to use for the outline. When stuck on color, my rule is to go back to the mood board which visualizes the design concept. Since the button is cloud-shaped, a blueish color would be nice to create an impression of clouds in the blue sky. So I pick the color of the sky reflected on the water surface of a lake in this photo from our daytime mood board picture (see Day 3 of this blog series):

A photograph of a partially frozen fall with its pool in deep blue Partially Frozen Aldeyjarfoss, a fall in Iceland (image source: Shivesh Ram via National Geographic)

With Mac OS's Digital Colour Meter, I sample the deep blue color of the fall's pool. Then, with my webapp Triangulum Color Picker, I reduce its luminance without changing the share of pure hue in it so it's dark enough to satisfy the 3-to-1 contrast against the pure white of the button. The result is rgb(69, 159, 189).

Triangulum Color Picker's user interface, showing the luminance of rgb(69, 159, 189) is 6.95 times higher than pure black The hue and luminance of rgb(69,159,189) (image source: Triangulum Color Picker)

With this shade of cyan-blue, I write the following CSS code:

button:focus #cloud,
button:hover #cloud {
  stroke: rgb(69,159,189);
}

button:focus svg,
button:hover svg {
  filter: drop-shadow(0 0 5px rgb(69,159,189));
}

Enter fullscreen mode Exit fullscreen mode

To set the outline color for SVG images, we need to use the stroke property instead of border. And I need to set the stroke property for the <path id='cloud'> element (which defines the cloud shape). If I apply the stroke property for the <svg> element instead, the button label icon will also be outlined. That's not what I want.

With all the coding so far, the focus state of the cloud-shaped button is rendered as follows:
The cloud-shaped button with blue glow around it, rendered over embedded Google Maps at the top-left corner iPad screen
The cloud-shaped button in focus state (screenshot by the author)

I was happy with the result until I realized that color-blind users would not be able to detect the change in the button's appearance when it's in focus. The blue glow and the default shadow has a similar level of luminance, and they are both blurred.

I need to reconsider stying the focus state as I continue developing the app. Styling the focus state is always a headache to me...

And designing a button is not yet finished. The last style to be designed is for when the button is clicked/tapped.

9. Styling active state

Finally, we need to style the active state: when the user taps/clicks the button. Without it, the user won't tell if tapping the button makes any difference—what the UI design guru Don Norman calls “the gulf of evaluation”.

I might want to add the ripple effect, an increasingly common way of expressing the active state of a button ever since Google's Material Design adopted it. But I'm not sure if it goes in line with the idea of using a cloud as a button. Clouds won't ripple...

A simple solution to style the active state is to disable the focus/hover state. When the user taps/clicks the button, it first turns on the focus state, then on the active state, and finally turns off the active state while keeping the focus state on. So the difference in style between focus and active states translates into a flash of something. In our case, the focus state has a blue outline. By disabling this outline in the active state, the outline briefly disappears when the user taps/clicks the button, as if the internally-illuminated button briefly switches its light off.

To disable the focus/hover state in the active state, the following CSS code does the job:

  button:active #cloud {
    stroke: none;
  }
  button:active svg {
    filter: none;
  }

Enter fullscreen mode Exit fullscreen mode

And the button label briefly goes back to the default shade of gray, adding to the flashing effect:

button:active svg {
  fill: rgb(90, 90, 90)
}
Enter fullscreen mode Exit fullscreen mode

Finally, designing and coding a single button is done, at least for now.

Left: cloud-shaped button with the three horizontal stripes on it, surrounded with a shadow; Middle: the same cloud-shaped button, now surrounded with a sky blue glow; Right: three horizontal stripes only

The three button styles: default (left), focus/hover (middle), active (right) (Image source: author)

Next up

Making cloud-shaped buttons is not finished yet. I need to create four of them and position them across the screen. Plus, I need to set the dark mode color scheme of the buttons. Each requires a fair amount of programming techniques, the description of which will make this article more than a 40-minute read... So I defer these two topics to next two articles.

Thank you for reading this much. Making buttons for a web app is not an easy task...

Changelog

Oct 4, 2021 (v1.0.1): The paragraph on the viewBox attribute is revised to correct the description of what the last two values of the attribute specify.

Dec 17, 2021 (v2.0.0): Section 4 is revised. The way I define the button's accessible name has changed, as the previous approach proved to be wrong.

References

Ahlin, Tobias (2019) “Smoother & sharper shadows with layered box-shadows”, tobiasahlin.com, Sep 19, 2019.

Arvanitakis, Aggelos (2019) “The unseen performance costs of modern CSS-in-JS libraries in React apps”, Web Performance Calendar, Dec 9, 2019.

Bailey, Eric (2018) “Focusing on Focus Styles”, CSS-Tricks, Mar 29, 2018.

Copes, Flavio (2018) “An in-depth SVG tutorial”, flaviocopes.com, Apr 6, 2018.

Coyier, Chris (2012) “Glowing Blue Input Highlights”, CSS-Tricks, Apr 11, 2012.

Coyier, Chris (2013) “Using SVG”, CSS-Tricks, Mar 5, 2013.

Coyier, Chris (2020) “A Complete Guide to Links and Buttons“, CSS-Tricks, Feb 14, 2020.

Dodds, Kent C. (undated) “Use CSS Variables instead of React Context”, Epic React, undated.

Edwards, Marc (2018) “I’m not a fan of pure black in UI…”, Twitter, Oct 20, 2018.

Gash, Dave, Meggin Kearney, Rachel Andrew, and Rob Dodson (2020) “Accessible tap targets”, web.dev, Mar 31, 2020.

Google (undated) “Light and Shadows”, Material Design, undated.

Kudamatsu, Masa (2021) “Mastering the art of alt text for images”, Web Dev Survey form Kyoto, May 19, 2021.

Livingstone, Margaret S. (2014) Vision and Art: the Biology of Seeing (Revised and Expanded Edition) (Abrams).

Longson, Robert (2013) “All user agens (browsers) ignore the version attribute...”, Stack Overflow, Aug 27, 2013.

Maza, Lari (2019) “Having a Little Fun With Custom Focus Styles”, CSS-Tricks, Dec 2, 2019.

MDN Contributors (2021) “d”, MDN Web Docs, Sep 24, 2021 (last updated).

O'Hara, Scott (2019) “Contextually Marking up accessible images and SVGs”, scottohara.me, May 22, 2019.

Olawanle, Joel (2021) “Adding Shadows to SVG Icons With CSS and SVG Filters”, CSS-Tricks, Jun 11, 2021.

Soueidan, Sara (2019) “Accessible Icon Buttons”, sarasoueidan.com, May 22, 2019.

Sticka, Tyler (2018) “Designing Button States”, Cloud Four, March 13, 2018.

Watson, Léonie (2017) "What is an accessible name?", TPGi, Apr 11, 2017.

Top comments (3)

Collapse
 
grahamthedev profile image
GrahamTheDev

Really in depth, love it!

For focus state why not just make the stroke thicker?

Alternatively you could go back to the SVG and use the offset tool to create a second outline of the cloud a couple of pixels further out and then hide this element unless the cloud is focused.

For contrast you may have to make a white outline offset by 2px and a black outline offset at 3px (for example) so that it works on any background colour.

Collapse
 
masakudamatsu profile image
Masa Kudamatsu

Thank you for letting me know your response! It took quite a bit to write this article while I was worried that no one would read such a long article with too much detail. Your comment taught me some people do appreciate my writing style.

Thickening the stroke for the focus state makes the button, uh, ugly... It won't look like a cloud anymore once a thick outline is added.

The difficulty stems from the button's background color being white. If it's gray, then I can change the shadow from black-ish to white-ish, which should be noticeable to the color-blind. For the dark mode (to be written about in a future article), the button will be gray, just like clouds in the night sky. So flipping the shadow from very dark to very bright works. But for the light mode, and because the button should look like a cloud, it's difficult...

I know accessibility shouldn't be compromised because of too much emphasis on aesthetics. But I don't want to give up too quickly. Let's see if I can come up with a good idea...

Collapse
 
grahamthedev profile image
GrahamTheDev

Remember that a white shadow will have terrible contrast with the surroundings if you are over say a white roofed house.

Instead of a shadow try the offset focus ring I suggested with a white strip and a black strip that follows the shape of the cloud so it will have contrast on any background, that should look aesthetically pleasing.

Either way I look forward to seeing what you decide to do…oh and

I know accessibility shouldn't be compromised because of too much emphasis on aesthetics.

Neither should aesthetics be compromised for accessibility, that is where the fun part is, making it accessible and pretty! ❤️