26

In the latest Material Design documentation (https://www.google.com/design/spec/what-is-material/elevation-shadows.html#elevation-shadows-elevation-android-) an exhaustive set of UI elements are referenced with their respective elevation (z-index in dp). For example a switch is elevated by 1dp, while a dialog is elevated by 24dp. Currently Google's list of UI elements uses 10 different elevation levels. Since the elevation decides the shadow of the element, we'll need 10 different shadows. And that's where I'm lost.

How do you calculate/deduce the right shadow values (color, x-offset, y-offset, blur, spread) for each elevation level?

I've found different sources that have calculated shadow values for 5 different elevations (https://news.layervault.com/stories/42319-calculating-shadow-values-for-material-design). However, 5 elevation steps is not enough, nor do they give an explanation as to how they got to these respective values.

Anthony Wijnen
  • 261
  • 1
  • 3
  • 6

8 Answers8

25

Got a good news! I got all the shadow depths from depth-1 to depth-24. I got this from Angular Material. Hope this would help you.

.md-whiteframe-1dp {
    box-shadow:  0px 1px 3px 0px rgba(0, 0, 0, 0.2),
    0px 1px 1px 0px rgba(0, 0, 0, 0.14),
    0px 2px 1px -1px rgba(0, 0, 0, 0.12); }
.md-whiteframe-2dp {
    box-shadow:  0px 1px 5px 0px rgba(0, 0, 0, 0.2),
    0px 2px 2px 0px rgba(0, 0, 0, 0.14),
    0px 3px 1px -2px rgba(0, 0, 0, 0.12); }
.md-whiteframe-3dp {
    box-shadow:0px 1px 8px 0px rgba(0, 0, 0, 0.2),
    0px 3px 4px 0px rgba(0, 0, 0, 0.14),
    0px 3px 3px -2px rgba(0, 0, 0, 0.12); }
.md-whiteframe-4dp {
    box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2),
    0px 4px 5px 0px rgba(0, 0, 0, 0.14),
    0px 1px 10px 0px rgba(0, 0, 0, 0.12); }
.md-whiteframe-5dp {
    box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
    0px 5px 8px 0px rgba(0, 0, 0, 0.14),
    0px 1px 14px 0px rgba(0, 0, 0, 0.12); }
.md-whiteframe-6dp {
    box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
    0px 6px 10px 0px rgba(0, 0, 0, 0.14),
    0px 1px 18px 0px rgba(0, 0, 0, 0.12); }
.md-whiteframe-7dp {
    box-shadow: 0px 4px 5px -2px rgba(0, 0, 0, 0.2),
    0px 7px 10px 1px rgba(0, 0, 0, 0.14),
    0px 2px 16px 1px rgba(0, 0, 0, 0.12); }
.md-whiteframe-8dp {
    box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2),
    0px 8px 10px 1px rgba(0, 0, 0, 0.14),
    0px 3px 14px 2px rgba(0, 0, 0, 0.12); }
.md-whiteframe-9dp {
    box-shadow: 0px 5px 6px -3px rgba(0, 0, 0, 0.2),
    0px 9px 12px 1px rgba(0, 0, 0, 0.14),
    0px 3px 16px 2px rgba(0, 0, 0, 0.12); }
.md-whiteframe-10dp {
    box-shadow: 0px 6px 6px -3px rgba(0, 0, 0, 0.2),
    0px 10px 14px 1px rgba(0, 0, 0, 0.14),
    0px 4px 18px 3px rgba(0, 0, 0, 0.12); }
.md-whiteframe-11dp {
    box-shadow: 0px 6px 7px -4px rgba(0, 0, 0, 0.2),
    0px 11px 15px 1px rgba(0, 0, 0, 0.14),
    0px 4px 20px 3px rgba(0, 0, 0, 0.12); }
.md-whiteframe-12dp {
    box-shadow: 0px 7px 8px -4px rgba(0, 0, 0, 0.2),
    0px 12px 17px 2px rgba(0, 0, 0, 0.14),
    0px 5px 22px 4px rgba(0, 0, 0, 0.12); }
.md-whiteframe-13dp {
    box-shadow: 0px 7px 8px -4px rgba(0, 0, 0, 0.2),
    0px 13px 19px 2px rgba(0, 0, 0, 0.14),
    0px 5px 24px 4px rgba(0, 0, 0, 0.12); }
.md-whiteframe-14dp {
    box-shadow: 0px 7px 9px -4px rgba(0, 0, 0, 0.2),
    0px 14px 21px 2px rgba(0, 0, 0, 0.14),
    0px 5px 26px 4px rgba(0, 0, 0, 0.12); }
.md-whiteframe-15dp {
    box-shadow: 0px 8px 9px -5px rgba(0, 0, 0, 0.2),
    0px 15px 22px 2px rgba(0, 0, 0, 0.14),
    0px 6px 28px 5px rgba(0, 0, 0, 0.12); }
.md-whiteframe-16dp {
    box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.2),
    0px 16px 24px 2px rgba(0, 0, 0, 0.14),
    0px 6px 30px 5px rgba(0, 0, 0, 0.12); }
.md-whiteframe-17dp {
    box-shadow: 0px 8px 11px -5px rgba(0, 0, 0, 0.2),
    0px 17px 26px 2px rgba(0, 0, 0, 0.14),
    0px 6px 32px 5px rgba(0, 0, 0, 0.12); }
.md-whiteframe-18dp {
    box-shadow: 0px 9px 11px -5px rgba(0, 0, 0, 0.2),
    0px 18px 28px 2px rgba(0, 0, 0, 0.14),
    0px 7px 34px 6px rgba(0, 0, 0, 0.12); }
.md-whiteframe-19dp {
    box-shadow: 0px 9px 12px -6px rgba(0, 0, 0, 0.2),
    0px 19px 29px 2px rgba(0, 0, 0, 0.14),
    0px 7px 36px 6px rgba(0, 0, 0, 0.12); }
.md-whiteframe-20dp {
    box-shadow: 0px 10px 13px -6px rgba(0, 0, 0, 0.2),
    0px 20px 31px 3px rgba(0, 0, 0, 0.14),
    0px 8px 38px 7px rgba(0, 0, 0, 0.12); }
.md-whiteframe-21dp {
    box-shadow: 0px 10px 13px -6px rgba(0, 0, 0, 0.2),
    0px 21px 33px 3px rgba(0, 0, 0, 0.14),
    0px 8px 40px 7px rgba(0, 0, 0, 0.12); }
.md-whiteframe-22dp {
    box-shadow: 0px 10px 14px -6px rgba(0, 0, 0, 0.2),
    0px 22px 35px 3px rgba(0, 0, 0, 0.14),
    0px 8px 42px 7px rgba(0, 0, 0, 0.12); }
.md-whiteframe-23dp {
    box-shadow: 0px 11px 14px -7px rgba(0, 0, 0, 0.2),
    0px 23px 36px 3px rgba(0, 0, 0, 0.14),
    0px 9px 44px 8px rgba(0, 0, 0, 0.12); }
.md-whiteframe-24dp {
    box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2),
    0px 24px 38px 3px rgba(0, 0, 0, 0.14),
    0px 9px 46px 8px rgba(0, 0, 0, 0.12); }
Scott Roa
  • 251
  • 3
  • 2
  • 1
    I want to use this in Android, but dons't understand how to read this? How can I read this. Eg: Like what will be shadow of left, top, right, bottom px and the color when we take any dp. – Saran Sankaran Mar 15 '18 at 06:13
  • It helped a lot – Gratien Asimbahwe Oct 04 '18 at 10:39
  • @SaranSankaran there are four properties: ````h-offset v-offset blur spread color```` and you can have multiple on each element by separating them with a comma, as in the example above. See more at https://www.w3schools.com/cssref/css3_pr_box-shadow.asp – Streching my competence Oct 11 '18 at 02:56
15

Here's a function that I came up with that gives a good result, I believe:

(Javascript)

function getShadow(object, dp)
{
    if (dp <= 0)
    {
        panel.style.boxShadow = "none";
        return;
    }

    panel.style.boxShadow = "0px " + dp + "px " + dp + "px " + "rgba(0, 0, 0, .38)";
}

A jfiddle: https://jsfiddle.net/crkb906z/

Basically, the distance from the base layer is how far down the shadow top is, and I use that same value for the blur to make it fuzzier as the material gets higher.

I compared the result of this to the 3 shadows used by polymer and they were very comparable. Since I can't even find consistency between the 5 layer shadows that most people provide, I imagine exactly how it's calculated isn't that important.

EDIT

Okay, after studying the available box-shadows for the 5 depths (they originated from here, but Google has since removed this from the documentation), I've come up with a more complex formula that results in shadows that look much more like the examples in that link:

function applyShadow(element, dp)
{
    if (dp == 0)
    {
        element.style.boxShadow = "none";
    }
    else
    {
        var shadow = "0px ";

        var ambientY = dp;
        var ambientBlur = dp == 1 ? 3 : dp * 2;
        var ambientAlpha = (dp + 10 + (dp / 9.38)) / 100;

        shadow += ambientY + "px " + ambientBlur + "px rgba(0, 0, 0, " + ambientAlpha + "), 0px ";

        var directY = (dp < 10 ? (dp % 2 == 0 ? dp - ((dp / 2) - 1) : (dp - ((dp - 1) / 2))) : dp - 4);
        var directBlur = dp == 1 ? 3 : dp * 2;
        var directAlpha = (24 - Math.round(dp / 10)) / 100;

        shadow += directY + "px " + directBlur + "px rgba(0, 0, 0, " + directAlpha + ")";

        element.style.boxShadow  = shadow;
    }
}

I update https://jsfiddle.net/crkb906z/1/ to show the difference.

Troncoso
  • 2,343
  • 3
  • 33
  • 52
5

I'm only posting this answer because Google's official MDC Web elevation stuff is different from what's been posted so far.

Here's the source file I'm copying from:

https://github.com/material-components/material-components-web/blob/e02b4c9fa2bd026c695f3a71efce58d01f460269/packages/mdc-elevation/_variables.scss

Google's official box-shadow values for elevation (SASS mixin):

//
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

@import "@material/animation/variables";

$mdc-elevation-baseline-color: black;
$mdc-elevation-umbra-opacity: .2;
$mdc-elevation-penumbra-opacity: .14;
$mdc-elevation-ambient-opacity: .12;

$mdc-elevation-umbra-map: (
  0: "0px 0px 0px 0px",
  1: "0px 2px 1px -1px",
  2: "0px 3px 1px -2px",
  3: "0px 3px 3px -2px",
  4: "0px 2px 4px -1px",
  5: "0px 3px 5px -1px",
  6: "0px 3px 5px -1px",
  7: "0px 4px 5px -2px",
  8: "0px 5px 5px -3px",
  9: "0px 5px 6px -3px",
  10: "0px 6px 6px -3px",
  11: "0px 6px 7px -4px",
  12: "0px 7px 8px -4px",
  13: "0px 7px 8px -4px",
  14: "0px 7px 9px -4px",
  15: "0px 8px 9px -5px",
  16: "0px 8px 10px -5px",
  17: "0px 8px 11px -5px",
  18: "0px 9px 11px -5px",
  19: "0px 9px 12px -6px",
  20: "0px 10px 13px -6px",
  21: "0px 10px 13px -6px",
  22: "0px 10px 14px -6px",
  23: "0px 11px 14px -7px",
  24: "0px 11px 15px -7px"
);

$mdc-elevation-penumbra-map: (
  0: "0px 0px 0px 0px",
  1: "0px 1px 1px 0px",
  2: "0px 2px 2px 0px",
  3: "0px 3px 4px 0px",
  4: "0px 4px 5px 0px",
  5: "0px 5px 8px 0px",
  6: "0px 6px 10px 0px",
  7: "0px 7px 10px 1px",
  8: "0px 8px 10px 1px",
  9: "0px 9px 12px 1px",
  10: "0px 10px 14px 1px",
  11: "0px 11px 15px 1px",
  12: "0px 12px 17px 2px",
  13: "0px 13px 19px 2px",
  14: "0px 14px 21px 2px",
  15: "0px 15px 22px 2px",
  16: "0px 16px 24px 2px",
  17: "0px 17px 26px 2px",
  18: "0px 18px 28px 2px",
  19: "0px 19px 29px 2px",
  20: "0px 20px 31px 3px",
  21: "0px 21px 33px 3px",
  22: "0px 22px 35px 3px",
  23: "0px 23px 36px 3px",
  24: "0px 24px 38px 3px"
);

$mdc-elevation-ambient-map: (
  0: "0px 0px 0px 0px",
  1: "0px 1px 3px 0px",
  2: "0px 1px 5px 0px",
  3: "0px 1px 8px 0px",
  4: "0px 1px 10px 0px",
  5: "0px 1px 14px 0px",
  6: "0px 1px 18px 0px",
  7: "0px 2px 16px 1px",
  8: "0px 3px 14px 2px",
  9: "0px 3px 16px 2px",
  10: "0px 4px 18px 3px",
  11: "0px 4px 20px 3px",
  12: "0px 5px 22px 4px",
  13: "0px 5px 24px 4px",
  14: "0px 5px 26px 4px",
  15: "0px 6px 28px 5px",
  16: "0px 6px 30px 5px",
  17: "0px 6px 32px 5px",
  18: "0px 7px 34px 6px",
  19: "0px 7px 36px 6px",
  20: "0px 8px 38px 7px",
  21: "0px 8px 40px 7px",
  22: "0px 8px 42px 7px",
  23: "0px 9px 44px 8px",
  24: "0px 9px 46px 8px"
);

/**
 * The css property used for elevation. In most cases this should not be changed. It is exposed
 * as a variable for abstraction / easy use when needing to reference the property directly, for
 * example in a `will-change` rule.
 */
$mdc-elevation-property: box-shadow !default;

/**
 * The default duration value for elevation transitions.
 */
$mdc-elevation-transition-duration: 280ms !default;

/**
 * The default easing value for elevation transitions.
 */
$mdc-elevation-transition-timing-function: $mdc-animation-standard-curve-timing-function !default;

Here's a vanilla ES6 elevation function I put together based on the above:

const umbraColor = 'rgba(0, 0, 0, 0.2)';
const penumbraColor = 'rgba(0, 0, 0, 0.14)';
const ambientColor = 'rgba(0, 0, 0, 0.12)';


const umbra = [
  "0px 0px 0px 0px",
  "0px 2px 1px -1px",
  "0px 3px 1px -2px",
  "0px 3px 3px -2px",
  "0px 2px 4px -1px",
  "0px 3px 5px -1px",
  "0px 3px 5px -1px",
  "0px 4px 5px -2px",
  "0px 5px 5px -3px",
  "0px 5px 6px -3px",
  "0px 6px 6px -3px",
  "0px 6px 7px -4px",
  "0px 7px 8px -4px",
  "0px 7px 8px -4px",
  "0px 7px 9px -4px",
  "0px 8px 9px -5px",
  "0px 8px 10px -5px",
  "0px 8px 11px -5px",
  "0px 9px 11px -5px",
  "0px 9px 12px -6px",
  "0px 10px 13px -6px",
  "0px 10px 13px -6px",
  "0px 10px 14px -6px",
  "0px 11px 14px -7px",
  "0px 11px 15px -7px"
];

const penumbra = [
  "0px 0px 0px 0px",
  "0px 1px 1px 0px",
  "0px 2px 2px 0px",
  "0px 3px 4px 0px",
  "0px 4px 5px 0px",
  "0px 5px 8px 0px",
  "0px 6px 10px 0px",
  "0px 7px 10px 1px",
  "0px 8px 10px 1px",
  "0px 9px 12px 1px",
  "0px 10px 14px 1px",
  "0px 11px 15px 1px",
  "0px 12px 17px 2px",
  "0px 13px 19px 2px",
  "0px 14px 21px 2px",
  "0px 15px 22px 2px",
  "0px 16px 24px 2px",
  "0px 17px 26px 2px",
  "0px 18px 28px 2px",
  "0px 19px 29px 2px",
  "0px 20px 31px 3px",
  "0px 21px 33px 3px",
  "0px 22px 35px 3px",
  "0px 23px 36px 3px",
  "0px 24px 38px 3px"
];

const ambient = [
  "0px 0px 0px 0px",
  "0px 1px 3px 0px",
  "0px 1px 5px 0px",
  "0px 1px 8px 0px",
  "0px 1px 10px 0px",
  "0px 1px 14px 0px",
  "0px 1px 18px 0px",
  "0px 2px 16px 1px",
  "0px 3px 14px 2px",
  "0px 3px 16px 2px",
  "0px 4px 18px 3px",
  "0px 4px 20px 3px",
  "0px 5px 22px 4px",
  "0px 5px 24px 4px",
  "0px 5px 26px 4px",
  "0px 6px 28px 5px",
  "0px 6px 30px 5px",
  "0px 6px 32px 5px",
  "0px 7px 34px 6px",
  "0px 7px 36px 6px",
  "0px 8px 38px 7px",
  "0px 8px 40px 7px",
  "0px 8px 42px 7px",
  "0px 9px 44px 8px",
  "0px 9px 46px 8px"
];

export default (z) => (
  `
    box-shadow: ${umbra[z]} ${umbraColor},
                ${penumbra[z]} ${penumbraColor},
                ${ambient[z]} ${ambientColor};
  `
);
anthonator
  • 4,915
  • 7
  • 36
  • 50
3

Here is Troncoso's formula ported to Stylus.

elevation(dp)
  if dp == 0
    box-shadow none
  else
    dp = unit(dp, px)
    blur = (dp == 1 ? 3 : dp * 2)
    amba = (dp + 10 + (dp / 9.38)) / 100
    diry = (dp < 10 ? (dp % 2 == 0 ? dp - ((dp / 2) - 1) : (dp - ((dp - 1) / 2))) : dp - 4)
    dira = (24 - (round(dp / 10))) / 100
    box-shadow 0px dp blur rgba(0, 0, 0, amba), 0px diry blur rgba(0, 0, 0, dira)
Joshua Kifer
  • 1,717
  • 3
  • 12
  • 14
2

There is a scss-library for that: SCSS-Material-Shadows

Here is the demo

You can look at the whole calculation in the scss-file.

Primary use is through a mixin called mdElevation(). mdElevationElement() is a convenient implementation of the predefined elevation values for all the Material elements found here

For the dynamic effect during elevation transition you can add mdElevationTransition() in your scss. As parameter use the delta between two elevations.

For further detailed information visit the github page.

neox5
  • 1,621
  • 13
  • 16
1

I got some shadows from Material Design Lite by Google. I think this code might help.

.shadow--2dp {
    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.shadow--3dp {
    box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14), 0 3px 3px -2px rgba(0, 0, 0, 0.2), 0 1px 8px 0 rgba(0, 0, 0, 0.12);
}

.shadow--4dp {
    box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}

.shadow--6dp {
    box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.2);
}

.shadow--8dp {
    box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
}

.shadow--16dp {
    box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.2);
}

.shadow--24dp {
    box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
}
Scott Roa
  • 11
  • 1
1

The following JS function receives level of elevation, and returns results which are very close to Google's Material components.

export const getShadow = (elevation=1) => {
    const extraEl = elevation-1
    const getSize = (v1, v24) => `${v1+Math.round(((v24-v1)/23)*extraEl)}px`
    const getShade = (y, blur, spread, alpha) => 
        `0 ${getSize(y[0], y[1])} ${getSize(blur[0], blur[1])} ${getSize(spread[0], spread[1])} rgba(0,0,0,${alpha})`

    return `box-shadow: ${getShade([2,11], [1,15], [-1,-7], 0.2)}, ${getShade([1,24], [1,38], [0,3], 0.14)}, ${getShade([1,9], [3,46], [0,8], 0.12)};`
}

Demo

const getShadow = (elevation=1) => {
 const extraEl = elevation-1
 const getSize = (v1, v24) => `${v1+Math.round(((v24-v1)/23)*extraEl)}px`
 const getShade = (y, blur, spread, alpha) => 
  `0 ${getSize(y[0], y[1])} ${getSize(blur[0], blur[1])} ${getSize(spread[0], spread[1])} rgba(0,0,0,${alpha})`

 return `box-shadow: ${getShade([2,11], [1,15], [-1,-7], 0.2)}, ${getShade([1,24], [1,38], [0,3], 0.14)}, ${getShade([1,9], [3,46], [0,8], 0.12)};`
}

const appDiv = document.getElementById('app');
appDiv.innerHTML = `<div style="margin: 50px; width: 100px; height: 100px; ${getShadow(12)}">Hello world</div>`;
<div id="app"></div>

It uses values from the first and last (24th) levels of elevation.

Ben Carp
  • 24,214
  • 9
  • 60
  • 72
0

Here is a slightly modified port for Sass:

@mixin shadow($dp: 1) {
    @if $dp == 0 { box-shadow: none; }
    @else {
        $blur: if($dp == 1, 3, $dp * 2);
        $amba: ($dp + 11) / 100;
        $diry: $dp - floor($dp / 4);
        $dirb: floor(($dp + 5) / 2);
        $dira: (24 - (round($dp / 10))) / 100;
        box-shadow: 0px #{$dp + 'px'} #{$blur + 'px'} rgba(0, 0, 0, $amba), 0px #{$diry + 'px'} #{$dirb + 'px'} rgba(0, 0, 0, $dira);
    }
}
Spencer
  • 709
  • 6
  • 6