Menu


Shadow editor


Edits one shadow: a preview card drawn with it, its layers with their offsets, blur, spread, color and opacity, and a button to add a layer.

Customize the preview


Show
View
Up and down arrow keys move the layer.
  1. Layer 1
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity
  2. Layer 2
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity

Options


The shadow editor has no choices: it is drawn one way.

Examples


Usage

Two layers, top first, over the card they draw. Drag a layer’s handle, or Tab to it and press the up and down arrow keys, to change their order.

Up and down arrow keys move the layer.
  1. Layer 1
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity
  2. Layer 2
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity

One layer

With one layer there is nothing to reorder, so the layer has no move handle.

  1. Layer 1
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity

Layers alone

Without the preview, Add layer or Remove, for a shadow whose layers are fixed and shown elsewhere.

Up and down arrow keys move the layer.
  1. Layer 1
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity
  2. Layer 2
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity

Guidelines


Purpose

Edits one shadow, such as a design system's shadow level: a preview card drawn with it, its layers from top to bottom, each with its offsets, blur, spread, color and opacity, and a button to add a layer. It is for tools that edit a design system's elevation. To show a shadow without editing it, draw it on a card.

When to use

  • To set the layers of a shadow, and see it as it is set.

When not to use

  • To choose among a system's shadow levels by name. Offer them by name.
  • For a shadow of one hard layer, which reads as a line: soft shadows are two or three layers.

Usage rules

Do

  • Keep a shadow to two or three soft layers.

Don't

  • Hide Remove and Add layer where the reader is meant to change how many layers there are.
Up and down arrow keys move the layer.
  1. Layer 1
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity
  2. Layer 2
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity
Do

Keep a shadow to two or three soft layers, as a system’s shadow levels are.

  1. Layer 1
    X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixels
    Color
    Opacity
Don't

Draw a shadow as one hard layer, which reads as a line rather than lift.

Anatomy

  • The block: a column of the preview, the layers and Add layer, each part of which can be left out but the layers.
  • The preview part: a well of the canvas color holding a card (the card part) on the default surface, raised by the shadow being edited.
  • The layers part: a list of the layer part, top layer first. Each layer is a card on the default surface inside a subtle edge:
    • the head part: the move handle (the handle part, shown when there are two layers or more) and the layer's name, such as "Layer 1" (the name part), then the Remove button (the remove part), which can be left out;
    • the fields part: four small number boxes, X, Y, Blur and Spread, each under its label (the field and label parts);
    • the paint part: the color picker's field and the opacity box, in percent.
  • The add part: a ghost button, "Add layer".

Behavior

Each change is drawn on the preview at once and reported to the page with every layer. shadowToCss writes the layers as a CSS box-shadow. A layer's color keeps its code; its opacity is the layer's own, so the color's own opacity is replaced. Blur never goes below 0. A layer marked as inset stays inset, though the editor offers no switch for it.

Remove takes a layer out; Add layer adds one at the foot. A layer's handle moves it up and down the list, dragged or with the arrow keys; the first layer is drawn on top.

On a touch screen the move handle keeps its size and takes an invisible press area round it, out to the comfortable touch target.

Accessibility


What it does for you

  • The layers are a list. Each box is named with its layer and its label, such as "Layer 1 Blur in pixels".
  • The move handle is a button named "Move Layer 1"; the up and down arrow keys move its layer, and focus stays on it. Dragging is never the only way.
  • Remove is named with its layer, "Remove Layer 2". After it, focus moves to the layer that takes its place, or to Add layer when none is left. Add layer moves focus to the new layer's first box.
  • Each change to the list is said politely: "Layer 3 added", "Layer 2 removed", "Layer 1 moved to place 2 of 3".
  • The preview is hidden from screen readers; the numbers are the content.
  • Every word it says by itself, such as the layers' names, the labels and what it says as layers change, keeps its English unless the page gives its own.
  • On a touch screen the move handle keeps its size and takes an invisible press area round it, out to the comfortable touch target.
  • In a forced-color mode, the edges of the preview and the layers are drawn in the reader's text color.

What you must do

  • Give the layers' names, the labels and the announcements in the page's own language when the page is not in English.

How to test

  • Tab to a layer's boxes and type a number: the preview changes at once. Tab to a move handle and press Up Arrow or Down Arrow: the layer moves, focus stays on the handle, and the move is announced.
  • Press Enter on Remove: the layer goes and focus moves to the layer that took its place, or to Add layer when none is left. Press Enter on Add layer: focus moves to the new layer's first box.
  • With a screen reader, the layers are read as a list, each box by its layer and its label, and each change is announced politely.
  • With text at 200% and the page 320 pixels wide, the layers' boxes stay whole and reachable.
  • In a forced-color mode, the edges of the layers and the preview stay visible.

Keyboard

Shadow editor keys
KeyWhat it does
Tab
Moves focus through the controls of each layer in turn, then to Add layer.
Up ArroworDown Arrow
On a move handle, moves its layer up or down the list. Focus stays on the handle.
EnterorSpace
On Remove or Add layer, presses it.

Source


The files the shadow editor is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.

Stylesheet
shadow-editor.css
/* smbk-shadow-editor — Shadow editor. Figma "Shadow editor" 7417:172 (Preview and Add layer switches) and its
 * "ShadowLayer" 7417:87 (Remove switch).
 * A column, the medium stack step apart: a preview (a card on the default surface, raised by the shadow being edited, in
 * a well of the canvas color inside a subtle edge), the layers from top to bottom, and the Add layer button.
 * Each layer is a card on the default surface inside a subtle edge, the medium inset round it and the small stack step
 * between its rows: a head (the move handle and the layer's name in Label MD, and the Remove button), a row of four
 * small number boxes (X, Y, Blur, Spread) and a row of the color picker's field with the opacity box, each box under its
 * label in Body XS. The shadow on the preview's card is data: the wrapper writes it to a custom property on the block. */

.smbk-shadow-editor {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--smbk-space-stack-md);
  min-width: 0;
}

/* The block frames the words it hides for screen readers, so they stay inside it wherever it is placed: without a
   frame they hang from the page itself and stretch it, past the end of an area that scrolls. No weight, so a parent
   that places the block keeps its say. */
:where(.smbk-shadow-editor) {
  position: relative;
}

.smbk-shadow-editor__preview,
.smbk-shadow-editor__layers {
  align-self: stretch;
}

/* The preview: a 64 tall card in the middle of a 128 tall well. Figma's card is 112 wide, written here as the 48 and 64
   size steps together, since no one size step is 112. */
.smbk-shadow-editor__preview {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding-block: var(--smbk-space-inset-3xl);
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
  border-radius: var(--smbk-radius-container-sm);
  background: var(--smbk-color-surface-canvas);
}

.smbk-shadow-editor__card {
  width: calc(var(--smbk-size-48) + var(--smbk-size-64));
  height: var(--smbk-size-64);
  border-radius: var(--smbk-radius-container-sm);
  background: var(--smbk-color-surface-default);
  box-shadow: var(--smbk-shadow-editor-shadow, none);
}

.smbk-shadow-editor__layers {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ---- a layer (Figma's ShadowLayer) ---- */

.smbk-shadow-editor__layer {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-sm);
  box-sizing: border-box;
  min-width: 0;
  padding: var(--smbk-space-inset-md);
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
  border-radius: var(--smbk-radius-container-sm);
  background: var(--smbk-color-surface-default);
}

.smbk-shadow-editor__layer[data-dragging] {
  box-shadow: var(--smbk-elevation-raised);
}

.smbk-shadow-editor__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: var(--smbk-size-control-xs);
}

.smbk-shadow-editor__title {
  display: flex;
  align-items: center;
  gap: var(--smbk-space-inline-xs);
  min-width: 0;
}

.smbk-shadow-editor__name {
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-label-md-font-family);
  font-size: var(--smbk-typography-label-md-font-size);
  font-weight: var(--smbk-typography-label-md-font-weight);
  line-height: var(--smbk-typography-label-md-line-height);
  letter-spacing: var(--smbk-typography-label-md-letter-spacing);
}

/* The move handle: Figma's 16 px DotsSixVertical icon, in a 24 px target that moves the layer. */
.smbk-shadow-editor__handle {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: var(--smbk-size-icon-sm);
  height: var(--smbk-size-icon-sm);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--smbk-radius-control-xs);
  background: none;
  color: var(--smbk-color-text-subtle);
  cursor: grab;
  touch-action: none;
  outline: none;
}

.smbk-shadow-editor__handle::before {
  content: '';
  position: absolute;
  inset: calc((var(--smbk-size-icon-sm) - var(--smbk-size-control-xs)) / 2);
}

.smbk-shadow-editor__handle .smbk-icon {
  font-size: var(--smbk-size-icon-sm);
}

@media (hover: hover) {
  .smbk-shadow-editor__handle:hover {
    color: var(--smbk-color-text-default);
  }
}

.smbk-shadow-editor__layer[data-dragging] .smbk-shadow-editor__handle {
  cursor: grabbing;
}

.smbk-shadow-editor__handle:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
}

.smbk-shadow-editor__fields,
.smbk-shadow-editor__paint {
  display: grid;
  gap: var(--smbk-space-inline-sm);
}

.smbk-shadow-editor__fields {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* The color takes the room the opacity box, Figma's 64, leaves. */
.smbk-shadow-editor__paint {
  grid-template-columns: minmax(0, 1fr) var(--smbk-size-64);
}

.smbk-shadow-editor__field {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-xs);
  min-width: 0;
}

.smbk-shadow-editor__label {
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
}

/* Words for screen readers only: a label's unit, a move's hint and what changed. */
/* .smbk-shadow-editor__hidden: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */

/* ---- touch: the move handle's area reaches the comfortable target ----
   On a touch screen the handle's invisible area grows from the smallest target to the comfortable one; the dots, the
   focus ring and every place stay as drawn. */

@media (pointer: coarse) {
  .smbk-shadow-editor__handle::before {
    inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }
}

/* ---- high contrast (forced colors) ---- */
@media (forced-colors: active) {
  .smbk-shadow-editor__preview,
  .smbk-shadow-editor__layer {
    border-color: CanvasText;
  }

  .smbk-shadow-editor__card {
    outline: var(--smbk-border-width-hairline) solid CanvasText;
  }

  .smbk-shadow-editor__handle:focus-visible {
    outline-color: Highlight;
  }
}
Tokens it reads
Shadow editor tokens
Token and CSSValuePreviewFoundation
space.stack.md--smbk-space-stack-mdcomfortable: 12pxcompact: 8pxchanges with densitySpacing
space.inset.3xl--smbk-space-inset-3xlcomfortable: 32pxcompact: 24pxchanges with densitySpacing
border-width.hairline--smbk-border-width-hairline1pxBorder width
color.border.subtle--smbk-color-border-subtlelight: #c4cdd8dark: #2d3237changes with themeColor
radius.container.sm--smbk-radius-container-sm8pxRadius
color.surface.canvas--smbk-color-surface-canvaslight: #e7ebf0dark: #111213changes with themeColor
size.48--smbk-size-4848pxSize
size.64--smbk-size-6464pxSize
color.surface.default--smbk-color-surface-defaultlight: #f3f5f8dark: #181a1cchanges with themeColor
space.stack.sm--smbk-space-stack-smcomfortable: 8pxcompact: 6pxchanges with densitySpacing
space.inset.md--smbk-space-inset-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
elevation.raised--smbk-elevation-raisedlight: 0px 2px 8px 1px #373d431a, 0px 1px 2px 0px #373d431adark: 0px 2px 8px 1px #00000026, 0px 1px 2px 0px #00000026Elevation
size.control.xs--smbk-size-control-xs24pxSize
space.inline.xs--smbk-space-inline-xs4pxSpacing
color.text.default--smbk-color-text-defaultlight: #232527dark: #e7ebf0changes with themeColor
typography.label-md--smbk-typography-label-md-font-family--smbk-typography-label-md-font-size--smbk-typography-label-md-font-weight--smbk-typography-label-md-line-height--smbk-typography-label-md-letter-spacing--smbk-typography-label-md-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px

The quick brown fox

Typography
size.icon.sm--smbk-size-icon-sm16pxSize
radius.control.xs--smbk-radius-control-xs2pxRadius
color.text.subtle--smbk-color-text-subtlelight: #4e555edark: #9ba5b1changes with themeColor
focus.ring.width--smbk-focus-ring-width2pxFocus
color.border.focus--smbk-color-border-focuslight: #cc0d0ddark: #f34336changes with themeColor
focus.ring.offset--smbk-focus-ring-offset2pxFocus
space.inline.sm--smbk-space-inline-smcomfortable: 6pxcompact: 4pxchanges with densitySpacing
space.stack.xs--smbk-space-stack-xs4pxSpacing
typography.body-xs--smbk-typography-body-xs-font-family--smbk-typography-body-xs-font-size--smbk-typography-body-xs-font-weight--smbk-typography-body-xs-line-height--smbk-typography-body-xs-letter-spacing--smbk-typography-body-xs-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
size.target.comfortable--smbk-size-target-comfortable44pxSize
Manifest
shadow-editor.manifest.json
{
  "name": "shadow-editor",
  "block": "smbk-shadow-editor",
  "summary": "Edits one shadow: a preview card drawn with it, its layers with their offsets, blur, spread, color and opacity, and a button to add a layer.",
  "adapt": "css",
  "axes": {},
  "flags": {},
  "parts": [
    "preview",
    "card",
    "layers",
    "layer",
    "head",
    "title",
    "handle",
    "name",
    "remove",
    "fields",
    "paint",
    "field",
    "label",
    "add",
    "hidden"
  ],
  "figmaNodeId": "7417:172"
}