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.
- Layer 1X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
- Layer 2X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
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.
- Layer 1X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
- Layer 2X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
One layer
With one layer there is nothing to reorder, so the layer has no move handle.
- Layer 1X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
Layers alone
Without the preview, Add layer or Remove, for a shadow whose layers are fixed and shown elsewhere.
- Layer 1X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
- Layer 2X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
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.
- Layer 1X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
- Layer 2X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
Keep a shadow to two or three soft layers, as a system’s shadow levels are.
- Layer 1X offset in pixelsY offset in pixelsBlur in pixelsSpread in pixelsColorOpacity
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
| Key | What 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
/* 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
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| space.stack.md--smbk-space-stack-md | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| space.inset.3xl--smbk-space-inset-3xl | comfortable: 32pxcompact: 24pxchanges with density | Spacing | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.subtle--smbk-color-border-subtle | light: #c4cdd8dark: #2d3237changes with theme | Color | |
| radius.container.sm--smbk-radius-container-sm | 8px | Radius | |
| color.surface.canvas--smbk-color-surface-canvas | light: #e7ebf0dark: #111213changes with theme | Color | |
| size.48--smbk-size-48 | 48px | Size | |
| size.64--smbk-size-64 | 64px | Size | |
| color.surface.default--smbk-color-surface-default | light: #f3f5f8dark: #181a1cchanges with theme | Color | |
| space.stack.sm--smbk-space-stack-sm | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| space.inset.md--smbk-space-inset-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| elevation.raised--smbk-elevation-raised | light: 0px 2px 8px 1px #373d431a, 0px 1px 2px 0px #373d431adark: 0px 2px 8px 1px #00000026, 0px 1px 2px 0px #00000026 | Elevation | |
| size.control.xs--smbk-size-control-xs | 24px | Size | |
| space.inline.xs--smbk-space-inline-xs | 4px | Spacing | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| 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-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px | The quick brown fox | Typography |
| size.icon.sm--smbk-size-icon-sm | 16px | Size | |
| radius.control.xs--smbk-radius-control-xs | 2px | Radius | |
| color.text.subtle--smbk-color-text-subtle | light: #4e555edark: #9ba5b1changes with theme | Color | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| focus.ring.offset--smbk-focus-ring-offset | 2px | Focus | |
| space.inline.sm--smbk-space-inline-sm | comfortable: 6pxcompact: 4pxchanges with density | Spacing | |
| space.stack.xs--smbk-space-stack-xs | 4px | Spacing | |
| 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-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| size.target.comfortable--smbk-size-target-comfortable | 44px | Size |
Manifest
{
"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"
}