<div class="swatch ">

    <div class="swatch__wrapper" aria-label="Color" tabindex="0" aria-activedescendant="opt-color-red-3" aria-required="true" role="listbox" aria-invalid="false">
        <div class="swatch__option-container " id="opt-color-orange-1" aria-label="Orange" tabindex="0" role="option" aria-selected="false">
            <div class="swatch__option " style="background-color: #ffa500">

            </div>
        </div>
        <div class="swatch__option-container " id="opt-color-green-2" aria-label="Green" tabindex="0" role="option" aria-selected="false">
            <div class="swatch__option " style="background-color: #00ff00">

            </div>
        </div>
        <div class="swatch__option-container selected" id="opt-color-red-3" aria-label="Red" tabindex="0" role="option" aria-selected="true">
            <div class="swatch__option " style="background-color: #ff0000">

            </div>
        </div>
        <div class="swatch__option-container " id="opt-color-black-4" aria-label="White" tabindex="0" role="option" aria-selected="false">
            <div class="swatch__option " style="background-color: #000000">

            </div>
        </div>
    </div>
</div>

<script src="/components/raw/swatch/swatch.js"></script>
<div class="swatch {{ class }}">
    {{#if heading}}
        <{{{ heading.tag }}}
            id="{{ heading.id }}"
            class="swatch__title {{ heading.class }}"
        >
            {{ heading.text }}
        </{{{ heading.tag }}}>
    {{/if}}

    <div
        class="swatch__wrapper"
        {{{ wrapper.attributes }}}
    >
        {{#each options }}
            <div
                class="swatch__option-container {{ this.class }}"
                id="{{ this.id }}"
                {{{ this.attributes }}}
                {{#if this.headingId}}
                    aria-describedby="{{ this.headingId }}"
                {{/if}}
            >
                <div
                    class="swatch__option {{ this.option.class}}"
                    {{{ this.option.attributes }}}
                >
                    {{ this.text }}
                </div>
            </div>
        {{/each }}
    </div>
</div>

{{#if script}}
    <script src="{{static 'swatch.js' }}"></script>
{{/if}}
{
  "script": true,
  "heading": false,
  "wrapper": {
    "attributes": "aria-label=\"Color\" tabindex=\"0\" aria-activedescendant=\"opt-color-red-3\" aria-required=\"true\" role=\"listbox\" aria-invalid=\"false\""
  },
  "options": [
    {
      "id": "opt-color-orange-1",
      "attributes": "aria-label=\"Orange\" tabindex=\"0\" role=\"option\" aria-selected=\"false\"",
      "option": {
        "attributes": "style=\"background-color: #ffa500\""
      }
    },
    {
      "id": "opt-color-green-2",
      "attributes": "aria-label=\"Green\" tabindex=\"0\" role=\"option\" aria-selected=\"false\"",
      "option": {
        "attributes": "style=\"background-color: #00ff00\""
      }
    },
    {
      "id": "opt-color-red-3",
      "class": "selected",
      "attributes": "aria-label=\"Red\" tabindex=\"0\" role=\"option\" aria-selected=\"true\"",
      "option": {
        "attributes": "style=\"background-color: #ff0000\""
      }
    },
    {
      "id": "opt-color-black-4",
      "attributes": "aria-label=\"White\" tabindex=\"0\" role=\"option\" aria-selected=\"false\"",
      "option": {
        "attributes": "style=\"background-color: #000000\""
      }
    }
  ]
}
  • Content:
    $swatch__margin                       : 0 !default;
    $swatch__margin--catalog              : 0 0 $spacer 0 !default;
    $swatch__transition                   : $transition-base !default;
    
    $swatch__wrapper-justify              : flex-start !default;
    $swatch__container-min-height         : 100px !default;
    $swatch__container-min-height--catalog: 56px !default;
    
    $swatch__title-margin                 : 0 0 $spacer !default;
    $swatch__title-font-size              : $font-size-base !default;
    $swatch__title-color                  : $color-secondary !default;
    
    $swatch__option-width                 : 40px !default;
    $swatch__option-height                : 40px !default;
    $swatch__option-width--catalog        : 30px !default;
    $swatch__option-height--catalog       : 30px !default;
    $swatch__option-width--image          : 40px !default;
    $swatch__option-height--image         : 40px !default;
    $swatch__option-margin                : 0 $spacer 0 0 !default;
    $swatch__option-margin--catalog       : 0 !default;
    $swatch__option-padding               : 0 0 1px 0 !default;
    $swatch__option-padding--catalog      : $spacer !default;
    $swatch__option-background            : $white !default;
    $swatch__option-color                 : $color-secondary !default;
    $swatch__option-border                : 2px solid $white !default;
    $swatch__option-border-width          : 0 0 2px 0 !default;
    $swatch__option-border--white         : 2px $border-style-base $gray-lighter !default;
    $swatch__option-border--white-catalog : $border-width-base $border-style-base $gray-light !default;
    $swatch__option-border-color--active  : $color-primary !default;
    $swatch__option-border--size          : $border-width-base $border-style-base $gray-light !default;
    
  • URL: /components/raw/swatch/_swatch-variables.scss
  • Filesystem Path: build/components/Molecules/swatch/_swatch-variables.scss
  • Size: 1.7 KB
  • Content:
    @import 'swatch-variables';
    
    .swatch {
        display: flex;
        flex-wrap: wrap;
        margin: $swatch__margin;
    
        &[class*="size"] {
            .swatch__option {
                border: $swatch__option-border--size;
            }
        }
    
        &__container {
            // magento container for loading
            position: relative;
            min-height: $swatch__container-min-height;
    
            &--catalog {
                width: 100%;
                min-height: $swatch__container-min-height--catalog;
    
                .swatch {
                    margin: $swatch__margin--catalog;
    
                    &__option-container {
                        padding: $swatch__option-padding--catalog;
                        margin: $swatch__option-margin--catalog;
    
                        &:hover,
                        &:focus,
                        &.selected {
                            .swatch__option--white {
                                border: $swatch__option-border--white-catalog;
                            }
                        }
                    }
    
                    &__option {
                        min-width: $swatch__option-width--catalog;
                        min-height: $swatch__option-height--catalog;
    
                        &--white {
                            border: $swatch__option-border--white-catalog;
                        }
                    }
                }
    
            }
        }
    
        &__wrapper {
            display: flex;
            flex-wrap: wrap;
            width: 100%;
            justify-content: $swatch__wrapper-justify;
        }
    
        &__option-container {
            box-sizing: border-box;
            border: $swatch__option-border;
            border-width: $swatch__option-border-width;
            margin: $swatch__option-margin;
            padding: $swatch__option-padding;
            transition: $swatch__transition;
            cursor: pointer;
    
            &:hover,
            &:focus,
            &.selected {
                outline: none;
                border-color: $swatch__option-border-color--active;
    
                .swatch__option--white {
                    border: 0;
                }
            }
    
            &.disabled {
                opacity: 0.5;
                cursor: not-allowed;
            }
    
            &:last-child {
                margin-right: 0;
            }
        }
    
        &__selected-option {
            display: none;
        }
    
        &__title {
            flex: 0 0 100%;
            margin: $swatch__title-margin;
            font-size: $swatch__title-font-size;
            color: $swatch__title-color;
        }
    
        &__option {
            display: flex;
            justify-content: center;
            align-items: center;
            min-width: $swatch__option-width;
            min-height: $swatch__option-height;
            background-color: $swatch__option-background;
            color: $swatch__option-color;
    
            &--image {
                min-height: $swatch__option-height--image;
                min-width: $swatch__option-width--image;
                background-size: cover;
                background-position: top center;
            }
    
            &--white {
                border: $swatch__option-border--white;
            }
        }
    
        &__input {
            @include visually-hidden;
        }
    }
    
  • URL: /components/raw/swatch/_swatch.scss
  • Filesystem Path: build/components/Molecules/swatch/_swatch.scss
  • Size: 3 KB
  • Content:
    'use strict';
    
    (function() { // eslint-disable-line
      const swatchWrapper = document.querySelector('.swatch__wrapper');
      const swatchOptions = swatchWrapper.querySelectorAll('.swatch__option-container');
    
      swatchOptions.forEach(option => {
        option.addEventListener('click', () => {
          swatchOptions.forEach(option => {
            option.classList.remove('selected');
            option.setAttribute('aria-selected', 'false');
          })
    
          option.classList.add('selected');
          swatchWrapper.setAttribute('aria-activedescendant', option.id);
          option.setAttribute('aria-selected', 'true');
        })
      })
    })();
    
  • URL: /components/raw/swatch/swatch.js
  • Filesystem Path: build/components/Molecules/swatch/swatch.js
  • Size: 617 Bytes

Swatches accessibility

Swatches require following aria structure to be accessible for assistive technology and keyboard:

  1. Swatches wrapper .swatch__wrapper should have following aria attributes:

    • role="listbox" to easy list swatch’s options
    • aria-activedescendant with value of selected swatch option
    • tabindex="0" to make element focusable
    • aria-required with value true or false depending if swatch option is required
    • aria-invalid with value true if required field is not filled (if none of options is selected) or with the value false if required field is field (option is selected)
  2. Swatch option .swatch__option should have following aria attributes:

    • role="option" to show options of swatch
    • tabindex="0" to make element focusable
    • aria-label with value of the name/label of swatch option if the swatch option is not a text but graphic element - icon or image (color option - ex: “Green”)
    • aria-describedby with value of the listbox label/heading if exist
    • aria-selected with value true if the option was selected