Skip to main content

Lunar Fusion

an artistic montage of Figma and Adobe XD component diagrams with a colorful overlay

Adobe's Design System

Component and Token Migration

I had a lot of late nights in college teaching myself the Adobe suite and how to write CSS. If only I had known I would have an opportunity to use my CSS skills to help build and maintain Adobe's open-source design system, Spectrum.

Adobe enlisted my team's help to migrate their UI components (PostCSS) to their newly refactored token system. This work was done during my time as a consultant for Sparkbox.

Project Details

Tools

  • PostCSS
  • CSS Custom Properties
  • Node.js
  • NPM
  • Yarn
  • Adobe XD
  • Figma
  • Milanote
  • Jira
  • git + github

Roles

  • CSS Migration
  • Conversion of XD design specs to CSS
  • Consultation with Adobe Design and Engineering to reach consensus
  • VRT Assessment and Correction
  • Documentation Updates

The Work

Adobe's new token system leveraged a complex custom property cascade build to support four color themes, two design themes, RTL languages, and Windows High Contrast Mode. The work had several requirements.

Update the CSS

The migration work included updating any outdated code and integrating new CSS once it was approved by Adobe engineers (and caniuse.)

Collaborate with Design and Engineering

Adobe's designers provided specs for design updates along with token usage instructions. New CSS was required for components featuring significant design changes, new variants, and new child component dependencies. Designers were often consulted to clarify variant behaviors and appearance as the migration work progressed.

Adobe's Spectrum design website also contains new and existing design specs which were integrated.

Enhance the Documentation

Our team contributed new documentation within the CSS as we did our migration work for easier developer wayfinding. We followed a BEM approach to components, modified to match Adobe's existing markup conventions. We were also tasked with updating markup in the design system's YML files so new variants and design requirements were reflected on the docs site.

diagrams of textfield variants

I used Adobe XD and Figma to illustrate design questions, such as in this visual representation of textfield markup and two possible helptext alignments.

diagrams of a tag component with colorful squares used to measure distances within the component's elements

I used Figma visuals like this as well as Codepens to check for design fidelity.

Migrated Components

A Sampling of some of the components I migrated.

Popover

Migration of popover required the addition of 22 position variants, 10 of which made use of logical properties. The triangular tip which points to the source of the popover needed to be positioned and rotated precisely for each popover position, and overridden for right-to-left languages for the 10 logical positions.

Codepens

screenshot of Adobe Popover

Popover CSS

  /*
  Copyright 2022 Adobe. All rights reserved.
  This file is licensed to you 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 REPRESENTATIONS
  OF ANY KIND, either express or implied. See the License for the specific language
  governing permissions and limitations under the License.
  */
   
  @import "../commons/overlay-coretokens.css";
   
  .spectrum-Popover {
    --spectrum-popover-cross-offset: 0; /* change to offset from source, default is 0 */
   
    --spectrum-popover-background-color: var(--spectrum-background-layer-2-color);
    --spectrum-popover-border-color: var(--spectrum-gray-400);
   
    /* popover inner padding */
    --spectrum-popover-content-area-spacing-vertical: var(--spectrum-popover-top-to-content-area);
   
    /* popover drop shadow */
    --spectrum-popover-shadow-horizontal: var(--spectrum-drop-shadow-x);
    --spectrum-popover-shadow-vertical: var(--spectrum-drop-shadow-y);
    --spectrum-popover-shadow-blur: var(--spectrum-drop-shadow-blur);
    --spectrum-popover-shadow-color: var(--spectrum-drop-shadow-color);
   
    /* popover corner radius */
    --spectrum-popover-corner-radius: var(--spectrum-corner-radius-100);
   
    /* pointer tip dimensions */
    --spectrum-popover-pointer-width: var(--spectrum-popover-tip-width);
    --spectrum-popover-pointer-height: var(--spectrum-popover-tip-height);
   
    /* pointer tip - default spacing to edge - corner radius plus half of tip width to neutralize override */
    --spectrum-popover-pointer-edge-offset: calc(var(--spectrum-corner-radius-100) + (var(--spectrum-popover-tip-width) / 2));
   
    /* pointer tip - spacing to edge will center pointer to source - apply in markup by setting '--spectrum-popover-pointer-edge-offset' value == half of source */
    --spectrum-popover-pointer-edge-spacing: calc(var(--spectrum-popover-pointer-edge-offset) - (var(--spectrum-popover-tip-width) / 2));
  }
   
  /* windows high contrast mode */
  @media (forced-colors: active) {
    .spectrum-Popover {
      --highcontrast-popover-border-color: CanvasText;
    }
  }
   
  .spectrum-Popover {
    @inherit: %spectrum-overlay;
   
    box-sizing: border-box;
    position: absolute;
   
    outline: none; /* Hide focus outline */
   
    padding: var(--mod-popover-content-area-spacing-vertical, var(--spectrum-popover-content-area-spacing-vertical)) 0;
   
    display: inline-flex;
    flex-direction: column;
   
    border-style: solid;
    border-radius: var(--mod-popover-corner-radius, var(--spectrum-popover-corner-radius));
    border-color: var(--highcontrast-popover-border-color, var(--mod-popover-border-color, var(--spectrum-popover-border-color)));
    border-width: var(--mod-popover-border-width, var(--spectrum-popover-border-width));
   
    background-color: var(--mod-popover-background-color, var(--spectrum-popover-background-color));
    filter: drop-shadow(var(--mod-popover-shadow-horizontal, var(--spectrum-popover-shadow-horizontal)) var(--mod-popover-shadow-vertical, var(--spectrum-popover-shadow-vertical)) var(--mod-popover-shadow-blur, var(--spectrum-popover-shadow-blur)) var(--mod-popover-shadow-color, var(--spectrum-popover-shadow-color)));
   
    /* default opens and animates upward */
    &.is-open {
      @inherit: %spectrum-overlay--open;
    }
   
    /* has tip triangle */
    &.spectrum-Popover--withTip {
      .spectrum-Popover-tip {
        /* triangle polygon */
        .spectrum-Popover-tip-triangle {
          stroke-linecap: square;
          stroke-linejoin: miter;
          fill: var(--mod-popover-background-color, var(--spectrum-popover-background-color));
          stroke: var(--mod-popover-border-color, var(--spectrum-popover-border-color));
          stroke-width: var(--mod-popover-border-width, var(--spectrum-popover-border-width));
        }
      }
    }
  }
   
  /* position naming - first position term is popover position, second term is source position */
  /* example: --top-left = popover is to top and source is to left */
   
  /* popover position is at top of source - default placement */
  .spectrum-Popover--top,
  .spectrum-Popover--top-left,
  .spectrum-Popover--top-right,
  .spectrum-Popover--top-start,
  .spectrum-Popover--top-end {
    margin-bottom: var(--spectrum-popover-cross-offset);
    /* popover animates upward ⬆ */
    &.is-open {
      @inherit: %spectrum-overlay--top--open;
    }
  }
  /* popover position is at bottom of source */
  .spectrum-Popover--bottom,
  .spectrum-Popover--bottom-left,
  .spectrum-Popover--bottom-right,
  .spectrum-Popover--bottom-start,
  .spectrum-Popover--bottom-end {
    margin-top: var(--spectrum-popover-cross-offset);
    /* popover animates downward ⬇ */
    &.is-open {
      @inherit: %spectrum-overlay--bottom--open;
    }
  }
  /* popover position is right of source */
  .spectrum-Popover--right,
  .spectrum-Popover--right-bottom,
  .spectrum-Popover--right-top {
    margin-left: var(--spectrum-popover-cross-offset);
    /* popover animates towards right ⮕ */
    &.is-open {
      @inherit: %spectrum-overlay--right--open;
    }
  }
  /* popover position is left of source */
  .spectrum-Popover--left,
  .spectrum-Popover--left-bottom,
  .spectrum-Popover--left-top {
    margin-right: var(--spectrum-popover-cross-offset);
    /* popover animates towards left ⬅ */
    &.is-open {
      @inherit: %spectrum-overlay--left--open;
    }
  }
  /* logical property - popover is horizontally at start */
  .spectrum-Popover--start,
  .spectrum-Popover--start-top,
  .spectrum-Popover--start-bottom {
    margin-inline-end: var(--spectrum-popover-cross-offset);
    /* LTR read, popover animates towards left ⬅ */
    &.is-open {
      @inherit: %spectrum-overlay--left--open;
    }
   
    /* RTL read, popover animates towards right ⮕ */
    [dir="rtl"] & {
      &.is-open {
        @inherit: %spectrum-overlay--right--open;
      }
    }
  }
  /* logical property - popover is horizontally at end */
  .spectrum-Popover--end,
  .spectrum-Popover--end-top,
  .spectrum-Popover--end-bottom {
    margin-inline-start: var(--spectrum-popover-cross-offset);
    /* LTR read, popover animates towards right ⮕ */
    &.is-open {
      @inherit: %spectrum-overlay--right--open;
    }
   
    /* RTL read, popover animates towards left ⬅ */
    [dir="rtl"] & {
    &.is-open {
        @inherit: %spectrum-overlay--left--open;
      }
    }
  }
  /* HAS TIP - popover with triangle pointer */
  /* default, top, and bottom position tip - tip defaults to pointing down ▽ */
  .spectrum-Popover--withTip {
    &.spectrum-Popover,
    &.spectrum-Popover--top,
    &.spectrum-Popover--top-left,
    &.spectrum-Popover--top-right,
    &.spectrum-Popover--top-start,
    &.spectrum-Popover--top-end,
    &.spectrum-Popover--bottom,
    &.spectrum-Popover--bottom-left,
    &.spectrum-Popover--bottom-right,
    &.spectrum-Popover--bottom-start,
    &.spectrum-Popover--bottom-end {
      .spectrum-Popover-tip {
        width: var(--mod-popover-pointer-width, var(--spectrum-popover-pointer-width));
        height: var(--mod-popover-pointer-height, var(--spectrum-popover-pointer-height));
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        margin: auto;
        /* https://stackoverflow.com/questions/44170229/how-to-prevent-half-pixel-svg-shift-on-high-pixel-ratio-devices-retina */
        transform: translate(0, 0);
      }
    }
   
    /* popover is at top of source, tip left and pointing down ▽ */
    &.spectrum-Popover--top-left {
      .spectrum-Popover-tip {
        left: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
        right: auto;
      }
    }
   
  /* popover is at top of source, tip right and pointing down ▽ */
    &.spectrum-Popover--top-right {
      .spectrum-Popover-tip {
        left: auto;
        right: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
      }
    }
   
    /* logical property - popover is above, source and tip are at start, tip pointing down ▽ */
    &.spectrum-Popover--top-start {
      .spectrum-Popover-tip {
        margin-inline-start: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
      }
    }
   
    /* logical property - popover is above, source and tip are at end, tip pointing down ▽ */
    &.spectrum-Popover--top-end {
      .spectrum-Popover-tip {
        margin-inline-end: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
      }
    }
   
    /* popover position is bottom of source with tip pointing up △ */
    &.spectrum-Popover--bottom,
    &.spectrum-Popover--bottom-left,
    &.spectrum-Popover--bottom-right,
    &.spectrum-Popover--bottom-start,
    &.spectrum-Popover--bottom-end {
      .spectrum-Popover-tip {
        top: auto;
        bottom: 100%;
        /* flip triangle to face up */
        transform: scaleY(-1);
      }
    }
   
    /* popover position is bottom, source is at left, tip pointing up △ */
    &.spectrum-Popover--bottom-left {
      .spectrum-Popover-tip {
        left: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
        right: auto;
      }
    }
   
    /* popover position is bottom, source is at right, tip pointing up △ */
    &.spectrum-Popover--bottom-right {
      .spectrum-Popover-tip {
        left: auto;
        right: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
      }
    }
   
    /* logical property - popover is below, source is at start, tip pointing up △ */
    &.spectrum-Popover--bottom-start {
      .spectrum-Popover-tip {
        margin-inline-start: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
      }
    }
   
    /* logical property - popover is below, source is at end, tip pointing up △ */
    &.spectrum-Popover--bottom-end {
      .spectrum-Popover-tip {
        margin-inline-end: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
      }
    }
   
    /* right, left start, end popover position with tip default to pointing right ▷ */
    &.spectrum-Popover--left,
    &.spectrum-Popover--left-bottom,
    &.spectrum-Popover--left-top,
    &.spectrum-Popover--right,
    &.spectrum-Popover--right-bottom,
    &.spectrum-Popover--right-top,
    &.spectrum-Popover--start,
    &.spectrum-Popover--start-top,
    &.spectrum-Popover--start-bottom,
    &.spectrum-Popover--end,
    &.spectrum-Popover--end-top,
    &.spectrum-Popover--end-bottom {
      .spectrum-Popover-tip {
        /* swap height and width for vertical triangle */
        width: var(--mod-popover-pointer-height, var(--spectrum-popover-pointer-height));
        height: var(--mod-popover-pointer-width, var(--spectrum-popover-pointer-width));
        top: 0;
        bottom: 0;
      }
   
      /* left popover with tip pointing right ▷ */
      &.spectrum-Popover--left,
      &.spectrum-Popover--left-bottom,
      &.spectrum-Popover--left-top {
        .spectrum-Popover-tip {
          left: 100%;
          right: auto;
        }
      }
   
      /* right popover with tip pointing left ◁ */
      &.spectrum-Popover--right,
      &.spectrum-Popover--right-bottom,
      &.spectrum-Popover--right-top {
        .spectrum-Popover-tip {
          right: 100%;
          left: auto;
          /* flip tip to point left ◁ */
          transform: scaleX(-1);
        }
      }
   
      /* popover with tip at top */
      &.spectrum-Popover--right-top,
      &.spectrum-Popover--left-top,
      &.spectrum-Popover--start-top,
      &.spectrum-Popover--end-top {
        .spectrum-Popover-tip {
          top: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
          bottom: auto;
        }
      }
   
      /* popover with tip at bottom */
      &.spectrum-Popover--right-bottom,
      &.spectrum-Popover--left-bottom,
      &.spectrum-Popover--start-bottom,
      &.spectrum-Popover--end-bottom {
        .spectrum-Popover-tip {
          top: auto;
          bottom: var(--mod-popover-pointer-edge-spacing, var(--spectrum-popover-pointer-edge-spacing));
        }
      }
    }
   
    /* logical property - start popover position with tip pointing toward end -  LTR default is ▷ */
    &.spectrum-Popover--start,
    &.spectrum-Popover--start-top,
    &.spectrum-Popover--start-bottom {
      .spectrum-Popover-tip {
        margin-inline-start: 100%;
   
        [dir="rtl"] & {
          transform: none;
          /* flip tip to point left ◁ */
          transform: scaleX(-1);
        }
      }
    }
   
    /* logical property - end popover position with tip pointing toward start  -  LTR default is ◁ **/
    &.spectrum-Popover--end,
    &.spectrum-Popover--end-top,
    &.spectrum-Popover--end-bottom {
      /* tip triangle  */
      .spectrum-Popover-tip {
        margin-inline-end: 100%;
        /* flip tip to point right ▷ */
        transform: scaleX(-1);
   
        [dir="rtl"] & {
          /* flip tip to point left ◁ */
          transform: scaleX(1);
        }
      }
    }
  }

Action Bar

One of the more complex components, action bar imports several child components which must be positioned precisely without conflicting with the style guidelines of each child.

screenshot of Adobe Action Bar

Actionbar CSS

  /*
  Copyright 2022 Adobe. All rights reserved.
  This file is licensed to you 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 REPRESENTATIONS
  OF ANY KIND, either express or implied. See the License for the specific language
  governing permissions and limitations under the License.
  */
   
  .spectrum-ActionBar {
    --spectrum-actionbar-height: var(--spectrum-action-bar-height);
    --spectrum-actionbar-corner-radius: var(--spectrum-corner-radius-100);
   
    /* item counter field label */
    --spectrum-actionbar-item-counter-font-size: var(--spectrum-font-size-100);
    --spectrum-actionbar-item-counter-line-height: var(--spectrum-line-height-100);
    --spectrum-actionbar-item-counter-color: var(--spectrum-neutral-content-color-default);
   
    /* cjk language support for item counter */
    &:lang(ja),
    &:lang(zh),
    &:lang(ko) {
      --spectrum-actionbar-item-counter-line-height-cjk: var(--spectrum-cjk-line-height-100);
    }
   
    /* colors - applied to popover */
    --spectrum-actionbar-popover-background-color: var(--spectrum-gray-50);
    --spectrum-actionbar-popover-border-color: var(--spectrum-gray-400);
   
    /* emphasized variation colors */
    --spectrum-actionbar-emphasized-background-color: var(--spectrum-informative-background-color-default);
    --spectrum-actionbar-emphasized-item-counter-color: var(--spectrum-white);
   
    /* spacing of action bar bottom and horizontal outer edge */
    --spectrum-actionbar-spacing-outer-edge: var(--spectrum-spacing-300);
   
    /* spacing of close button */
    --spectrum-actionbar-spacing-close-button-top: var(--spectrum-spacing-100);
    --spectrum-actionbar-spacing-close-button-start: var(--spectrum-spacing-100);
    --spectrum-actionbar-spacing-close-button-end: var(--spectrum-spacing-75);
   
    /* spacing of item counter field label */
    --spectrum-actionbar-spacing-item-counter-top: var(--spectrum-action-bar-top-to-item-counter);
    --spectrum-actionbar-spacing-item-counter-end: var(--spectrum-spacing-400);
   
    /* spacing of action group */
    --spectrum-actionbar-spacing-action-group-top: var(--spectrum-spacing-100);
    --spectrum-actionbar-spacing-action-group-end: var(--spectrum-spacing-100);
   
    /* drop shadow */
    --spectrum-actionbar-shadow-horizontal: var(--spectrum-drop-shadow-x);
    --spectrum-actionbar-shadow-vertical: var(--spectrum-drop-shadow-y);
    --spectrum-actionbar-shadow-blur: var(--spectrum-drop-shadow-blur);
    --spectrum-actionbar-shadow-color: var(--spectrum-drop-shadow-color);
  }
   
  /* windows high contrast mode */
  @media (forced-colors: active) {
    .spectrum-ActionBar {
      --highcontrast-actionbar-popover-border-color: CanvasText;
    }
   
    .spectrum-ActionBar--emphasized {
      .spectrum-ActionBar-popover {
        --highcontrast-actionbar-popover-border-color: CanvasText;
      }
    }
  }
   
  /* ActionBar is outer wrapper with nested popover component within */
  .spectrum-ActionBar {
    /* creates horizontal spacing to edge */
    padding: 0 var(--mod-actionbar-spacing-outer-edge, var(--spectrum-actionbar-spacing-outer-edge));
    inset-block-end: 0;
    z-index: 1;
   
    /* Account for fixed width */
    box-sizing: border-box;
   
    /* Let clicks in blank space fall through */
    pointer-events: none;
   
    /* Take up no space and be invisible when not open */
    block-size: 0;
    opacity: 0;
   
    &.is-open {
      /* add ActionBar bottom margin to height for correct spacing even when sticky */
      block-size: calc(var(--mod-actionbar-spacing-outer-edge, var(--spectrum-actionbar-spacing-outer-edge)) + var(--mod-actionbar-height, var(--spectrum-actionbar-height)));
      opacity: 1;
    }
   
    .spectrum-ActionBar-popover {
      /* popover is ActionBar height */
      block-size: var(--mod-actionbar-height, var(--spectrum-actionbar-height));
      box-sizing: border-box;
      inline-size: 100%;
      margin: auto;
      padding-block-start: 0;
      padding-block-end: 0;
   
      /* Be relative so our width can be restricted */
      position: relative;
   
      border-radius: var(--mod-actionbar-corner-radius, var(--spectrum-actionbar-corner-radius));
      border-color: var(--highcontrast-actionbar-popover-border-color, var(--mod-actionbar-popover-border-color, var(--spectrum-actionbar-popover-border-color)));
      background-color: var(--mod-actionbar-popover-background-color, var(--spectrum-actionbar-popover-background-color));
   
      filter: drop-shadow(var(--mod-actionbar-shadow-horizontal, var(--spectrum-actionbar-shadow-horizontal)) var(--mod-actionbar-shadow-vertical, var(--spectrum-actionbar-shadow-vertical)) var(--mod-actionbar-shadow-blur, var(--spectrum-actionbar-shadow-blur)) var(--mod-actionbar-shadow-color, var(--spectrum-actionbar-shadow-color)));
   
      /* Let clicks do their thing */
      pointer-events: auto;
   
      /* inner layout of content items */
      display: flex;
      flex-direction: row;
    }
   
    /* close button */
    .spectrum-CloseButton {
      margin-inline-start: var(--mod-actionbar-spacing-close-button-start, var(--spectrum-actionbar-spacing-close-button-start));
      margin-inline-end: var(--mod-actionbar-spacing-close-button-end, var(--spectrum-actionbar-spacing-close-button-end));
      margin-block-start: var(--mod-actionbar-spacing-close-button-top, var(--spectrum-actionbar-spacing-close-button-top));
      flex-shrink: 0;
    }
   
    /* item counter */
    .spectrum-FieldLabel {
      margin-inline-end: var(--mod-actionbar-spacing-item-counter-end, var(--spectrum-actionbar-spacing-item-counter-end));
      margin-block-start: var(--mod-actionbar-spacing-item-counter-top, var(--spectrum-actionbar-spacing-item-counter-top));
   
      /* neutralize padding for correct spacing within ActionBar */
      padding: 0;
   
      font-size: var(--mod-actionbar-item-counter-font-size, var(--spectrum-actionbar-item-counter-font-size));
      color: var(--mod-actionbar-item-counter-color, var(--spectrum-actionbar-item-counter-color));
      line-height: var(--mod-actionbar-item-counter-line-height, var(--spectrum-actionbar-item-counter-line-height));
   
      /* cjk language support */
      &:lang(ja),
      &:lang(zh),
      &:lang(ko) {
        line-height: var(--mod-actionbar-item-counter-line-height-cjk, var(--spectrum-actionbar-item-counter-line-height-cjk));
      }
    }
   
    /* action group */
    .spectrum-ActionGroup {
      margin-inline-end: var(--mod-actionbar-spacing-action-group-end, var(--spectrum-actionbar-spacing-action-group-end));
      margin-block-start: var(--mod-actionbar-spacing-action-group-top, var(--spectrum-actionbar-spacing-action-group-top));
      /* align to end by default */
      margin-inline-start: auto;
    }
  }
   
  .spectrum-ActionBar--emphasized {
    .spectrum-ActionBar-popover {
      filter: none;
      background-color: var(--mod-actionbar-emphasized-background-color, var(--spectrum-actionbar-emphasized-background-color));
      /* border transparent instead of none so WHCM will have visible border */
      border-color: transparent;
    }
   
    /* ensure text is legible on emphasized background */
    .spectrum-FieldLabel {
      color: var(--mod-actionbar-emphasized-item-counter-color, var(--spectrum-actionbar-emphasized-item-counter-color));
    }
  }
   
  .spectrum-ActionBar--sticky {
    inset-inline-start: 0;
    inset-inline-end: 0;
    position: sticky;
  }
   
  .spectrum-ActionBar--fixed {
     position: fixed;
  }
   
  /* flexible width */
  .spectrum-ActionBar--flexible {
    .spectrum-ActionBar-popover {
      inline-size: auto;
    }
  }
   

Puzzles of Note

Project Puzzle: Windows High Contrast Mode

Adobe's design system supports Windows High Contrast Mode. Our team was working on Apple machines, so our testing capabilities for this were limited to Chrome's browser emulation. Design specs for WHCM were provided with the appearance of Chrome's emulation of forced-colors: active combined with prefers-color-scheme: dark, so we used that combination as our standard for this test.

High Contrast Keywords

High-contrast mode utilizes a collection of keywords which can be applied to override specific component custom properties. This can give us more accessible results.

  • Highlight
  • HighlightText
  • CanvasText
  • GrayText
  • ButtonText
  • ButtonFace
  • LinkText
  • Canvas
sceenshot of Tag shown with Chrome emulation of high contrast mode and dark mode

We conducted initial high contrast mode testing using Chrome's emulation tools.

Testing Tool for High-Contrast Mode

I created an HTML document to test how these keywords look when applied as background color, border color, and text color. I asked team mates to view this on a PC and provide screenshots. I later obtained access to an Assistiv Labs emulator for more extensive testing.

These screenshots show a testing document I created to see what results were achieved from applying keywords in different ways.

High Contrast Mode and Focus States

While reviewing a PR, I became concerned that we may choose inconsistent keywords for focus states, which are particularly important for people navigating with a keyboard. Not everyone uses a mouse. If the focus state looks different for each component, it can be difficult for a keyboard user to know what they are focused on.

I consulted with Adobe's accessibility experts, using the testing document I'd created. I then built a new HTML testing document to test focus states specifically. We agreed on standardizing our color keywords for a component's focused border color (if it has a border) and the focus indicator border color and we chose keywords for these.

a documentation of focus appearance for radio, action button, close button, picker, and avatar

I created a visual comparison to demonstrate that some components had inconsistent focus appearance in high contrast mode.

visuals of component border and focus indicator standard selections

I created this document to confirm our standard high-contrast focus state selections and help build consensus about the choices.

Project Puzzle: Logical Properties

Using logical properties when positioning elements allows the system to support languages that are read right-to-left and top-to-bottom. Logical properties relate to the "start" and "end" of an element rather than left/right/top/bottom, allowing us to align and order our elements in a way that makes sense for a given language.

Logical properties can help make the web more inclusive.

a diagram of CSS logical properties for margin and padding

I was new to using logical properties on this project, so I created this diagram as a personal learning tool.

Project Puzzle: Focus Indicator Border Radius

This puzzle was a fun one. We needed to build focus indicators for these components. We had custom properties dictating component border width, border radius, the gap between the component border and the focus indicator border, and focus indicator border thickness.

We needed the border radius of the focus indicator to hug the component. Using the same border radius doesn't work, and we wanted this to to be Adobe quality, so it had to match perfectly. So we used a calc that goes a little like this:

border-radius: calc(#{$border-radius-component} + #{$gap-focus-ring} + #{$border-width-component});

...in other words,

border-radius: calc(component-border-radius + focus-indicator-gap + component-border-width);

a screenshot showing an imaginary component with and without a focus indicator

I built an imaginary component to demonstrate how to relate border radii as well as logical positioning of a focus indicator in relation to a component.

View the Codepen for This Puzzle

See the Pen Border Radius and Focus Ring Alignment - Adobe UI by Monet (@mfort) on CodePen.

Adobe Codepens

Using Codepen to Communicate and Collaborate

I used codepen extensively to solve problems I encountered on Adobe's components. View the collection:

screenashots from codepens of Adobe component work